From f61a0483909323e1cf1ff964305c2c07a6a74b61 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Fri, 21 Jan 2022 10:44:31 +0100 Subject: [PATCH 001/328] - ownCloudApp.framework: - add OCViewHost as container for views provided via OCViewProviders / OCResourceRequests - add view providers for OCAvatar, OCResourceTextPlaceholder - add OCCircularContentView, OCCircularImageView, OCCircularTextView - BookmarkViewController: download a users's avatar and save it in the bookmark - ClientRootViewController: check for an updated version of the user's avatar on every connect - GroupSharingTableViewController: display avatars instead of icons for users - StaticTableViewRow: add support for leading accessory cell - ThemeTableViewCell: extend support for custom layout, revise CellLayouter to support single line of text - remove bolted-on QuickLook thumbnailing (now covered by the new OCResource system) - update static/single login + accounts list UI to work with avatars --- ios-sdk | 2 +- ownCloud.xcodeproj/project.pbxproj | 64 ++++++ .../xcshareddata/xcschemes/ownCloud.xcscheme | 5 + .../Bookmarks/BookmarkViewController.swift | 59 +++--- .../Client/ClientRootViewController.swift | 19 ++ .../Client/Viewer/DisplayViewController.swift | 2 +- .../Issues/IssuesCardViewController.swift | 8 +- .../Server List/ServerListBookmarkCell.swift | 23 +- .../ServerListTableHeaderView.swift | 12 +- .../ServerListTableViewController.swift | 71 +++++-- ...ingleAccountServerListViewController.swift | 42 +++- .../StaticLoginStepViewController.swift | 40 ++-- ownCloud/Static Login/StaticLoginBundle.swift | 6 +- .../View Providers/OCAvatar+ViewProvider.h | 27 +++ .../View Providers/OCAvatar+ViewProvider.m | 43 ++++ .../OCResourceTextPlaceholder+ViewProvider.h | 27 +++ .../OCResourceTextPlaceholder+ViewProvider.m | 43 ++++ .../View Providers/OCViewHost.h | 42 ++++ .../View Providers/OCViewHost.m | 196 ++++++++++++++++++ .../Views/OCCircularContentView.h | 30 +++ .../Views/OCCircularContentView.m | 72 +++++++ .../Views/OCCircularImageView.h | 32 +++ .../Views/OCCircularImageView.m | 59 ++++++ .../Views/OCCircularTextView.h | 29 +++ .../Views/OCCircularTextView.m | 64 ++++++ ownCloudAppFramework/ownCloudApp.h | 6 + .../GroupSharingTableViewController.swift | 22 +- .../UIImageView+Thumbnails.swift | 78 ++----- .../User Interface/More/MoreViewHeader.swift | 6 +- .../StaticTableView/StaticTableViewRow.swift | 12 +- .../Theme/UI/ThemeTableViewCell.swift | 106 +++++++--- 31 files changed, 1058 insertions(+), 189 deletions(-) create mode 100644 ownCloudAppFramework/View Providers/OCAvatar+ViewProvider.h create mode 100644 ownCloudAppFramework/View Providers/OCAvatar+ViewProvider.m create mode 100644 ownCloudAppFramework/View Providers/OCResourceTextPlaceholder+ViewProvider.h create mode 100644 ownCloudAppFramework/View Providers/OCResourceTextPlaceholder+ViewProvider.m create mode 100644 ownCloudAppFramework/View Providers/OCViewHost.h create mode 100644 ownCloudAppFramework/View Providers/OCViewHost.m create mode 100644 ownCloudAppFramework/Views/OCCircularContentView.h create mode 100644 ownCloudAppFramework/Views/OCCircularContentView.m create mode 100644 ownCloudAppFramework/Views/OCCircularImageView.h create mode 100644 ownCloudAppFramework/Views/OCCircularImageView.m create mode 100644 ownCloudAppFramework/Views/OCCircularTextView.h create mode 100644 ownCloudAppFramework/Views/OCCircularTextView.m diff --git a/ios-sdk b/ios-sdk index 00e01d20d..7c8d3a437 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit 00e01d20dafd90eaa68ece1b1cac8004f66f7ccb +Subproject commit 7c8d3a43769b05c4da231a613d5fb8d6f65ba3d9 diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 15526c668..2dfad2358 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -496,6 +496,14 @@ DCE4E4C724C255E00051722F /* AppExtensionNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE4E4C624C255E00051722F /* AppExtensionNavigationController.swift */; }; DCE684F6241BD4E800799C30 /* Branding.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3931206A2326451900E8DFBA /* Branding.plist */; }; DCEE1C9C23A0EADD00FE8D98 /* LicenseOfferView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCEE1C9B23A0EADD00FE8D98 /* LicenseOfferView.swift */; }; + DCF072D72798558B00E0B01D /* OCCircularImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF072D5279850CC00E0B01D /* OCCircularImageView.m */; }; + DCF072D82798559900E0B01D /* OCCircularImageView.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF072D4279850CC00E0B01D /* OCCircularImageView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCF072DC279857A300E0B01D /* OCCircularContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF072D9279856A700E0B01D /* OCCircularContentView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCF072DD279857C800E0B01D /* OCCircularContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF072DA279856A700E0B01D /* OCCircularContentView.m */; }; + DCF072E02798592100E0B01D /* OCCircularTextView.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF072DE2798592100E0B01D /* OCCircularTextView.h */; }; + DCF072E12798592100E0B01D /* OCCircularTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF072DF2798592100E0B01D /* OCCircularTextView.m */; }; + DCF072EC27986CCA00E0B01D /* OCResourceTextPlaceholder+ViewProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF072EA27986CCA00E0B01D /* OCResourceTextPlaceholder+ViewProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCF072ED27986CCA00E0B01D /* OCResourceTextPlaceholder+ViewProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF072EB27986CCA00E0B01D /* OCResourceTextPlaceholder+ViewProvider.m */; }; DCF2DA7A24C82E480026D790 /* FileProviderServiceSource.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF2DA7924C82E480026D790 /* FileProviderServiceSource.m */; }; DCF2DA7D24C835BF0026D790 /* OCVault+FPServices.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF2DA7B24C835BF0026D790 /* OCVault+FPServices.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCF2DA7E24C835BF0026D790 /* OCVault+FPServices.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF2DA7C24C835BF0026D790 /* OCVault+FPServices.m */; }; @@ -504,6 +512,10 @@ DCF2DA8324C83BFB0026D790 /* OCFileProviderService.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF2DA7524C82C9F0026D790 /* OCFileProviderService.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCF2DA8624C87A330026D790 /* OCCore+FPServices.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF2DA8424C87A330026D790 /* OCCore+FPServices.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCF2DA8724C87A330026D790 /* OCCore+FPServices.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF2DA8524C87A330026D790 /* OCCore+FPServices.m */; }; + DCF575EB2796CBDF003BEBBA /* OCAvatar+ViewProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF575E92796CBDF003BEBBA /* OCAvatar+ViewProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCF575EC2796CBDF003BEBBA /* OCAvatar+ViewProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF575EA2796CBDF003BEBBA /* OCAvatar+ViewProvider.m */; }; + DCF575EF2796CE38003BEBBA /* OCViewHost.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF575ED2796CE38003BEBBA /* OCViewHost.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCF575F02796CE38003BEBBA /* OCViewHost.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF575EE2796CE38003BEBBA /* OCViewHost.m */; }; DCFB74BB21AD5C46005796AF /* StaticLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC44344321AC031600376B16 /* StaticLoginViewController.swift */; }; DCFB74C121AD5C88005796AF /* StaticLoginStepViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4434C321AC894700376B16 /* StaticLoginStepViewController.swift */; }; DCFB74C221AD5D10005796AF /* StaticLoginSetupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4434C521AC898700376B16 /* StaticLoginSetupViewController.swift */; }; @@ -1460,6 +1472,14 @@ DCE974BB207EACA60069FC2B /* UIImage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = ""; }; DCEC3DE3242F665D0076B43C /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; DCEE1C9B23A0EADD00FE8D98 /* LicenseOfferView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicenseOfferView.swift; sourceTree = ""; }; + DCF072D4279850CC00E0B01D /* OCCircularImageView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCCircularImageView.h; sourceTree = ""; }; + DCF072D5279850CC00E0B01D /* OCCircularImageView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCCircularImageView.m; sourceTree = ""; }; + DCF072D9279856A700E0B01D /* OCCircularContentView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCCircularContentView.h; sourceTree = ""; }; + DCF072DA279856A700E0B01D /* OCCircularContentView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCCircularContentView.m; sourceTree = ""; }; + DCF072DE2798592100E0B01D /* OCCircularTextView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCCircularTextView.h; sourceTree = ""; }; + DCF072DF2798592100E0B01D /* OCCircularTextView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCCircularTextView.m; sourceTree = ""; }; + DCF072EA27986CCA00E0B01D /* OCResourceTextPlaceholder+ViewProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCResourceTextPlaceholder+ViewProvider.h"; sourceTree = ""; }; + DCF072EB27986CCA00E0B01D /* OCResourceTextPlaceholder+ViewProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCResourceTextPlaceholder+ViewProvider.m"; sourceTree = ""; }; DCF2DA7524C82C9F0026D790 /* OCFileProviderService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCFileProviderService.h; sourceTree = ""; }; DCF2DA7824C82E480026D790 /* FileProviderServiceSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileProviderServiceSource.h; sourceTree = ""; }; DCF2DA7924C82E480026D790 /* FileProviderServiceSource.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FileProviderServiceSource.m; sourceTree = ""; }; @@ -1473,6 +1493,10 @@ DCF4F17A20519F9D00189B9A /* StaticTableViewSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticTableViewSection.swift; sourceTree = ""; }; DCF4F17E2051A0D000189B9A /* StaticTableViewRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticTableViewRow.swift; sourceTree = ""; }; DCF4F18A2052BA4C00189B9A /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; + DCF575E92796CBDF003BEBBA /* OCAvatar+ViewProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCAvatar+ViewProvider.h"; sourceTree = ""; }; + DCF575EA2796CBDF003BEBBA /* OCAvatar+ViewProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCAvatar+ViewProvider.m"; sourceTree = ""; }; + DCF575ED2796CE38003BEBBA /* OCViewHost.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCViewHost.h; sourceTree = ""; }; + DCF575EE2796CE38003BEBBA /* OCViewHost.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCViewHost.m; sourceTree = ""; }; DCFB74C321AD7E18005796AF /* StaticLoginServerListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLoginServerListViewController.swift; sourceTree = ""; }; DCFED971208095E200A2D984 /* ClientItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientItemCell.swift; sourceTree = ""; }; DCFED9B920809B8900A2D984 /* ThemeTVGResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeTVGResource.swift; sourceTree = ""; }; @@ -2688,6 +2712,8 @@ DCC5E443232654C1002E5B84 /* Foundation Extensions */, DCFEFE2223687637009A142F /* Licensing */, DCC832E5242CB14E00153F8C /* Notifications */, + DCF072D02798504E00E0B01D /* Views */, + DCF575E52796CBB3003BEBBA /* View Providers */, DC774E5422F44DF6000B11A1 /* SDK Extensions */, DC0030BE2350B1CE00BB8570 /* Tools */, DC774E5B22F44E4A000B11A1 /* ZIP Archive */, @@ -2998,6 +3024,19 @@ path = Offers; sourceTree = ""; }; + DCF072D02798504E00E0B01D /* Views */ = { + isa = PBXGroup; + children = ( + DCF072DA279856A700E0B01D /* OCCircularContentView.m */, + DCF072D9279856A700E0B01D /* OCCircularContentView.h */, + DCF072D5279850CC00E0B01D /* OCCircularImageView.m */, + DCF072D4279850CC00E0B01D /* OCCircularImageView.h */, + DCF072DF2798592100E0B01D /* OCCircularTextView.m */, + DCF072DE2798592100E0B01D /* OCCircularTextView.h */, + ); + path = Views; + sourceTree = ""; + }; DCF2DA6F24C82C820026D790 /* File Provider Services */ = { isa = PBXGroup; children = ( @@ -3077,6 +3116,19 @@ path = Tools; sourceTree = ""; }; + DCF575E52796CBB3003BEBBA /* View Providers */ = { + isa = PBXGroup; + children = ( + DCF575EE2796CE38003BEBBA /* OCViewHost.m */, + DCF575ED2796CE38003BEBBA /* OCViewHost.h */, + DCF575EA2796CBDF003BEBBA /* OCAvatar+ViewProvider.m */, + DCF575E92796CBDF003BEBBA /* OCAvatar+ViewProvider.h */, + DCF072EB27986CCA00E0B01D /* OCResourceTextPlaceholder+ViewProvider.m */, + DCF072EA27986CCA00E0B01D /* OCResourceTextPlaceholder+ViewProvider.h */, + ); + path = "View Providers"; + sourceTree = ""; + }; DCFEFE2223687637009A142F /* Licensing */ = { isa = PBXGroup; children = ( @@ -3191,20 +3243,26 @@ buildActionMask = 2147483647; files = ( DC7C101124B5FA7700227085 /* OCBookmark+AppExtensions.h in Headers */, + DCF072E02798592100E0B01D /* OCCircularTextView.h in Headers */, DCDC20AB2399A8CF003CFF5B /* OCLicenseEnterpriseProvider.h in Headers */, DCD8109A23984AF2003B0053 /* OCLicenseDuration.h in Headers */, DCFEFE2A236876BD009A142F /* OCLicenseManager.h in Headers */, DCFEFE4923687C83009A142F /* OCLicenseEntitlement.h in Headers */, DC4332002472E1B4002DC0E5 /* OCLicenseEMMProvider.h in Headers */, + DCF072DC279857A300E0B01D /* OCCircularContentView.h in Headers */, DCFEFE39236877A7009A142F /* OCLicenseFeature.h in Headers */, DC23D1DA238F391200423F62 /* OCLicenseAppStoreReceipt.h in Headers */, DC70398526128B89009F2DC1 /* NSString+ByteCountParser.h in Headers */, + DCF072EC27986CCA00E0B01D /* OCResourceTextPlaceholder+ViewProvider.h in Headers */, DCF2DA8324C83BFB0026D790 /* OCFileProviderService.h in Headers */, DCF2DA8624C87A330026D790 /* OCCore+FPServices.h in Headers */, + DCF575EF2796CE38003BEBBA /* OCViewHost.h in Headers */, DC774E6022F44E57000B11A1 /* ZIPArchive.h in Headers */, DC24B28725BA2A2E005783E2 /* Branding.h in Headers */, + DCF072D82798559900E0B01D /* OCCircularImageView.h in Headers */, DCFEFE9C2368D7FA009A142F /* OCLicenseObserver.h in Headers */, DCFEFE2E236876D4009A142F /* OCLicenseProvider.h in Headers */, + DCF575EB2796CBDF003BEBBA /* OCAvatar+ViewProvider.h in Headers */, DCC832F3242CC28900153F8C /* NotificationManager.h in Headers */, DCC085802293F490008CC05C /* DisplaySettings.h in Headers */, DCFEFE3D236877B7009A142F /* OCLicenseProduct.h in Headers */, @@ -4321,8 +4379,10 @@ files = ( DCFEFE9D2368D7FA009A142F /* OCLicenseObserver.m in Sources */, DC66F39D239659C000CF4812 /* OCASN1.m in Sources */, + DCF072ED27986CCA00E0B01D /* OCResourceTextPlaceholder+ViewProvider.m in Sources */, DCCD778C2604C91B00098573 /* NSDate+ComputedTimes.m in Sources */, DC66F3A623965A1400CF4812 /* NSDate+RFC3339.m in Sources */, + DCF575EC2796CBDF003BEBBA /* OCAvatar+ViewProvider.m in Sources */, DCF2DA8724C87A330026D790 /* OCCore+FPServices.m in Sources */, DC7C101224B5FD6500227085 /* OCBookmark+AppExtensions.m in Sources */, DC4332012472E1B4002DC0E5 /* OCLicenseEMMProvider.m in Sources */, @@ -4340,7 +4400,9 @@ DC70398626128B89009F2DC1 /* NSString+ByteCountParser.m in Sources */, DCFEFE3A236877A7009A142F /* OCLicenseFeature.m in Sources */, DCFEFE50236880B5009A142F /* OCLicenseOffer.m in Sources */, + DCF072DD279857C800E0B01D /* OCCircularContentView.m in Sources */, DC0030C12350B1CE00BB8570 /* NSData+Encoding.m in Sources */, + DCF072D72798558B00E0B01D /* OCCircularImageView.m in Sources */, DC774E5F22F44E57000B11A1 /* ZIPArchive.m in Sources */, DCDBB60B2525306000FAD707 /* NotificationAuthErrorForwarder.m in Sources */, DCD71E8027427463001592C6 /* BuildOptions.m in Sources */, @@ -4350,9 +4412,11 @@ DCB2C061250C253C001083CA /* BrandingClassSettingsSource.m in Sources */, DCFEFE2F236876D4009A142F /* OCLicenseProvider.m in Sources */, DCC832F4242CC28F00153F8C /* NotificationManager.m in Sources */, + DCF072E12798592100E0B01D /* OCCircularTextView.m in Sources */, DCFEFE4A23687C83009A142F /* OCLicenseEntitlement.m in Sources */, DC66F3AC23965C9C00CF4812 /* OCLicenseAppStoreReceiptInAppPurchase.m in Sources */, DCD8109B23984AF6003B0053 /* OCLicenseDuration.m in Sources */, + DCF575F02796CE38003BEBBA /* OCViewHost.m in Sources */, DCB458EE2604A7D4006A02AB /* OCQueryCondition+SearchSegmenter.m in Sources */, DC080CF3238C92480044C5D2 /* OCLicenseAppStoreItem.m in Sources */, DCFEFE982368D099009A142F /* OCLicenseEnvironment.m in Sources */, diff --git a/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme b/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme index 3c75e5b84..1a01a417a 100644 --- a/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme +++ b/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme @@ -200,6 +200,11 @@ value = "0" isEnabled = "YES"> + + Bool in return (message.bookmarkUUID == bookmark.uuid) && !message.resolved @@ -165,7 +174,7 @@ class ServerListBookmarkCell : ThemeTableViewCell { self.titleLabel.applyThemeCollection(collection, itemStyle: .title, itemState: itemState) self.detailLabel.applyThemeCollection(collection, itemStyle: .message, itemState: itemState) if !VendorServices.shared.isBranded { - self.iconView.image = self.iconView.image?.tinted(with: collection.tableRowColors.labelColor) + self.logoFallbackView.image = self.logoFallbackView.image?.tinted(with: collection.tableRowColors.labelColor) } } @@ -177,7 +186,7 @@ class ServerListBookmarkCell : ThemeTableViewCell { self.titleLabel.applyThemeCollection(collection, itemStyle: .title, itemState: itemState) self.detailLabel.applyThemeCollection(collection, itemStyle: .message, itemState: itemState) if !VendorServices.shared.isBranded { - self.iconView.image = self.iconView.image?.tinted(with: collection.tableRowColors.labelColor) + self.logoFallbackView.image = self.logoFallbackView.image?.tinted(with: collection.tableRowColors.labelColor) } } } diff --git a/ownCloud/Server List/ServerListTableHeaderView.swift b/ownCloud/Server List/ServerListTableHeaderView.swift index a7ab562d1..c9bed6de9 100644 --- a/ownCloud/Server List/ServerListTableHeaderView.swift +++ b/ownCloud/Server List/ServerListTableHeaderView.swift @@ -24,8 +24,8 @@ class ServerListTableHeaderView: UIView, Themeable { // MARK: - Constants fileprivate let shadowHeight: CGFloat = 1.0 fileprivate let textLabelTopMargin: CGFloat = 10.0 - fileprivate let textLabelHorizontalMargin: CGFloat = 20.0 - fileprivate let textLabelHeight: CGFloat = 24.0 + fileprivate let textLabelHorizontalMargin: CGFloat = 16.0 + fileprivate let textLabelHeight: CGFloat = 18.0 // MARK: - Instance variables. var messageThemeApplierToken : ThemeApplierToken? @@ -38,7 +38,7 @@ class ServerListTableHeaderView: UIView, Themeable { textLabel.translatesAutoresizingMaskIntoConstraints = false textLabel.text = "Accounts".localized - textLabel.font = UIFont.boldSystemFont(ofSize: 24.0) + textLabel.font = UIFont.preferredFont(forTextStyle: .headline) self.addSubview(textLabel) @@ -58,10 +58,8 @@ class ServerListTableHeaderView: UIView, Themeable { textLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: textLabelTopMargin), textLabel.leftAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leftAnchor, constant: textLabelHorizontalMargin), - textLabel.rightAnchor.constraint(equalTo: self.safeAreaLayoutGuide.rightAnchor, constant: -textLabelHorizontalMargin), - textLabel.heightAnchor.constraint(equalToConstant: textLabelHeight) - - ]) + textLabel.rightAnchor.constraint(equalTo: self.safeAreaLayoutGuide.rightAnchor, constant: -textLabelHorizontalMargin) + ]) messageThemeApplierToken = Theme.shared.add(applier: { [weak self] (_, collection, _) in self?.backgroundColor = collection.navigationBarColors.backgroundColor diff --git a/ownCloud/Server List/ServerListTableViewController.swift b/ownCloud/Server List/ServerListTableViewController.swift index cef56d2b2..83ad1dc65 100644 --- a/ownCloud/Server List/ServerListTableViewController.swift +++ b/ownCloud/Server List/ServerListTableViewController.swift @@ -74,22 +74,6 @@ class ServerListTableViewController: UITableViewController, Themeable, StateRest NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil) } - // TODO: Rebuild welcomeOverlayView in code - /* - override func loadView() { - super.loadView() - - welcomeOverlayView = UIView() - welcomeOverlayView.translatesAutoresizingMaskIntoConstraints = false - - welcomeTitleLabel = UILabel() - welcomeTitleLabel.font = UIFont.boldSystemFont(ofSize: 34) - welcomeTitleLabel.translatesAutoresizingMaskIntoConstraints = false - - welcomeAddServerButton = ThemeButton() - } - */ - // MARK: - View controller events override func viewDidLoad() { super.viewDidLoad() @@ -120,7 +104,58 @@ class ServerListTableViewController: UITableViewController, Themeable, StateRest welcomeLogoTVGView.vectorImage = Theme.shared.tvgImage(for: "owncloud-logo") } - self.navigationItem.title = VendorServices.shared.appName + let logoImage = UIImage(named: "branding-login-logo") + let logoImageView = UIImageView(image: logoImage) + logoImageView.contentMode = .scaleAspectFit + logoImageView.translatesAutoresizingMaskIntoConstraints = false + if let logoImage = logoImage { + // Keep aspect ratio + scale logo to 90% of available height + logoImageView.widthAnchor.constraint(equalTo: logoImageView.heightAnchor, multiplier: (logoImage.size.width / logoImage.size.height) * 0.9).isActive = true + } + + let logoLabel = UILabel() + logoLabel.translatesAutoresizingMaskIntoConstraints = false + logoLabel.text = VendorServices.shared.appName + logoLabel.font = UIFont.systemFont(ofSize: 20, weight: .bold) + logoLabel.setContentCompressionResistancePriority(.required, for: .horizontal) + logoLabel.setContentCompressionResistancePriority(.required, for: .vertical) + + let logoContainer = UIView() + logoContainer.translatesAutoresizingMaskIntoConstraints = false + logoContainer.addSubview(logoImageView) + logoContainer.addSubview(logoLabel) + logoContainer.setContentHuggingPriority(.required, for: .horizontal) + logoContainer.setContentHuggingPriority(.required, for: .vertical) + + let logoWrapperView = ThemeView() + logoWrapperView.addSubview(logoContainer) + + NSLayoutConstraint.activate([ + logoImageView.topAnchor.constraint(greaterThanOrEqualTo: logoContainer.topAnchor), + logoImageView.bottomAnchor.constraint(lessThanOrEqualTo: logoContainer.bottomAnchor), + logoImageView.centerYAnchor.constraint(equalTo: logoContainer.centerYAnchor), + logoLabel.topAnchor.constraint(greaterThanOrEqualTo: logoContainer.topAnchor), + logoLabel.bottomAnchor.constraint(lessThanOrEqualTo: logoContainer.bottomAnchor), + logoLabel.centerYAnchor.constraint(equalTo: logoContainer.centerYAnchor), + + logoImageView.leadingAnchor.constraint(equalTo: logoContainer.leadingAnchor), + logoLabel.leadingAnchor.constraint(equalToSystemSpacingAfter: logoImageView.trailingAnchor, multiplier: 1), + logoLabel.trailingAnchor.constraint(equalTo: logoContainer.trailingAnchor), + + logoContainer.topAnchor.constraint(equalTo: logoWrapperView.topAnchor), + logoContainer.bottomAnchor.constraint(equalTo: logoWrapperView.bottomAnchor), + logoContainer.centerXAnchor.constraint(equalTo: logoWrapperView.centerXAnchor) + ]) + + logoWrapperView.addThemeApplier({ (_, collection, _) in + logoLabel.applyThemeCollection(collection, itemStyle: .logo) + if !VendorServices.shared.isBranded { + logoImageView.image = logoImageView.image?.tinted(with: collection.navigationBarColors.labelColor) + } + }) + + self.navigationItem.largeTitleDisplayMode = .never + self.navigationItem.titleView = logoWrapperView if #available(iOS 13, *) { } else { // Log in automatically on iOS 12 (handled by scene restoration in iOS 13+) @@ -365,7 +400,7 @@ class ServerListTableViewController: UITableViewController, Themeable, StateRest } // Add Header View - self.tableView.tableHeaderView = ServerListTableHeaderView(frame: CGRect(x: 0.0, y: 0.0, width: self.view.frame.size.width, height: 50.0)) + self.tableView.tableHeaderView = ServerListTableHeaderView(frame: CGRect(x: 0.0, y: 0.0, width: self.view.frame.size.width, height: 40.0)) self.navigationController?.navigationBar.shadowImage = UIImage() self.tableView.tableHeaderView?.applyThemeCollection(Theme.shared.activeCollection) diff --git a/ownCloud/Static Login/Interface/StaticLoginSingleAccountServerListViewController.swift b/ownCloud/Static Login/Interface/StaticLoginSingleAccountServerListViewController.swift index 3981767fd..0d0bb1171 100644 --- a/ownCloud/Static Login/Interface/StaticLoginSingleAccountServerListViewController.swift +++ b/ownCloud/Static Login/Interface/StaticLoginSingleAccountServerListViewController.swift @@ -18,6 +18,7 @@ import UIKit import ownCloudSDK +import ownCloudApp import ownCloudAppShared import CoreMedia @@ -128,6 +129,8 @@ class StaticLoginSingleAccountServerListViewController: ServerListTableViewContr override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) +// tableView.reloadSections(IndexSet(integer: SingleAccountSection.accessFiles.rawValue), with: .none) +// staticLoginViewController?.navigationController?.setNeedsStatusBarAppearanceUpdate() } @@ -259,12 +262,43 @@ class StaticLoginSingleAccountServerListViewController: ServerListTableViewContr override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { if SingleAccountSection(rawValue: section) == .accessFiles { - if headerView == nil, let bookmark : OCBookmark = OCBookmarkManager.shared.bookmarks.first, let displayName = self.displayName ?? bookmark.userName { - let headerText = String(format: "You are connected as\n%@".localized, displayName) + if let bookmark : OCBookmark = OCBookmarkManager.shared.bookmarks.first, let displayName = self.displayName ?? bookmark.userName { + + var avatarView : OCViewHost? + let avatarSize = CGSize(width: 128, height: 128) + let fallbackView = UIImageView(image: UIImage(named: "branding-login-logo")) + + if let avatar = bookmark.avatar { + avatarView = OCViewHost(viewProvider: avatar, fallbackSize: avatarSize, fallbackView: fallbackView, viewProviderContext: nil) + } else { + avatarView = OCViewHost(fallbackView: fallbackView, viewProviderContext: nil) + } + + if let avatarView = avatarView { + avatarView.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + avatarView.widthAnchor.constraint(equalToConstant: avatarSize.width), + avatarView.heightAnchor.constraint(equalToConstant:avatarSize.height) + ]) + } + + let attributedTitle = NSMutableAttributedString() + + attributedTitle.append(NSAttributedString(string: displayName.appendingFormat("\n"), attributes: [ + NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 24) + ])) + + if let serverName = bookmark.url?.host { + attributedTitle.append(NSAttributedString(string: serverName, attributes: [ + NSAttributedString.Key.font : UIFont.systemFont(ofSize: 18) + ])) + } + if VendorServices.shared.isBranded { - headerView = StaticTableViewSection.buildHeader(title: headerText) + headerView = StaticTableViewSection.buildHeader(attributedTitle: attributedTitle) } else { - headerView = StaticTableViewSection.buildHeader(title: headerText, image: UIImage(named: "branding-login-logo"), topSpacing: 10) + headerView = StaticTableViewSection.buildHeader(attributedTitle: attributedTitle, imageViewReplacement: avatarView, topSpacing: 40, intermediateSpacing: 20, bottomSpacing: 40) } } diff --git a/ownCloud/Static Login/Interface/StaticLoginStepViewController.swift b/ownCloud/Static Login/Interface/StaticLoginStepViewController.swift index 7e5486a98..429a2c84a 100644 --- a/ownCloud/Static Login/Interface/StaticLoginStepViewController.swift +++ b/ownCloud/Static Login/Interface/StaticLoginStepViewController.swift @@ -67,33 +67,44 @@ class FullWidthHeaderView : ThemeView { } extension StaticTableViewSection { - static func buildHeader(title: String, message: String? = nil, image: UIImage? = nil, cellSpacing: CGFloat = 20, topSpacing: CGFloat = 30, bottomSpacing: CGFloat = 20, imageWidth: CGFloat = 100) -> UIView { + static func buildHeader(title: String? = nil, textStyle: UIFont.TextStyle = .title2, attributedTitle: NSAttributedString? = nil, message: String? = nil, image: UIImage? = nil, imageViewReplacement: UIView? = nil, cellSpacing: CGFloat = 20, topSpacing: CGFloat = 30, intermediateSpacing: CGFloat? = nil, bottomSpacing: CGFloat = 20, imageWidth: CGFloat = 100) -> UIView { let horizontalPadding: CGFloat = 0 let headerView = FullWidthHeaderView() headerView.translatesAutoresizingMaskIntoConstraints = false - let imageView = UIImageView() - if let image = image { - imageView.image = image - imageView.translatesAutoresizingMaskIntoConstraints = false - imageView.contentMode = .scaleAspectFit + var imageView : UIView? = imageViewReplacement + if let image = image, imageView == nil { + let localImageView = UIImageView() + + localImageView.image = image + localImageView.translatesAutoresizingMaskIntoConstraints = false + localImageView.contentMode = .scaleAspectFit + + imageView = localImageView + } + + if let imageView = imageView { headerView.addSubview(imageView) NSLayoutConstraint.activate([ imageView.centerXAnchor.constraint(equalTo: headerView.safeAreaLayoutGuide.centerXAnchor), - imageView.widthAnchor.constraint(equalToConstant: imageWidth), - imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor), imageView.topAnchor.constraint(equalTo: headerView.safeAreaLayoutGuide.topAnchor, constant: topSpacing) - ]) + ]) } let titleLabel = UILabel() titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.setContentHuggingPriority(.defaultLow, for: .horizontal) - titleLabel.text = title + if let title = title { + titleLabel.text = title + titleLabel.font = UIFont.preferredFont(forTextStyle: textStyle) + } else if let attributedTitle = attributedTitle { + titleLabel.attributedText = attributedTitle + } + titleLabel.textAlignment = .center titleLabel.numberOfLines = 0 headerView.addSubview(titleLabel) @@ -103,15 +114,12 @@ extension StaticTableViewSection { titleLabel.applyThemeCollection(collection, itemStyle: .welcomeTitle) } else { titleLabel.applyThemeCollection(collection, itemStyle: .logo) - imageView.image = imageView.image?.tinted(with: collection.navigationBarColors.labelColor) + (imageView as? UIImageView)?.image = (imageView as? UIImageView)?.image?.tinted(with: collection.navigationBarColors.labelColor) } }) - titleLabel.textColor = .red - - titleLabel.font = UIFont.systemFont(ofSize: UIFont.systemFontSize * 1.5, weight: .bold) var layoutAnchor = headerView.safeAreaLayoutGuide.topAnchor - if image != nil { + if let imageView = imageView { layoutAnchor = imageView.bottomAnchor } @@ -120,7 +128,7 @@ extension StaticTableViewSection { titleLabel.rightAnchor.constraint(lessThanOrEqualTo: headerView.safeAreaLayoutGuide.rightAnchor, constant: -horizontalPadding), titleLabel.centerXAnchor.constraint(equalTo: headerView.safeAreaLayoutGuide.centerXAnchor), - titleLabel.topAnchor.constraint(equalTo: layoutAnchor, constant: topSpacing) + titleLabel.topAnchor.constraint(equalTo: layoutAnchor, constant: intermediateSpacing ?? topSpacing) ]) if message != nil { diff --git a/ownCloud/Static Login/StaticLoginBundle.swift b/ownCloud/Static Login/StaticLoginBundle.swift index a46554526..50ee88230 100644 --- a/ownCloud/Static Login/StaticLoginBundle.swift +++ b/ownCloud/Static Login/StaticLoginBundle.swift @@ -46,11 +46,11 @@ class StaticLoginBundle: NSObject { } if let profileDefinitions = branding.profileDefinitions { - let profiles = profileDefinitions.map { (profile) -> StaticLoginProfile? in + let profiles = profileDefinitions.map { (profile) -> StaticLoginProfile in return StaticLoginProfile(from: profile) - } as? [StaticLoginProfile] + } - bundle.profiles = profiles! + bundle.profiles = profiles } } diff --git a/ownCloudAppFramework/View Providers/OCAvatar+ViewProvider.h b/ownCloudAppFramework/View Providers/OCAvatar+ViewProvider.h new file mode 100644 index 000000000..0c4458b6b --- /dev/null +++ b/ownCloudAppFramework/View Providers/OCAvatar+ViewProvider.h @@ -0,0 +1,27 @@ +// +// OCAvatar+ViewProvider.h +// ownCloudApp +// +// Created by Felix Schwarz on 18.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface OCAvatar (ViewProvider) + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudAppFramework/View Providers/OCAvatar+ViewProvider.m b/ownCloudAppFramework/View Providers/OCAvatar+ViewProvider.m new file mode 100644 index 000000000..83832b733 --- /dev/null +++ b/ownCloudAppFramework/View Providers/OCAvatar+ViewProvider.m @@ -0,0 +1,43 @@ +// +// OCAvatar+ViewProvider.m +// ownCloudApp +// +// Created by Felix Schwarz on 18.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import "OCAvatar+ViewProvider.h" +#import "OCCircularImageView.h" + +@implementation OCAvatar (ViewProvider) + +- (void)provideViewForSize:(CGSize)size inContext:(nullable OCViewProviderContext *)context completion:(void(^)(OCView * _Nullable view))completionHandler +{ + [self requestImageForSize:size scale:0 withCompletionHandler:^(OCImage * _Nullable ocImage, NSError * _Nullable error, CGSize maximumSizeInPoints, UIImage * _Nullable image) { + if (image != nil) + { + dispatch_async(dispatch_get_main_queue(), ^{ + OCCircularImageView *avatarView = nil; + + avatarView = [[OCCircularImageView alloc] initWithImage:image]; + avatarView.translatesAutoresizingMaskIntoConstraints = NO; + + completionHandler(avatarView); + }); + } + + completionHandler(nil); + }]; +} + +@end diff --git a/ownCloudAppFramework/View Providers/OCResourceTextPlaceholder+ViewProvider.h b/ownCloudAppFramework/View Providers/OCResourceTextPlaceholder+ViewProvider.h new file mode 100644 index 000000000..0a3494d09 --- /dev/null +++ b/ownCloudAppFramework/View Providers/OCResourceTextPlaceholder+ViewProvider.h @@ -0,0 +1,27 @@ +// +// OCResourceTextPlaceholder+ViewProvider.h +// ownCloudApp +// +// Created by Felix Schwarz on 19.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface OCResourceTextPlaceholder (ViewProvider) + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudAppFramework/View Providers/OCResourceTextPlaceholder+ViewProvider.m b/ownCloudAppFramework/View Providers/OCResourceTextPlaceholder+ViewProvider.m new file mode 100644 index 000000000..4214d77d6 --- /dev/null +++ b/ownCloudAppFramework/View Providers/OCResourceTextPlaceholder+ViewProvider.m @@ -0,0 +1,43 @@ +// +// OCResourceTextPlaceholder+ViewProvider.m +// ownCloudApp +// +// Created by Felix Schwarz on 19.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import "OCResourceTextPlaceholder+ViewProvider.h" +#import "OCCircularTextView.h" + +@implementation OCResourceTextPlaceholder (ViewProvider) + +- (void)provideViewForSize:(CGSize)size inContext:(nullable OCViewProviderContext *)context completion:(void(^)(OCView * _Nullable view))completionHandler +{ + if (self.text.length > 0) + { + dispatch_async(dispatch_get_main_queue(), ^{ + OCCircularTextView *circularTextView = [OCCircularTextView new]; + + circularTextView.translatesAutoresizingMaskIntoConstraints = NO; + circularTextView.text = self.text; + + completionHandler(circularTextView); + }); + } + else + { + completionHandler(nil); + } +} + +@end diff --git a/ownCloudAppFramework/View Providers/OCViewHost.h b/ownCloudAppFramework/View Providers/OCViewHost.h new file mode 100644 index 000000000..6650c046c --- /dev/null +++ b/ownCloudAppFramework/View Providers/OCViewHost.h @@ -0,0 +1,42 @@ +// +// OCViewHost.h +// ownCloudApp +// +// Created by Felix Schwarz on 18.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface OCViewHost : UIView + +@property(strong,nullable,nonatomic) OCViewProviderContext *viewProviderContext; + +@property(strong,nullable,nonatomic) OCResourceRequest *request; +@property(strong,nullable,nonatomic) id activeViewProvider; + +@property(strong,nullable,nonatomic) UIView *fallbackView; +@property(assign) CGSize fallbackSize; + +- (instancetype)initWithFallbackSize:(CGSize)fallbackSize; +- (instancetype)initWithFallbackView:(UIView *)fallbackView viewProviderContext:(nullable OCViewProviderContext *)viewProviderContext; + +- (instancetype)initWithRequest:(OCResourceRequest *)request fallbackView:(nullable UIView *)fallbackView viewProviderContext:(nullable OCViewProviderContext *)viewProviderContext; +- (instancetype)initWithViewProvider:(id)viewProvider fallbackSize:(CGSize)fallbackSize fallbackView:(nullable UIView *)fallbackView viewProviderContext:(nullable OCViewProviderContext *)viewProviderContext; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudAppFramework/View Providers/OCViewHost.m b/ownCloudAppFramework/View Providers/OCViewHost.m new file mode 100644 index 000000000..47373dd7a --- /dev/null +++ b/ownCloudAppFramework/View Providers/OCViewHost.m @@ -0,0 +1,196 @@ +// +// OCViewHost.m +// ownCloudApp +// +// Created by Felix Schwarz on 18.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import "OCViewHost.h" + +@interface OCViewHost () +{ + UIView *_hostedView; +} +@end + +@implementation OCViewHost + +- (instancetype)initWithFallbackSize:(CGSize)fallbackSize +{ + if ((self = [super init]) != nil) + { + _fallbackSize = fallbackSize; + } + + return (self); +} + +- (instancetype)initWithFallbackView:(UIView *)fallbackView viewProviderContext:(nullable OCViewProviderContext *)viewProviderContext +{ + if ((self = [super init]) != nil) + { + _viewProviderContext = viewProviderContext; + self.fallbackView = fallbackView; + } + + return (self); +} + +- (instancetype)initWithRequest:(OCResourceRequest *)request fallbackView:(UIView *)fallbackView viewProviderContext:(OCViewProviderContext *)viewProviderContext; +{ + if ((self = [super init]) != nil) + { + _fallbackSize = request.maxPointSize; + _viewProviderContext = viewProviderContext; + _fallbackView = fallbackView; + self.request = request; + } + + return (self); +} + +- (instancetype)initWithViewProvider:(id)viewProvider fallbackSize:(CGSize)fallbackSize fallbackView:(UIView *)fallbackView viewProviderContext:(OCViewProviderContext *)viewProviderContext +{ + if ((self = [super init]) != nil) + { + _viewProviderContext = viewProviderContext; + _fallbackView = fallbackView; + _fallbackSize = fallbackSize; + self.activeViewProvider = viewProvider; + } + + return (self); +} + +#pragma mark - Resource requests +- (void)setRequest:(OCResourceRequest *)request +{ + _request.delegate = nil; + + _request = request; + _request.delegate = self; + + [self setActiveViewProviderFromResource:_request.resource]; +} + +- (void)resourceRequest:(nonnull OCResourceRequest *)request didChangeWithError:(nullable NSError *)error isOngoing:(BOOL)isOngoing previousResource:(nullable OCResource *)previousResource newResource:(nullable OCResource *)newResource +{ + if ((error == nil) && (newResource != nil)) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [self setActiveViewProviderFromResource:newResource]; + }); + } + + if (!isOngoing) + { + _request.delegate = nil; + _request = nil; + } +} + +#pragma mark - View Provider Context +- (void)setViewProviderContext:(OCViewProviderContext *)viewProviderContext +{ + _viewProviderContext = viewProviderContext; + [self updateView]; +} + +#pragma mark - Active view provider +- (void)setActiveViewProviderFromResource:(OCResource *)resource +{ + id newViewProvider; + + if ((newViewProvider = OCConformanceCast(resource, OCViewProvider)) != nil) + { + if (_activeViewProvider != newViewProvider) + { + self.activeViewProvider = newViewProvider; + } + } +} + +- (void)setActiveViewProvider:(id)activeViewProvider +{ + _activeViewProvider = activeViewProvider; + [self updateView]; +} + +#pragma mark - Fallback view +- (void)setFallbackView:(UIView *)fallbackView +{ + _fallbackView = fallbackView; + [self updateView]; +} + +#pragma mark - Update views +- (void)setHostedView:(UIView *)newView +{ + if (newView != _hostedView) + { + [_hostedView removeFromSuperview]; + _hostedView = newView; + + newView.translatesAutoresizingMaskIntoConstraints = NO; + + if (newView != nil) + { + [self addSubview:newView]; + + [self addConstraints:@[ + [newView.leftAnchor constraintEqualToAnchor:self.leftAnchor], + [newView.rightAnchor constraintEqualToAnchor:self.rightAnchor], + [newView.topAnchor constraintEqualToAnchor:self.topAnchor], + [newView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor] + ]]; + } + } +} + +- (void)updateView +{ + id originalViewProvider = _activeViewProvider; + + if (_activeViewProvider != nil) + { + CGSize size = self.frame.size; + + if ((size.width == 0) || (size.height == 0)) + { + size = _fallbackSize; + } + + [_activeViewProvider provideViewForSize:size inContext:self.viewProviderContext completion:^(UIView * _Nullable newView) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (originalViewProvider == self.activeViewProvider) { + [self setHostedView:(newView != nil) ? newView : self.fallbackView]; + } else { + OCLogWarning(@"_activeViewProvider changed during receipt") + } + }); + }]; + } + else + { + dispatch_async(dispatch_get_main_queue(), ^{ + if (originalViewProvider == self.activeViewProvider) { + [self setHostedView:self.fallbackView]; + } else { + OCLogWarning(@"_activeViewProvider changed during receipt") + } + }); + } +} + +@end diff --git a/ownCloudAppFramework/Views/OCCircularContentView.h b/ownCloudAppFramework/Views/OCCircularContentView.h new file mode 100644 index 000000000..e0995a73b --- /dev/null +++ b/ownCloudAppFramework/Views/OCCircularContentView.h @@ -0,0 +1,30 @@ +// +// OCCircularContentView.h +// ownCloud +// +// Created by Felix Schwarz on 19.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface OCCircularContentView : UIView + +- (CGSize)circularContentSizeForSize:(CGSize)size; +- (void)drawContentInRect:(CGRect)rect circularSize:(CGSize)size; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudAppFramework/Views/OCCircularContentView.m b/ownCloudAppFramework/Views/OCCircularContentView.m new file mode 100644 index 000000000..99b869e52 --- /dev/null +++ b/ownCloudAppFramework/Views/OCCircularContentView.m @@ -0,0 +1,72 @@ +// +// OCCircularContentView.m +// ownCloud +// +// Created by Felix Schwarz on 19.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import "OCCircularContentView.h" + +@implementation OCCircularContentView + +- (instancetype)init +{ + if ((self = [super init]) != nil) + { + self.opaque = NO; + } + + return (self); +} + +- (void)drawRect:(CGRect)rect +{ + CGSize viewSize = self.bounds.size; + CGFloat sideLength = (viewSize.width > viewSize.height) ? viewSize.height : viewSize.width; + CGSize circularSize = CGSizeMake(sideLength, sideLength); + CGSize contentSize = [self circularContentSizeForSize:circularSize], renderSize; + + CGContextRef contextRef = UIGraphicsGetCurrentContext(); + UIBezierPath *clipPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake((viewSize.width - sideLength) / 2.0, (viewSize.height - sideLength) / 2.0, sideLength, sideLength)]; + + if (contentSize.width > contentSize.height) + { + renderSize.height = sideLength; + renderSize.width = (contentSize.width * sideLength) / contentSize.height; + } + else + { + renderSize.width = sideLength; + renderSize.height = (contentSize.height * sideLength) / contentSize.width; + } + + CGContextSaveGState(contextRef); + + [clipPath addClip]; + [self drawContentInRect:CGRectMake((viewSize.width - renderSize.width) / 2.0, (viewSize.height - renderSize.height) / 2.0, renderSize.width, renderSize.height) circularSize:circularSize]; + + CGContextRestoreGState(contextRef); +} + +- (CGSize)circularContentSizeForSize:(CGSize)size +{ + return (CGSizeMake(0, 0)); +} + +- (void)drawContentInRect:(CGRect)rect circularSize:(CGSize)size +{ + +} + +@end diff --git a/ownCloudAppFramework/Views/OCCircularImageView.h b/ownCloudAppFramework/Views/OCCircularImageView.h new file mode 100644 index 000000000..87877a3ec --- /dev/null +++ b/ownCloudAppFramework/Views/OCCircularImageView.h @@ -0,0 +1,32 @@ +// +// OCCircularImageView.h +// ownCloud +// +// Created by Felix Schwarz on 19.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import +#import "OCCircularContentView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCCircularImageView : OCCircularContentView + +@property(strong, nullable, nonatomic) UIImage *image; + +- (instancetype)initWithImage:(nullable UIImage *)image; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudAppFramework/Views/OCCircularImageView.m b/ownCloudAppFramework/Views/OCCircularImageView.m new file mode 100644 index 000000000..5fc03e7b1 --- /dev/null +++ b/ownCloudAppFramework/Views/OCCircularImageView.m @@ -0,0 +1,59 @@ +// +// OCCircularImageView.m +// ownCloud +// +// Created by Felix Schwarz on 19.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import "OCCircularImageView.h" + +@implementation OCCircularImageView + +- (instancetype)init +{ + if ((self = [super init]) != nil) + { + self.opaque = NO; + } + + return (self); +} + +- (instancetype)initWithImage:(nullable UIImage *)image +{ + if ((self = [self init]) != nil) + { + _image = image; + } + + return (self); +} + +- (void)setImage:(UIImage *)image +{ + _image = image; + [self setNeedsDisplay]; +} + +- (CGSize)circularContentSizeForSize:(CGSize)size +{ + return (_image.size); +} + +- (void)drawContentInRect:(CGRect)rect circularSize:(CGSize)size +{ + [_image drawInRect:rect]; +} + +@end diff --git a/ownCloudAppFramework/Views/OCCircularTextView.h b/ownCloudAppFramework/Views/OCCircularTextView.h new file mode 100644 index 000000000..4bed3dd00 --- /dev/null +++ b/ownCloudAppFramework/Views/OCCircularTextView.h @@ -0,0 +1,29 @@ +// +// OCCircularTextView.h +// ownCloudApp +// +// Created by Felix Schwarz on 19.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import "OCCircularContentView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCCircularTextView : OCCircularContentView + +@property(strong,nonatomic,nullable) NSString *text; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudAppFramework/Views/OCCircularTextView.m b/ownCloudAppFramework/Views/OCCircularTextView.m new file mode 100644 index 000000000..4b0fb3117 --- /dev/null +++ b/ownCloudAppFramework/Views/OCCircularTextView.m @@ -0,0 +1,64 @@ +// +// OCCircularTextView.m +// ownCloudApp +// +// Created by Felix Schwarz on 19.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import "OCCircularTextView.h" + +@interface OCCircularTextView () +{ + CGSize _textSize; +} +@end + +@implementation OCCircularTextView + +- (void)setText:(NSString *)text +{ + _text = text; + [self setNeedsDisplay]; +} + +- (UIFont *)fontForSize:(CGSize)size +{ + return ([UIFont systemFontOfSize:(size.height / 2.0) weight:UIFontWeightSemibold]); +} + +- (CGSize)circularContentSizeForSize:(CGSize)size +{ + CGRect boundingRect = [self.text boundingRectWithSize:size options:0 attributes:@{ + NSFontAttributeName : [self fontForSize:size], + } context:nil]; + + _textSize = CGRectStandardize(boundingRect).size; + + return (_textSize); +} + +- (void)drawContentInRect:(CGRect)rect circularSize:(CGSize)size +{ + CGRect viewBounds = self.bounds; + + [[UIColor grayColor] setFill]; + UIRectFill(rect); + + [self.text drawAtPoint:CGPointMake((viewBounds.size.width - _textSize.width) / 2.0, (viewBounds.size.height - _textSize.height) / 2.0) withAttributes:@{ + NSFontAttributeName : [self fontForSize:size], + NSForegroundColorAttributeName : UIColor.whiteColor + }]; +} + +@end diff --git a/ownCloudAppFramework/ownCloudApp.h b/ownCloudAppFramework/ownCloudApp.h index 6ca94e137..db8d6e89e 100644 --- a/ownCloudAppFramework/ownCloudApp.h +++ b/ownCloudAppFramework/ownCloudApp.h @@ -74,3 +74,9 @@ FOUNDATION_EXPORT const unsigned char ownCloudAppVersionString[]; #import #import + +#import +#import +#import +#import +#import diff --git a/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift b/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift index bd7d93149..835744177 100644 --- a/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift +++ b/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift @@ -18,6 +18,7 @@ import UIKit import ownCloudSDK +import ownCloudApp open class GroupSharingTableViewController: SharingTableViewController, UISearchResultsUpdating, UISearchBarDelegate, OCRecipientSearchControllerDelegate { @@ -261,13 +262,13 @@ open class GroupSharingTableViewController: SharingTableViewController, UISearch } self.navigationController?.pushViewController(editSharingViewController, animated: true) - }, title: displayName, subtitle: share.permissionDescription(for: core?.connection.capabilities), image: recipient.user?.avatar, accessoryType: .disclosureIndicator) + }, title: displayName, subtitle: share.permissionDescription(for: core?.connection.capabilities), image: nil, accessoryType: .disclosureIndicator) shareRow.representedObject = share shareRows.append(shareRow) } else { - let shareRow = StaticTableViewRow(rowWithAction: nil, title: displayName, subtitle: share.permissionDescription(for: core?.connection.capabilities), image: recipient.user?.avatar, accessoryType: .none) + let shareRow = StaticTableViewRow(rowWithAction: nil, title: displayName, subtitle: share.permissionDescription(for: core?.connection.capabilities), image: nil, accessoryType: .none) shareRow.representedObject = share @@ -403,10 +404,23 @@ open class GroupSharingTableViewController: SharingTableViewController, UISearch guard let itemPath = self.item.path else { continue } var title = "" var image: UIImage? + var leadingAccessoryView : UIView? if recipient.type == .user { guard let displayName = recipient.displayName else { continue } title = displayName - image = UIImage(named: "person") + + if let recipientUser = recipient.user { + let avatarRequest = OCResourceRequestAvatar(for: recipientUser, maximumSize: OCAvatar.defaultSize, scale: 0, waitForConnectivity: false) { request, error, ongoing, oldResource, newResource in + Log.debug("Avatar for \(String(describing: recipient.user?.userName)): ongoing=\(ongoing), resource=\(newResource.debugDescription)") + } + leadingAccessoryView = OCViewHost(request: avatarRequest, fallbackView: nil, viewProviderContext: nil) + leadingAccessoryView?.translatesAutoresizingMaskIntoConstraints = false + leadingAccessoryView?.widthAnchor.constraint(equalToConstant: 40).isActive = true + leadingAccessoryView?.heightAnchor.constraint(equalToConstant: 30).isActive = true + core.vault.resourceManager?.start(avatarRequest) + } else { + image = UIImage(named: "person") + } } else { guard let displayName = recipient.displayName else { continue } let groupTitle = "(Group)".localized @@ -429,7 +443,7 @@ open class GroupSharingTableViewController: SharingTableViewController, UISearch let navigationController = ThemeNavigationController(rootViewController: editSharingViewController) self.navigationController?.present(navigationController, animated: true, completion: nil) } - }, title: title, image: image) + }, title: title, image: image, leadingAccessoryView: leadingAccessoryView) ) } } diff --git a/ownCloudAppShared/UIKit Extension/UIImageView+Thumbnails.swift b/ownCloudAppShared/UIKit Extension/UIImageView+Thumbnails.swift index 43c4a4e42..fc7d2a0ff 100644 --- a/ownCloudAppShared/UIKit Extension/UIImageView+Thumbnails.swift +++ b/ownCloudAppShared/UIKit Extension/UIImageView+Thumbnails.swift @@ -6,74 +6,27 @@ // Copyright © 2019 ownCloud GmbH. All rights reserved. // +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + import UIKit import ownCloudSDK -#if canImport(QuickLookThumbnailing) -import QuickLookThumbnailing -#endif - public protocol ItemContainer { var item : OCItem? { get } } public extension UIImageView { - - private func cacheThumbnail(image: UIImage, size:CGSize, for item:OCItem, in core:OCCore) { - - guard let itemVersionIdentifier = item.itemVersionIdentifier else { return } - - let specID = item.mimeType != nil ? item.mimeType! : "_none_" - let event = OCEvent(type: .retrieveThumbnail, - userInfo: [OCEventUserInfoKey(rawValue: "specID") : NSString(string: specID), .itemVersionIdentifier : itemVersionIdentifier], - ephermalUserInfo: nil, - result: nil) - - let thumbnail = OCItemThumbnail() - thumbnail.itemVersionIdentifier = item.itemVersionIdentifier - thumbnail.maximumSizeInPixels = size - thumbnail.mimeType = "image/jpeg" - thumbnail.data = image.jpegData(compressionQuality: 1.0) - thumbnail.specID = specID - - event.result = thumbnail - item.thumbnail = thumbnail - - core.handle(event, sender: self) - } - - @discardableResult func setThumbnailImage(using core: OCCore, from requestItem: OCItem, with size: CGSize, avoidSystemThumbnails: Bool = false, itemContainer: ItemContainer? = nil, progressHandler: ((_ progress:Progress) -> Void)? = nil) -> Progress? { - - weak var weakCore = core - - func requestSystemThumbnailIfPossible() { - #if canImport(QuickLookThumbnailing) - if #available(iOS 13, *) { - - var types : QLThumbnailGenerator.Request.RepresentationTypes? - types = [.lowQualityThumbnail, .thumbnail] - - if let itemURL = weakCore?.localURL(for: requestItem) { - let thumbnailRequest = QLThumbnailGenerator.Request(fileAt: itemURL, size: size, scale: UIScreen.main.scale, representationTypes:types!) - - QLThumbnailGenerator.shared.generateBestRepresentation(for: thumbnailRequest) { [weak self] (representation, error) in - if let image = representation?.uiImage, let self = self, let core = weakCore, error == nil { - self.cacheThumbnail(image: image, size:size, for: requestItem, in: core) - - if (itemContainer == nil) || ((itemContainer != nil) && itemContainer?.item?.itemVersionIdentifier == requestItem.itemVersionIdentifier) { - OnMainThread { - self.image = image - } - } - } - } - } - } - #endif - } - + @discardableResult func setThumbnailImage(using core: OCCore, from requestItem: OCItem, with size: CGSize, itemContainer: ItemContainer? = nil, progressHandler: ((_ progress:Progress) -> Void)? = nil) -> Progress? { let displayThumbnail = { (thumbnail: OCItemThumbnail?) in - _ = thumbnail?.requestImage(for: size, scale: 0, withCompletionHandler: { (thumbnail, error, _, image) in + _ = thumbnail?.request(for: size, scale: 0, withCompletionHandler: { (_, error, _, image) in if error == nil, image != nil, (itemContainer == nil) || ((itemContainer != nil) && itemContainer?.item?.itemVersionIdentifier == thumbnail?.itemVersionIdentifier) { OnMainThread { self.image = image @@ -90,16 +43,11 @@ public extension UIImageView { } if requestItem.thumbnailAvailability != .none { - - let activeThumbnailRequestProgress = weakCore?.retrieveThumbnail(for: requestItem, maximumSize: size, scale: 0, retrieveHandler: { (_, _, _, thumbnail, _, progress) in - + let activeThumbnailRequestProgress = core.retrieveThumbnail(for: requestItem, maximumSize: size, scale: 0, retrieveHandler: { (_, _, _, thumbnail, _, progress) in // Did we get valid thumbnail? if thumbnail != nil { requestItem.thumbnail = thumbnail displayThumbnail(thumbnail) - } else if avoidSystemThumbnails == false { - // No thumbnail returned by the core, try QuickLook thumbnailing on iOS 13 - requestSystemThumbnailIfPossible() } if progress != nil { diff --git a/ownCloudAppShared/User Interface/More/MoreViewHeader.swift b/ownCloudAppShared/User Interface/More/MoreViewHeader.swift index 27994c82c..329ebf0a9 100644 --- a/ownCloudAppShared/User Interface/More/MoreViewHeader.swift +++ b/ownCloudAppShared/User Interface/More/MoreViewHeader.swift @@ -203,10 +203,8 @@ open class MoreViewHeader: UIView { if item.thumbnailAvailability != .none { let displayThumbnail = { (thumbnail: OCItemThumbnail?) in - _ = thumbnail?.requestImage(for: CGSize(width: self.thumbnailSize.width, height: self.thumbnailSize.height), scale: 0, withCompletionHandler: { (thumbnail, error, _, image) in - if error == nil, - image != nil, - self.item.itemVersionIdentifier == thumbnail?.itemVersionIdentifier { + _ = thumbnail?.request(for: CGSize(width: self.thumbnailSize.width, height: self.thumbnailSize.height), scale: 0, withCompletionHandler: { (_, error, _, image) in + if error == nil, image != nil, self.item.itemVersionIdentifier == thumbnail?.itemVersionIdentifier { OnMainThread { self.showsIcon = false self.iconView.image = image diff --git a/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift b/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift index cab42eb1e..72766f3ef 100644 --- a/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift +++ b/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift @@ -17,6 +17,7 @@ */ import UIKit +import ownCloudApp public typealias StaticTableViewRowAction = (_ staticRow : StaticTableViewRow, _ sender: Any?) -> Void public typealias StaticTableViewRowTextAction = (_ staticRow : StaticTableViewRow, _ sender: Any?, _ type: StaticTableViewRowActionType) -> Void @@ -123,15 +124,19 @@ open class StaticTableViewRow : NSObject, UITextFieldDelegate { public var additionalAccessoryView : UIView? + public var leadingAccessoryView : UIView? + override public init() { type = .row super.init() } - convenience public init(rowWithAction: StaticTableViewRowAction?, title: String, subtitle: String? = nil, image: UIImage? = nil, imageWidth: CGFloat? = nil, imageTintColorKey : String = "labelColor", alignment: NSTextAlignment = .left, messageStyle: StaticTableViewRowMessageStyle? = nil, recreatedLabelLayout : ThemeTableViewCell.CellLayouter? = nil, accessoryType: UITableViewCell.AccessoryType = .none, identifier : String? = nil, accessoryView: UIView? = nil) { + convenience public init(rowWithAction: StaticTableViewRowAction?, title: String, subtitle: String? = nil, image: UIImage? = nil, imageWidth: CGFloat? = nil, imageTintColorKey : String = "labelColor", alignment: NSTextAlignment = .left, messageStyle: StaticTableViewRowMessageStyle? = nil, recreatedLabelLayout cellLayouter: ThemeTableViewCell.CellLayouter? = nil, leadingAccessoryView: UIView? = nil, accessoryType: UITableViewCell.AccessoryType = .none, identifier : String? = nil, accessoryView: UIView? = nil) { self.init() type = .row + var recreatedLabelLayout : ThemeTableViewCell.CellLayouter? = cellLayouter + var image = image if image != nil, imageWidth != nil { image = image?.paddedTo(width: imageWidth) @@ -143,6 +148,11 @@ open class StaticTableViewRow : NSObject, UITextFieldDelegate { cellStyle = UITableViewCell.CellStyle.subtitle } + if let leadingAccessoryView = leadingAccessoryView { + self.leadingAccessoryView = leadingAccessoryView + recreatedLabelLayout = ThemeTableViewCell.customLeadingViewLayout(leadingView: leadingAccessoryView) + } + let themeCell = ThemeTableViewCell(withLabelColorUpdates: true, style: cellStyle, recreatedLabelLayout: recreatedLabelLayout, reuseIdentifier: nil) themeCell.messageStyle = messageStyle diff --git a/ownCloudAppShared/User Interface/Theme/UI/ThemeTableViewCell.swift b/ownCloudAppShared/User Interface/Theme/UI/ThemeTableViewCell.swift index 12e8a8831..fcb143894 100644 --- a/ownCloudAppShared/User Interface/Theme/UI/ThemeTableViewCell.swift +++ b/ownCloudAppShared/User Interface/Theme/UI/ThemeTableViewCell.swift @@ -55,54 +55,100 @@ open class ThemeTableViewCell: UITableViewCell, Themeable { } } - public typealias CellLayouter = (_ cell: ThemeTableViewCell, _ textLabel: UILabel, _ detailLabel : UILabel) -> Void + public typealias CellLayouter = (_ cell: ThemeTableViewCell, _ textLabel: UILabel, _ detailLabel : UILabel?) -> Void static public var systemLikeLayout : CellLayouter = { (cell, textLabel, detailLabel) in - NSLayoutConstraint.activate([ - textLabel.topAnchor.constraint(equalToSystemSpacingBelow: cell.contentView.topAnchor, multiplier: 1), + customLeadingViewLayout(leadingView: cell.contentView)(cell, textLabel, detailLabel) + } + + static func customLeadingViewLayout(leadingView: UIView) -> CellLayouter { + let layouter : CellLayouter = { (cell, textLabel, detailLabel) in + let includeLeadingView : Bool = (leadingView != cell.contentView) + var leadingAnchor : NSLayoutXAxisAnchor = cell.contentView.leadingAnchor + + if includeLeadingView { + if leadingView.superview == nil { + // Add leadingView + cell.contentView.addSubview(leadingView) + } + + leadingAnchor = leadingView.trailingAnchor + } + + var constraints = [ + textLabel.leadingAnchor.constraint(equalToSystemSpacingAfter: leadingAnchor, multiplier: 1), + textLabel.trailingAnchor.constraint(equalToSystemSpacingAfter: cell.contentView.trailingAnchor, multiplier: -1) + ] + + if let detailLabel = detailLabel { + constraints += [ + textLabel.topAnchor.constraint(equalToSystemSpacingBelow: cell.contentView.topAnchor, multiplier: 1), + + detailLabel.leadingAnchor.constraint(equalToSystemSpacingAfter: leadingAnchor, multiplier: 1), + detailLabel.trailingAnchor.constraint(equalToSystemSpacingAfter: cell.contentView.trailingAnchor, multiplier: -1), - textLabel.leadingAnchor.constraint(equalToSystemSpacingAfter: cell.contentView.leadingAnchor, multiplier: 1), - textLabel.trailingAnchor.constraint(equalToSystemSpacingAfter: cell.contentView.trailingAnchor, multiplier: -1), + detailLabel.topAnchor.constraint(equalToSystemSpacingBelow: textLabel.bottomAnchor, multiplier: 1), + detailLabel.bottomAnchor.constraint(equalToSystemSpacingBelow: cell.contentView.bottomAnchor, multiplier: -1) + ] + } else { + constraints += [ + textLabel.topAnchor.constraint(greaterThanOrEqualTo: cell.contentView.topAnchor, constant: 11), + textLabel.bottomAnchor.constraint(lessThanOrEqualTo: cell.contentView.bottomAnchor, constant: -11), + textLabel.centerYAnchor.constraint(equalTo: cell.contentView.centerYAnchor) + ] + } - detailLabel.leadingAnchor.constraint(equalToSystemSpacingAfter: cell.contentView.leadingAnchor, multiplier: 1), - detailLabel.trailingAnchor.constraint(equalToSystemSpacingAfter: cell.contentView.trailingAnchor, multiplier: -1), + if includeLeadingView { + constraints += [ + leadingView.leadingAnchor.constraint(equalToSystemSpacingAfter: cell.contentView.leadingAnchor, multiplier: 1), + leadingView.topAnchor.constraint(greaterThanOrEqualToSystemSpacingBelow:cell.contentView.topAnchor, multiplier: 1), + leadingView.bottomAnchor.constraint(lessThanOrEqualToSystemSpacingBelow:cell.contentView.bottomAnchor, multiplier: -1), + leadingView.centerYAnchor.constraint(equalTo: cell.contentView.centerYAnchor) + ] + } + + NSLayoutConstraint.activate(constraints) + } - detailLabel.topAnchor.constraint(equalToSystemSpacingBelow: textLabel.bottomAnchor, multiplier: 1), - detailLabel.bottomAnchor.constraint(equalToSystemSpacingBelow: cell.contentView.bottomAnchor, multiplier: -1) - ]) + return layouter } convenience public init(withLabelColorUpdates labelColorUpdates: Bool, style: UITableViewCell.CellStyle = .default, recreatedLabelLayout : CellLayouter? = nil, reuseIdentifier: String? = nil) { self.init(style: style, reuseIdentifier: reuseIdentifier) if let recreatedLabelLayout = recreatedLabelLayout { - if style == .subtitle { - let replacementTextLabel = UILabel() - let replacementDetailLabel = UILabel() + let replacementTextLabel = UILabel() + var replacementDetailLabel : UILabel? + + replacementTextLabel.translatesAutoresizingMaskIntoConstraints = false + replacementTextLabel.setContentHuggingPriority(.required, for: .vertical) + replacementTextLabel.setContentCompressionResistancePriority(.required, for: .vertical) + replacementTextLabel.numberOfLines = 0 + replacementTextLabel.lineBreakMode = .byWordWrapping - replacementTextLabel.translatesAutoresizingMaskIntoConstraints = false - replacementTextLabel.setContentHuggingPriority(.required, for: .vertical) - replacementTextLabel.setContentCompressionResistancePriority(.required, for: .vertical) - replacementTextLabel.numberOfLines = 0 - replacementTextLabel.lineBreakMode = .byWordWrapping + replacementTextLabel.font = (style == .subtitle) ? + UIFont.systemFont(ofSize: UIFont.systemFontSize) : + UIFont.systemFont(ofSize: UIFont.labelFontSize) - replacementDetailLabel.translatesAutoresizingMaskIntoConstraints = false - replacementDetailLabel.setContentHuggingPriority(.required, for: .vertical) - replacementDetailLabel.setContentCompressionResistancePriority(.required, for: .vertical) - replacementDetailLabel.numberOfLines = 0 - replacementDetailLabel.lineBreakMode = .byWordWrapping + self.customTextLabels = [ replacementTextLabel ] + self.contentView.addSubview(replacementTextLabel) - replacementTextLabel.font = UIFont.systemFont(ofSize: UIFont.systemFontSize) - replacementDetailLabel.font = UIFont.systemFont(ofSize: UIFont.smallSystemFontSize) + if style == .subtitle { + replacementDetailLabel = UILabel() - self.customTextLabels = [ replacementTextLabel ] - self.customDetailTextLabels = [ replacementDetailLabel ] + replacementDetailLabel?.translatesAutoresizingMaskIntoConstraints = false + replacementDetailLabel?.setContentHuggingPriority(.required, for: .vertical) + replacementDetailLabel?.setContentCompressionResistancePriority(.required, for: .vertical) + replacementDetailLabel?.numberOfLines = 0 + replacementDetailLabel?.lineBreakMode = .byWordWrapping - self.contentView.addSubview(replacementTextLabel) - self.contentView.addSubview(replacementDetailLabel) + replacementDetailLabel?.font = UIFont.systemFont(ofSize: UIFont.smallSystemFontSize) - recreatedLabelLayout(self, replacementTextLabel, replacementDetailLabel) + self.customDetailTextLabels = [ replacementDetailLabel! ] + self.contentView.addSubview(replacementDetailLabel!) } + + recreatedLabelLayout(self, replacementTextLabel, replacementDetailLabel) } updateLabelColors = labelColorUpdates From de1d6cfa04cb5a9b6f34d6ac36a9c6923a512146 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Fri, 21 Jan 2022 10:55:43 +0100 Subject: [PATCH 002/328] - OCHostView: harden against request changes during result delivery --- .../View Providers/OCViewHost.m | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/ownCloudAppFramework/View Providers/OCViewHost.m b/ownCloudAppFramework/View Providers/OCViewHost.m index 47373dd7a..174800665 100644 --- a/ownCloudAppFramework/View Providers/OCViewHost.m +++ b/ownCloudAppFramework/View Providers/OCViewHost.m @@ -21,6 +21,7 @@ @interface OCViewHost () { UIView *_hostedView; + NSUInteger _requestSeed; } @end @@ -81,15 +82,29 @@ - (void)setRequest:(OCResourceRequest *)request _request = request; _request.delegate = self; + _requestSeed++; + [self setActiveViewProviderFromResource:_request.resource]; } - (void)resourceRequest:(nonnull OCResourceRequest *)request didChangeWithError:(nullable NSError *)error isOngoing:(BOOL)isOngoing previousResource:(nullable OCResource *)previousResource newResource:(nullable OCResource *)newResource { + if (request != self->_request) + { + OCLogDebug(@"Delayed request update received"); + return; + } + if ((error == nil) && (newResource != nil)) { + NSUInteger requestSeedOnDelivery = _requestSeed; + dispatch_async(dispatch_get_main_queue(), ^{ - [self setActiveViewProviderFromResource:newResource]; + if ((request == self->_request) || // same request + ((self->_request == nil) && (requestSeedOnDelivery == self->_requestSeed))) // request is no longer ongoing, but no new request has been set in the meantime, either, so this resource can be used + { + [self setActiveViewProviderFromResource:newResource]; + } }); } From ab7c865d727db675de13cba8fe764c79116636cc Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Fri, 21 Jan 2022 13:13:44 +0100 Subject: [PATCH 003/328] - SDK update --- ios-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios-sdk b/ios-sdk index 7c8d3a437..1019a5f27 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit 7c8d3a43769b05c4da231a613d5fb8d6f65ba3d9 +Subproject commit 1019a5f27939f8746e6c83c46e9ff4a3366d6436 From f7107594e3822f01694cad4efae87b4c0a77e40e Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Mon, 24 Jan 2022 17:41:59 +0100 Subject: [PATCH 004/328] - FileProvider: switch thumbnails from thumbnail API to OCResourceManager - OCViewHost: improve responsiveness and direct updates (saving unnecessary main runloop cycles), add explicit reload method - ResourceViewHost: subclass of OCViewHost with support for auto-reload on Theme change - ItemPolicyCell, DisplayViewController, ServerListBookmarkCell, ShareClientItemCell, ClientItemCell, NamingViewController, MoreViewHeader: switch from UIImageView and old thumbnail / icon API mix to ResourceViewHost - move ThemeView base class from ownCloudApp to ownCloudAppShared - extend OCAvatar+ViewProvider to OCImage+ViewProvider, so it also returns views for all other OCImages - add ResourceItemIcon and ResourceSourceItemIcons to return TVG resources and provide views for them - VectorImageView: clear layer if not image could be created --- ios-sdk | 2 +- .../FileProviderExtensionThumbnailRequest.h | 5 +- .../FileProviderExtensionThumbnailRequest.m | 52 ++++++++++++++++-- .../ShareViewController.swift | 5 ++ ownCloud.xcodeproj/project.pbxproj | 44 ++++++++++----- .../Client/ClientRootViewController.swift | 2 + .../Item Policies/ItemPolicyCell.swift | 4 +- .../Client/Viewer/DisplayViewController.swift | 13 ++--- .../Server List/ServerListBookmarkCell.swift | 2 +- ...ingleAccountServerListViewController.swift | 6 +- ...+ViewProvider.h => OCImage+ViewProvider.h} | 4 +- ...+ViewProvider.m => OCImage+ViewProvider.m} | 34 +++++++++--- .../View Providers/OCViewHost.h | 2 + .../View Providers/OCViewHost.m | 46 ++++++++++++---- ownCloudAppFramework/ownCloudApp.h | 2 +- .../Resource Sources/ResourceItemIcon.swift | 52 ++++++++++++++++++ .../ResourceSourceItemIcons.swift | 55 +++++++++++++++++++ .../GroupSharingTableViewController.swift | 2 +- .../Client/Sharing/ShareClientItemCell.swift | 4 +- .../User Interface/ClientItemCell.swift | 33 ++++------- .../User Interface/NamingViewController.swift | 14 +++-- .../User Interface/More/MoreViewHeader.swift | 31 ++--------- .../Theme/TVG/VectorImageView.swift | 2 + .../Theme/UI/ResourceViewHost.swift | 52 ++++++++++++++++++ .../User Interface/Theme/UI}/ThemeView.swift | 13 ++--- 25 files changed, 358 insertions(+), 123 deletions(-) rename ownCloudAppFramework/View Providers/{OCAvatar+ViewProvider.h => OCImage+ViewProvider.h} (87%) rename ownCloudAppFramework/View Providers/{OCAvatar+ViewProvider.m => OCImage+ViewProvider.m} (57%) create mode 100644 ownCloudAppShared/Client/Resource Sources/ResourceItemIcon.swift create mode 100644 ownCloudAppShared/Client/Resource Sources/ResourceSourceItemIcons.swift create mode 100644 ownCloudAppShared/User Interface/Theme/UI/ResourceViewHost.swift rename {ownCloud/Theming => ownCloudAppShared/User Interface/Theme/UI}/ThemeView.swift (77%) diff --git a/ios-sdk b/ios-sdk index 1019a5f27..d081e5f58 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit 1019a5f27939f8746e6c83c46e9ff4a3366d6436 +Subproject commit d081e5f584dcb8a53d2178847ee76d6c90c64ac0 diff --git a/ownCloud File Provider/FileProviderExtensionThumbnailRequest.h b/ownCloud File Provider/FileProviderExtensionThumbnailRequest.h index 114f77062..cfee4296c 100644 --- a/ownCloud File Provider/FileProviderExtensionThumbnailRequest.h +++ b/ownCloud File Provider/FileProviderExtensionThumbnailRequest.h @@ -25,9 +25,6 @@ typedef void (^FileProviderExtensionThumbnailRequestPerThumbnailCompletionHandle typedef void (^FileProviderExtensionThumbnailRequestCompletionHandler)(NSError * _Nullable error); @interface FileProviderExtensionThumbnailRequest : NSObject -{ - BOOL _isDone; -} @property(strong) FileProviderExtension *extension; @@ -39,7 +36,7 @@ typedef void (^FileProviderExtensionThumbnailRequestCompletionHandler)(NSError * @property(copy) FileProviderExtensionThumbnailRequestPerThumbnailCompletionHandler perThumbnailCompletionHandler; @property(copy) FileProviderExtensionThumbnailRequestCompletionHandler completionHandler; -@property(nullable,strong) NSProgress *progress; +@property(nullable,strong,nonatomic) NSProgress *progress; - (void)requestNextThumbnail; diff --git a/ownCloud File Provider/FileProviderExtensionThumbnailRequest.m b/ownCloud File Provider/FileProviderExtensionThumbnailRequest.m index edda46827..126ab0341 100644 --- a/ownCloud File Provider/FileProviderExtensionThumbnailRequest.m +++ b/ownCloud File Provider/FileProviderExtensionThumbnailRequest.m @@ -19,6 +19,13 @@ #import "FileProviderExtensionThumbnailRequest.h" #import "NSError+MessageResolution.h" +@interface FileProviderExtensionThumbnailRequest () +{ + BOOL _isDone; + OCResourceRequestItemThumbnail *_thumbnailRequest; +} +@end + @implementation FileProviderExtensionThumbnailRequest - (void)dealloc @@ -52,11 +59,14 @@ - (void)requestNextThumbnail if ((core = self.extension.core) != nil) { -// if (core.connectionStatus == OCCoreConnectionStatusOnline) [core retrieveItemFromDatabaseForLocalID:(OCLocalID)itemIdentifier completionHandler:^(NSError *error, OCSyncAnchor syncAnchor, OCItem *itemFromDatabase) { OCLogDebug(@"Retrieving %ld: %@", self.cursorPosition-1, itemFromDatabase.name); - if ((itemFromDatabase.type == OCItemTypeCollection) || (itemFromDatabase.thumbnailAvailability == OCItemThumbnailAvailabilityNone)) + OCResourceManager *resourceManager = core.vault.resourceManager; + + if ((itemFromDatabase.type == OCItemTypeCollection) || // No previews for folders + (itemFromDatabase.thumbnailAvailability == OCItemThumbnailAvailabilityNone) || // No thumbnails available for this type + (resourceManager == nil)) // No ResourceManager available for this core { dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ self.perThumbnailCompletionHandler(itemIdentifier, nil, nil); @@ -68,23 +78,32 @@ - (void)requestNextThumbnail } else { - NSProgress *retrieveProgress = [self.extension.core retrieveThumbnailFor:itemFromDatabase maximumSize:self.sizeInPixels scale:1.0 waitForConnectivity:NO retrieveHandler:^(NSError *error, OCCore *core, OCItem *item, OCItemThumbnail *thumbnail, BOOL isOngoing, NSProgress *progress) { + OCResourceRequestItemThumbnail *thumbnailRequest = [OCResourceRequestItemThumbnail requestThumbnailFor:itemFromDatabase maximumSize:self.sizeInPixels scale:1.0 waitForConnectivity:NO changeHandler:^(OCResourceRequest * _Nonnull request, NSError * _Nullable error, BOOL isOngoing, OCResource * _Nullable previousResource, OCResource * _Nullable newResource) { OCLogDebug(@"Retrieved %ld: %@ -> %d", self.cursorPosition-1, itemFromDatabase.name, isOngoing); if (!isOngoing) { + OCResourceImage *thumbnailResource = OCTypedCast(newResource, OCResourceImage); + OCItemThumbnail *thumbnail = thumbnailResource.thumbnail; + dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ - self.perThumbnailCompletionHandler(itemIdentifier, thumbnail.data, (thumbnail==nil) ? nil : [error translatedError]); + NSError *returnError = (thumbnail==nil) ? + ((error != nil) ? error.translatedError : OCError(OCErrorInternal)) : + nil; + + self.perThumbnailCompletionHandler(itemIdentifier, thumbnail.data, returnError); - OCLogDebug(@"Replied %ld: %@ -> thumbnailData=%d, error=%@", self.cursorPosition-1, itemFromDatabase.name, (thumbnail.data != nil), error); + OCLogDebug(@"Replied %ld: %@ -> thumbnailData=%d, error=%@", self.cursorPosition-1, itemFromDatabase.name, (thumbnail.data != nil), returnError); [self requestNextThumbnail]; }); } }]; - [self.progress addChild:retrieveProgress withPendingUnitCount:1]; + [resourceManager startRequest:thumbnailRequest]; + + self->_thumbnailRequest = thumbnailRequest; } }]; } @@ -114,6 +133,27 @@ - (void)completedRequestWithError:(NSError *)error } } +- (void)setProgress:(NSProgress *)progress +{ + __weak FileProviderExtensionThumbnailRequest *weakSelf = self; + + progress.cancellationHandler = ^{ + FileProviderExtensionThumbnailRequest *strongSelf; + + if ((strongSelf = weakSelf) != nil) + { + OCResourceRequestItemThumbnail *thumbnailRequest; + + if ((thumbnailRequest = strongSelf->_thumbnailRequest) != nil) + { + [strongSelf.extension.core.vault.resourceManager stopRequest:thumbnailRequest]; + } + + strongSelf->_thumbnailRequest = nil; + } + }; +} + - (nonnull NSArray *)logTags { return (@[@"FPThumbs"]); diff --git a/ownCloud Share Extension/ShareViewController.swift b/ownCloud Share Extension/ShareViewController.swift index ed95c98c4..155450f8f 100644 --- a/ownCloud Share Extension/ShareViewController.swift +++ b/ownCloud Share Extension/ShareViewController.swift @@ -109,6 +109,11 @@ class ShareViewController: MoreStaticTableViewController { self.requestedCoreBookmarks.remove(at: index) } } + + if let core = core { + core.vault.resourceManager?.add(ResourceSourceItemIcons(core: core)) + } + completionHandler(core, error) }) } diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 2dfad2358..d4f371943 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -100,7 +100,6 @@ 399EA75A25E66DB000B6FF11 /* ClientItemResolvingCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399EA75925E66DB000B6FF11 /* ClientItemResolvingCell.swift */; }; 39A243C424BDD9E100F4441F /* StaticLoginBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A243C324BDD9E100F4441F /* StaticLoginBundle.swift */; }; 39A7138722E79C6700089423 /* ownCloud Intents.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 39A7138022E79C6700089423 /* ownCloud Intents.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 39B394492385334D00892E8D /* ThemeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B394482385334D00892E8D /* ThemeView.swift */; }; 39BC9C3023DB831F0097C52D /* DocumentEditingAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CD755123D787E400193950 /* DocumentEditingAction.swift */; }; 39BE385D23435AFE0062A2FE /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39BE385C23435AFE0062A2FE /* String+Extension.swift */; }; 39BF674C25E7FE020039663F /* CancelLabelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39BF674B25E7FE020039663F /* CancelLabelViewController.swift */; }; @@ -270,7 +269,6 @@ DC0A359424C0E5C800FB58FC /* GitCommit.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC85493321831B0B00782BA8 /* GitCommit.swift */; }; DC0A359524C0E5F900FB58FC /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE974BB207EACA60069FC2B /* UIImage+Extension.swift */; }; DC0A359624C0E61500FB58FC /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398FD4502334CF66004B68A1 /* UIView+Extension.swift */; }; - DC0A359724C0E64500FB58FC /* UIImageView+Thumbnails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C63F0E4231A91230088E8CA /* UIImageView+Thumbnails.swift */; }; DC0A359824C0E68700FB58FC /* OCItem+AppExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCBD8EA924B3755B00D92E1F /* OCItem+AppExtension.swift */; }; DC0A359924C0E6FE00FB58FC /* VendorServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCB44D86218718BA00DAA4CC /* VendorServices.swift */; }; DC0A359E24C0EBE500FB58FC /* ownCloudApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCC0855C2293F1FD008CC05C /* ownCloudApp.framework */; }; @@ -332,6 +330,8 @@ DC63208321FCAC1E007EC0A8 /* ClientActivityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC63208221FCAC1E007EC0A8 /* ClientActivityViewController.swift */; }; DC63208521FCEBE9007EC0A8 /* ClientActivityCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC63208421FCEBE9007EC0A8 /* ClientActivityCell.swift */; }; DC6428D02081406800493A01 /* CollapsibleProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC6428CF2081406800493A01 /* CollapsibleProgressBar.swift */; }; + DC66A9F4279EEBF900792AC8 /* ThemeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B394482385334D00892E8D /* ThemeView.swift */; }; + DC66A9F6279EEC3100792AC8 /* ResourceViewHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC66A9F5279EEC3100792AC8 /* ResourceViewHost.swift */; }; DC66F39C239659C000CF4812 /* OCASN1.h in Headers */ = {isa = PBXBuildFile; fileRef = DC66F39A239659C000CF4812 /* OCASN1.h */; }; DC66F39D239659C000CF4812 /* OCASN1.m in Sources */ = {isa = PBXBuildFile; fileRef = DC66F39B239659C000CF4812 /* OCASN1.m */; }; DC66F3A523965A1400CF4812 /* NSDate+RFC3339.h in Headers */ = {isa = PBXBuildFile; fileRef = DC66F3A323965A1400CF4812 /* NSDate+RFC3339.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -369,6 +369,8 @@ DC9BFBB320A19AF4007064B5 /* doc in Resources */ = {isa = PBXBuildFile; fileRef = DC9BFBB220A19AF3007064B5 /* doc */; }; DC9BFBBD20A1C37B007064B5 /* PasswordManagerAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC9BFBBC20A1C37B007064B5 /* PasswordManagerAccess.swift */; }; DC9C1AEC247C76470067895A /* MessageGroupCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC9C1AEB247C76470067895A /* MessageGroupCell.swift */; }; + DCA2EDE2279B16F1001F04E6 /* ResourceSourceItemIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA2EDE1279B16F1001F04E6 /* ResourceSourceItemIcons.swift */; }; + DCA2EDE4279B1789001F04E6 /* ResourceItemIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA2EDE3279B1789001F04E6 /* ResourceItemIcon.swift */; }; DCA35D3F24CEDA5200DBE2B0 /* DiagnosticViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA35D3E24CEDA5200DBE2B0 /* DiagnosticViewController.swift */; }; DCA35D8124D1707100DBE2B0 /* OCSyncRecordActivity+DiagnosticGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA35D8024D1707100DBE2B0 /* OCSyncRecordActivity+DiagnosticGenerator.swift */; }; DCA35DA724D309B600DBE2B0 /* OCFileProviderServiceSession+UploadByFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA35DA624D309B600DBE2B0 /* OCFileProviderServiceSession+UploadByFileProvider.swift */; }; @@ -512,8 +514,8 @@ DCF2DA8324C83BFB0026D790 /* OCFileProviderService.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF2DA7524C82C9F0026D790 /* OCFileProviderService.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCF2DA8624C87A330026D790 /* OCCore+FPServices.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF2DA8424C87A330026D790 /* OCCore+FPServices.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCF2DA8724C87A330026D790 /* OCCore+FPServices.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF2DA8524C87A330026D790 /* OCCore+FPServices.m */; }; - DCF575EB2796CBDF003BEBBA /* OCAvatar+ViewProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF575E92796CBDF003BEBBA /* OCAvatar+ViewProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DCF575EC2796CBDF003BEBBA /* OCAvatar+ViewProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF575EA2796CBDF003BEBBA /* OCAvatar+ViewProvider.m */; }; + DCF575EB2796CBDF003BEBBA /* OCImage+ViewProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF575E92796CBDF003BEBBA /* OCImage+ViewProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCF575EC2796CBDF003BEBBA /* OCImage+ViewProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF575EA2796CBDF003BEBBA /* OCImage+ViewProvider.m */; }; DCF575EF2796CE38003BEBBA /* OCViewHost.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF575ED2796CE38003BEBBA /* OCViewHost.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCF575F02796CE38003BEBBA /* OCViewHost.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF575EE2796CE38003BEBBA /* OCViewHost.m */; }; DCFB74BB21AD5C46005796AF /* StaticLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC44344321AC031600376B16 /* StaticLoginViewController.swift */; }; @@ -1307,6 +1309,7 @@ DC63208421FCEBE9007EC0A8 /* ClientActivityCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientActivityCell.swift; sourceTree = ""; }; DC63208621FCEE5D007EC0A8 /* ProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressView.swift; sourceTree = ""; }; DC6428CF2081406800493A01 /* CollapsibleProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleProgressBar.swift; sourceTree = ""; }; + DC66A9F5279EEC3100792AC8 /* ResourceViewHost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceViewHost.swift; sourceTree = ""; }; DC66F39A239659C000CF4812 /* OCASN1.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCASN1.h; sourceTree = ""; }; DC66F39B239659C000CF4812 /* OCASN1.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCASN1.m; sourceTree = ""; }; DC66F3A323965A1400CF4812 /* NSDate+RFC3339.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSDate+RFC3339.h"; sourceTree = ""; }; @@ -1354,6 +1357,8 @@ DC9BFBBA20A1B3CA007064B5 /* icon-password-manager.tvg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "icon-password-manager.tvg"; path = "img/filetypes-tvg/icon-password-manager.tvg"; sourceTree = SOURCE_ROOT; }; DC9BFBBC20A1C37B007064B5 /* PasswordManagerAccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordManagerAccess.swift; sourceTree = ""; }; DC9C1AEB247C76470067895A /* MessageGroupCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageGroupCell.swift; sourceTree = ""; }; + DCA2EDE1279B16F1001F04E6 /* ResourceSourceItemIcons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceSourceItemIcons.swift; sourceTree = ""; }; + DCA2EDE3279B1789001F04E6 /* ResourceItemIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceItemIcon.swift; sourceTree = ""; }; DCA35D3E24CEDA5200DBE2B0 /* DiagnosticViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiagnosticViewController.swift; sourceTree = ""; }; DCA35D8024D1707100DBE2B0 /* OCSyncRecordActivity+DiagnosticGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCSyncRecordActivity+DiagnosticGenerator.swift"; sourceTree = ""; }; DCA35DA624D309B600DBE2B0 /* OCFileProviderServiceSession+UploadByFileProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCFileProviderServiceSession+UploadByFileProvider.swift"; sourceTree = ""; }; @@ -1493,8 +1498,8 @@ DCF4F17A20519F9D00189B9A /* StaticTableViewSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticTableViewSection.swift; sourceTree = ""; }; DCF4F17E2051A0D000189B9A /* StaticTableViewRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticTableViewRow.swift; sourceTree = ""; }; DCF4F18A2052BA4C00189B9A /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; - DCF575E92796CBDF003BEBBA /* OCAvatar+ViewProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCAvatar+ViewProvider.h"; sourceTree = ""; }; - DCF575EA2796CBDF003BEBBA /* OCAvatar+ViewProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCAvatar+ViewProvider.m"; sourceTree = ""; }; + DCF575E92796CBDF003BEBBA /* OCImage+ViewProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCImage+ViewProvider.h"; sourceTree = ""; }; + DCF575EA2796CBDF003BEBBA /* OCImage+ViewProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCImage+ViewProvider.m"; sourceTree = ""; }; DCF575ED2796CE38003BEBBA /* OCViewHost.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCViewHost.h; sourceTree = ""; }; DCF575EE2796CE38003BEBBA /* OCViewHost.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCViewHost.m; sourceTree = ""; }; DCFB74C321AD7E18005796AF /* StaticLoginServerListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLoginServerListViewController.swift; sourceTree = ""; }; @@ -1833,6 +1838,7 @@ 3912208023436E9B0026C290 /* Client */ = { isa = PBXGroup; children = ( + DCA2EDDB279B0E5D001F04E6 /* Resource Sources */, DCE4E43424C199860051722F /* Actions */, DCE4E42D24C1961D0051722F /* File Lists */, 399EA6ED25E6544000B6FF11 /* Sharing */, @@ -2396,7 +2402,6 @@ isa = PBXGroup; children = ( 397E276B23D05A5400117B07 /* ServerListToolCell.swift */, - 39B394482385334D00892E8D /* ThemeView.swift */, DC680579212EAB5E006C3B1F /* ThemeCertificateViewController.swift */, ); path = Theming; @@ -2537,10 +2542,12 @@ 39B39446238532DC00892E8D /* ThemeTableViewCell.swift */, DCE974B1207E3AF80069FC2B /* ThemeNavigationController.swift */, DC0B37962051681600189B9A /* ThemeButton.swift */, + 39B394482385334D00892E8D /* ThemeView.swift */, 39E2FDFF21FF814A00F0117F /* ThemeRoundedButton.swift */, DC243BF92317B446004FBB5C /* ThemeWindow.swift */, DCAB9CC823417243009091B6 /* ThemedAlertController.swift */, 3971B48E221B23FE006FB441 /* ThemeableColoredView.swift */, + DC66A9F5279EEC3100792AC8 /* ResourceViewHost.swift */, ); path = UI; sourceTree = ""; @@ -2616,6 +2623,15 @@ path = Licensing; sourceTree = ""; }; + DCA2EDDB279B0E5D001F04E6 /* Resource Sources */ = { + isa = PBXGroup; + children = ( + DCA2EDE1279B16F1001F04E6 /* ResourceSourceItemIcons.swift */, + DCA2EDE3279B1789001F04E6 /* ResourceItemIcon.swift */, + ); + path = "Resource Sources"; + sourceTree = ""; + }; DCA35D6724CF7B6E00DBE2B0 /* Diagnostic */ = { isa = PBXGroup; children = ( @@ -3121,8 +3137,8 @@ children = ( DCF575EE2796CE38003BEBBA /* OCViewHost.m */, DCF575ED2796CE38003BEBBA /* OCViewHost.h */, - DCF575EA2796CBDF003BEBBA /* OCAvatar+ViewProvider.m */, - DCF575E92796CBDF003BEBBA /* OCAvatar+ViewProvider.h */, + DCF575EA2796CBDF003BEBBA /* OCImage+ViewProvider.m */, + DCF575E92796CBDF003BEBBA /* OCImage+ViewProvider.h */, DCF072EB27986CCA00E0B01D /* OCResourceTextPlaceholder+ViewProvider.m */, DCF072EA27986CCA00E0B01D /* OCResourceTextPlaceholder+ViewProvider.h */, ); @@ -3262,7 +3278,7 @@ DCF072D82798559900E0B01D /* OCCircularImageView.h in Headers */, DCFEFE9C2368D7FA009A142F /* OCLicenseObserver.h in Headers */, DCFEFE2E236876D4009A142F /* OCLicenseProvider.h in Headers */, - DCF575EB2796CBDF003BEBBA /* OCAvatar+ViewProvider.h in Headers */, + DCF575EB2796CBDF003BEBBA /* OCImage+ViewProvider.h in Headers */, DCC832F3242CC28900153F8C /* NotificationManager.h in Headers */, DCC085802293F490008CC05C /* DisplaySettings.h in Headers */, DCFEFE3D236877B7009A142F /* OCLicenseProduct.h in Headers */, @@ -4139,7 +4155,6 @@ DC3F4522271A23A000ED2383 /* AcknowledgementsTableViewController.swift in Sources */, 4C88041822E78D790016CBA9 /* MediaFilesSettings.swift in Sources */, 025FC7272478123E009307A7 /* MediaExportSettingsSection.swift in Sources */, - 39B394492385334D00892E8D /* ThemeView.swift in Sources */, 025FC720247810AB009307A7 /* MediaUploadSettingsViewController.swift in Sources */, 4CB8ADDE22DF5D3700F1FEBC /* PHPhotoLibrary+Extension.swift in Sources */, 6EA78B8F2179B55400A5216A /* ImageScrollView.swift in Sources */, @@ -4258,6 +4273,7 @@ DCE4E44F24C1DF130051722F /* UIViewController+Extension.swift in Sources */, DC0A357F24C0E43C00FB58FC /* ThemeStyle+DefaultStyles.swift in Sources */, DCB5D60B25FC14B6004C52D9 /* OCIssue+Extension.swift in Sources */, + DC66A9F4279EEBF900792AC8 /* ThemeView.swift in Sources */, DC0A356F24C0E42700FB58FC /* StaticTableViewController.swift in Sources */, DC0A357224C0E42D00FB58FC /* PushPresentationController.swift in Sources */, DC0A358F24C0E46000FB58FC /* PointerEffect.swift in Sources */, @@ -4269,10 +4285,10 @@ DC0A355424C0E2C200FB58FC /* SortBar.swift in Sources */, DCE4E45924C1F0F70051722F /* ClientQueryViewController.swift in Sources */, DCE4E45124C1E4430051722F /* UIBarButtonItem+Extension.swift in Sources */, + DCA2EDE2279B16F1001F04E6 /* ResourceSourceItemIcons.swift in Sources */, DC0A357724C0E43200FB58FC /* ProgressSummarizer.swift in Sources */, 392CFEB72705831700631D2B /* LAContext+Extension.swift in Sources */, DCE4E48724C1F9F50051722F /* CreateFolderAction.swift in Sources */, - DC0A359724C0E64500FB58FC /* UIImageView+Thumbnails.swift in Sources */, 39E6DE86233CDF1E008DAE04 /* OCItemTracker.swift in Sources */, DC0A358624C0E44600FB58FC /* ThemeTVGResource.swift in Sources */, DC0A358524C0E44600FB58FC /* ThemeResource.swift in Sources */, @@ -4288,6 +4304,7 @@ DC0A357424C0E42D00FB58FC /* PushTransition.swift in Sources */, DC0A357824C0E43700FB58FC /* CardPresentationController.swift in Sources */, 393D2B3F23FEB6DC00ED4F8C /* DispatchQueueTools.swift in Sources */, + DC66A9F6279EEC3100792AC8 /* ResourceViewHost.swift in Sources */, DC0A357324C0E42D00FB58FC /* PushTransitionDelegate.swift in Sources */, DCE4E45424C1EC040051722F /* BreadCrumbTableViewController.swift in Sources */, 399EA73A25E656A900B6FF11 /* UITableView+Extension.swift in Sources */, @@ -4302,6 +4319,7 @@ DC0A357924C0E43700FB58FC /* CardTransitionDelegate.swift in Sources */, DC0A358724C0E44600FB58FC /* ThemeImage.swift in Sources */, DC0A355824C0E35B00FB58FC /* Synchronized.swift in Sources */, + DCA2EDE4279B1789001F04E6 /* ResourceItemIcon.swift in Sources */, 0287DD7D249131E000C912CA /* AppStatistics.swift in Sources */, DC0A359924C0E6FE00FB58FC /* VendorServices.swift in Sources */, DC0A358324C0E44200FB58FC /* VectorImage.swift in Sources */, @@ -4382,7 +4400,7 @@ DCF072ED27986CCA00E0B01D /* OCResourceTextPlaceholder+ViewProvider.m in Sources */, DCCD778C2604C91B00098573 /* NSDate+ComputedTimes.m in Sources */, DC66F3A623965A1400CF4812 /* NSDate+RFC3339.m in Sources */, - DCF575EC2796CBDF003BEBBA /* OCAvatar+ViewProvider.m in Sources */, + DCF575EC2796CBDF003BEBBA /* OCImage+ViewProvider.m in Sources */, DCF2DA8724C87A330026D790 /* OCCore+FPServices.m in Sources */, DC7C101224B5FD6500227085 /* OCBookmark+AppExtensions.m in Sources */, DC4332012472E1B4002DC0E5 /* OCLicenseEMMProvider.m in Sources */, diff --git a/ownCloud/Client/ClientRootViewController.swift b/ownCloud/Client/ClientRootViewController.swift index 48aaec5b7..3275d7a6c 100644 --- a/ownCloud/Client/ClientRootViewController.swift +++ b/ownCloud/Client/ClientRootViewController.swift @@ -357,6 +357,8 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa func coreReady(_ lastVisibleItemId: String?) { OnMainThread { if let core = self.core { + core.vault.resourceManager?.add(ResourceSourceItemIcons(core: core)) + if let localItemId = lastVisibleItemId { self.createFileListStack(for: localItemId) } else { diff --git a/ownCloud/Client/Library/Item Policies/ItemPolicyCell.swift b/ownCloud/Client/Library/Item Policies/ItemPolicyCell.swift index 74c91d5eb..dc2bba19a 100644 --- a/ownCloud/Client/Library/Item Policies/ItemPolicyCell.swift +++ b/ownCloud/Client/Library/Item Policies/ItemPolicyCell.swift @@ -59,11 +59,11 @@ class ItemPolicyCell: ClientItemResolvingCell { if let itemPolicy = itemPolicy { if let itemPath = itemPolicy.path { if itemPath.hasSuffix("/") { - self.iconView.image = Theme.shared.image(for: "folder", size: iconSize) + self.iconView.activeViewProvider = ResourceItemIcon.folder self.itemResolutionPath = itemPath } else { - self.iconView.image = Theme.shared.image(for: "file", size: iconSize) + self.iconView.activeViewProvider = ResourceItemIcon.file if let itemLocalID = itemPolicy.localID { self.itemResolutionLocalID = itemLocalID diff --git a/ownCloud/Client/Viewer/DisplayViewController.swift b/ownCloud/Client/Viewer/DisplayViewController.swift index e2dd29885..0a08ea5d2 100644 --- a/ownCloud/Client/Viewer/DisplayViewController.swift +++ b/ownCloud/Client/Viewer/DisplayViewController.swift @@ -177,7 +177,7 @@ class DisplayViewController: UIViewController, Themeable, OCQueryDelegate { // MARK: - Subviews / UI elements - private var iconImageView = UIImageView() + private var iconImageView = ResourceViewHost() private var progressView = UIProgressView(progressViewStyle: .bar) private var cancelButton = ThemeButton(type: .custom) private var metadataInfoLabel = UILabel() @@ -225,7 +225,6 @@ class DisplayViewController: UIViewController, Themeable, OCQueryDelegate { } iconImageView.translatesAutoresizingMaskIntoConstraints = false - iconImageView.contentMode = .scaleAspectFit view.addSubview(iconImageView) @@ -614,16 +613,12 @@ class DisplayViewController: UIViewController, Themeable, OCQueryDelegate { updateDisplayTitleAndButtons() if let core = self.core { - let localCopy = core.localCopy(of: newItem) var didUpdate : Bool = false - if localCopy == nil { - iconImageView.setThumbnailImage(using: core, from: newItem, with: iconImageSize) - } + let request = OCResourceRequestItemThumbnail.request(for: newItem, maximumSize: iconImageSize, scale: 0, waitForConnectivity: true, changeHandler: nil) + iconImageView.request = request - if iconImageView.image == nil { - iconImageView.image = newItem.icon(fitInSize:iconImageSize) - } + core.vault.resourceManager?.start(request) // If we don't need to download item, just get direct URL (e.g. for video which can be streamed) if itemDirectURL == nil && !requiresLocalCopyForPreview { diff --git a/ownCloud/Server List/ServerListBookmarkCell.swift b/ownCloud/Server List/ServerListBookmarkCell.swift index ccc283106..81f556ad5 100644 --- a/ownCloud/Server List/ServerListBookmarkCell.swift +++ b/ownCloud/Server List/ServerListBookmarkCell.swift @@ -27,7 +27,7 @@ class ServerListBookmarkCell : ThemeTableViewCell { public var titleLabel : UILabel = UILabel() public var detailLabel : UILabel = UILabel() public var logoFallbackView : UIImageView = UIImageView() - public var iconView : OCViewHost = OCViewHost(fallbackSize: CGSize(width: ServerListBookmarkCell.iconSideLength, height: ServerListBookmarkCell.iconSideLength)) + public var iconView : ResourceViewHost = ResourceViewHost(fallbackSize: CGSize(width: ServerListBookmarkCell.iconSideLength, height: ServerListBookmarkCell.iconSideLength)) public var infoView : UIView = UIView() public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { diff --git a/ownCloud/Static Login/Interface/StaticLoginSingleAccountServerListViewController.swift b/ownCloud/Static Login/Interface/StaticLoginSingleAccountServerListViewController.swift index 0d0bb1171..7104c6337 100644 --- a/ownCloud/Static Login/Interface/StaticLoginSingleAccountServerListViewController.swift +++ b/ownCloud/Static Login/Interface/StaticLoginSingleAccountServerListViewController.swift @@ -264,14 +264,14 @@ class StaticLoginSingleAccountServerListViewController: ServerListTableViewContr if SingleAccountSection(rawValue: section) == .accessFiles { if let bookmark : OCBookmark = OCBookmarkManager.shared.bookmarks.first, let displayName = self.displayName ?? bookmark.userName { - var avatarView : OCViewHost? + var avatarView : ResourceViewHost? let avatarSize = CGSize(width: 128, height: 128) let fallbackView = UIImageView(image: UIImage(named: "branding-login-logo")) if let avatar = bookmark.avatar { - avatarView = OCViewHost(viewProvider: avatar, fallbackSize: avatarSize, fallbackView: fallbackView, viewProviderContext: nil) + avatarView = ResourceViewHost(viewProvider: avatar, fallbackSize: avatarSize, fallbackView: fallbackView, viewProviderContext: nil) } else { - avatarView = OCViewHost(fallbackView: fallbackView, viewProviderContext: nil) + avatarView = ResourceViewHost(fallbackView: fallbackView, viewProviderContext: nil) } if let avatarView = avatarView { diff --git a/ownCloudAppFramework/View Providers/OCAvatar+ViewProvider.h b/ownCloudAppFramework/View Providers/OCImage+ViewProvider.h similarity index 87% rename from ownCloudAppFramework/View Providers/OCAvatar+ViewProvider.h rename to ownCloudAppFramework/View Providers/OCImage+ViewProvider.h index 0c4458b6b..2bc3699e6 100644 --- a/ownCloudAppFramework/View Providers/OCAvatar+ViewProvider.h +++ b/ownCloudAppFramework/View Providers/OCImage+ViewProvider.h @@ -1,5 +1,5 @@ // -// OCAvatar+ViewProvider.h +// OCImage+ViewProvider.h // ownCloudApp // // Created by Felix Schwarz on 18.01.22. @@ -20,7 +20,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface OCAvatar (ViewProvider) +@interface OCImage (ViewProvider) @end diff --git a/ownCloudAppFramework/View Providers/OCAvatar+ViewProvider.m b/ownCloudAppFramework/View Providers/OCImage+ViewProvider.m similarity index 57% rename from ownCloudAppFramework/View Providers/OCAvatar+ViewProvider.m rename to ownCloudAppFramework/View Providers/OCImage+ViewProvider.m index 83832b733..b68698f2a 100644 --- a/ownCloudAppFramework/View Providers/OCAvatar+ViewProvider.m +++ b/ownCloudAppFramework/View Providers/OCImage+ViewProvider.m @@ -1,5 +1,5 @@ // -// OCAvatar+ViewProvider.m +// OCImage+ViewProvider.m // ownCloudApp // // Created by Felix Schwarz on 18.01.22. @@ -16,27 +16,43 @@ * */ -#import "OCAvatar+ViewProvider.h" +#import "OCImage+ViewProvider.h" #import "OCCircularImageView.h" -@implementation OCAvatar (ViewProvider) +@implementation OCImage (ViewProvider) - (void)provideViewForSize:(CGSize)size inContext:(nullable OCViewProviderContext *)context completion:(void(^)(OCView * _Nullable view))completionHandler { + BOOL isAvatar = [self isKindOfClass:OCAvatar.class]; + [self requestImageForSize:size scale:0 withCompletionHandler:^(OCImage * _Nullable ocImage, NSError * _Nullable error, CGSize maximumSizeInPoints, UIImage * _Nullable image) { if (image != nil) { dispatch_async(dispatch_get_main_queue(), ^{ - OCCircularImageView *avatarView = nil; + if (isAvatar) + { + OCCircularImageView *avatarView = nil; + + avatarView = [[OCCircularImageView alloc] initWithImage:image]; + avatarView.translatesAutoresizingMaskIntoConstraints = NO; + + completionHandler(avatarView); + } + else + { + UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; - avatarView = [[OCCircularImageView alloc] initWithImage:image]; - avatarView.translatesAutoresizingMaskIntoConstraints = NO; + imageView.translatesAutoresizingMaskIntoConstraints = NO; + imageView.contentMode = UIViewContentModeScaleAspectFit; - completionHandler(avatarView); + completionHandler(imageView); + } }); } - - completionHandler(nil); + else + { + completionHandler(nil); + } }]; } diff --git a/ownCloudAppFramework/View Providers/OCViewHost.h b/ownCloudAppFramework/View Providers/OCViewHost.h index 6650c046c..dec51d713 100644 --- a/ownCloudAppFramework/View Providers/OCViewHost.h +++ b/ownCloudAppFramework/View Providers/OCViewHost.h @@ -37,6 +37,8 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithRequest:(OCResourceRequest *)request fallbackView:(nullable UIView *)fallbackView viewProviderContext:(nullable OCViewProviderContext *)viewProviderContext; - (instancetype)initWithViewProvider:(id)viewProvider fallbackSize:(CGSize)fallbackSize fallbackView:(nullable UIView *)fallbackView viewProviderContext:(nullable OCViewProviderContext *)viewProviderContext; +- (void)reloadView; + @end NS_ASSUME_NONNULL_END diff --git a/ownCloudAppFramework/View Providers/OCViewHost.m b/ownCloudAppFramework/View Providers/OCViewHost.m index 174800665..e84521689 100644 --- a/ownCloudAppFramework/View Providers/OCViewHost.m +++ b/ownCloudAppFramework/View Providers/OCViewHost.m @@ -95,17 +95,24 @@ - (void)resourceRequest:(nonnull OCResourceRequest *)request didChangeWithError: return; } - if ((error == nil) && (newResource != nil)) + if ((error == nil) && ((newResource != nil) || !isOngoing)) { NSUInteger requestSeedOnDelivery = _requestSeed; - dispatch_async(dispatch_get_main_queue(), ^{ + [self onMainThread:^{ if ((request == self->_request) || // same request ((self->_request == nil) && (requestSeedOnDelivery == self->_requestSeed))) // request is no longer ongoing, but no new request has been set in the meantime, either, so this resource can be used { - [self setActiveViewProviderFromResource:newResource]; + if (newResource != nil) + { + [self setActiveViewProviderFromResource:newResource]; + } + else + { + self.activeViewProvider = nil; + } } - }); + }]; } if (!isOngoing) @@ -173,6 +180,18 @@ - (void)setHostedView:(UIView *)newView } } +- (void)onMainThread:(dispatch_block_t)block +{ + if (NSThread.isMainThread) + { + block(); + } + else + { + dispatch_async(dispatch_get_main_queue(), block); + } +} + - (void)updateView { id originalViewProvider = _activeViewProvider; @@ -187,25 +206,32 @@ - (void)updateView } [_activeViewProvider provideViewForSize:size inContext:self.viewProviderContext completion:^(UIView * _Nullable newView) { - dispatch_async(dispatch_get_main_queue(), ^{ + [self onMainThread:^{ if (originalViewProvider == self.activeViewProvider) { [self setHostedView:(newView != nil) ? newView : self.fallbackView]; + // OCLogDebug(@"%p: _activeViewProvider/originalViewProvider: %@, size: %@, newView: %@", self, originalViewProvider, NSStringFromCGSize(size), newView); } else { - OCLogWarning(@"_activeViewProvider changed during receipt") + OCLogWarning(@"%p: _activeViewProvider changed during receipt (from %@ to %@)", self, originalViewProvider, self.activeViewProvider); } - }); + }]; }]; } else { - dispatch_async(dispatch_get_main_queue(), ^{ + [self onMainThread:^{ if (originalViewProvider == self.activeViewProvider) { + // OCLogDebug(@"%p: _activeViewProvider/originalViewProvider: %@, size: %@, newView: %@", self, originalViewProvider, NSStringFromCGSize(size), self.fallbackView); [self setHostedView:self.fallbackView]; } else { - OCLogWarning(@"_activeViewProvider changed during receipt") + OCLogWarning(@"%p: _activeViewProvider changed during receipt (from %@ to %@)", self, originalViewProvider, self.activeViewProvider); } - }); + }]; } } +- (void)reloadView +{ + [self updateView]; +} + @end diff --git a/ownCloudAppFramework/ownCloudApp.h b/ownCloudAppFramework/ownCloudApp.h index db8d6e89e..da72ec07f 100644 --- a/ownCloudAppFramework/ownCloudApp.h +++ b/ownCloudAppFramework/ownCloudApp.h @@ -76,7 +76,7 @@ FOUNDATION_EXPORT const unsigned char ownCloudAppVersionString[]; #import #import -#import +#import #import #import #import diff --git a/ownCloudAppShared/Client/Resource Sources/ResourceItemIcon.swift b/ownCloudAppShared/Client/Resource Sources/ResourceItemIcon.swift new file mode 100644 index 000000000..bc1e309ae --- /dev/null +++ b/ownCloudAppShared/Client/Resource Sources/ResourceItemIcon.swift @@ -0,0 +1,52 @@ +// +// ResourceItemIcon.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 21.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK + +public class ResourceItemIcon: OCResource, OCViewProvider { + public var iconName : String? + + public static let folder : ResourceItemIcon = ResourceItemIcon(iconName: "folder") + public static let file : ResourceItemIcon = ResourceItemIcon(iconName: "file") + + public convenience init(iconName: String, identifier: String? = nil) { + + self.init() + + self.type = .itemThumbnail + self.identifier = identifier ?? "icon:\(iconName)" + + self.iconName = iconName + } + + public func provideView(for size: CGSize, in context: OCViewProviderContext?, completion completionHandler: @escaping (UIView?) -> Void) { + if let iconName = iconName, let vectorImage = Theme.shared.tvgImage(for: iconName) { + OnMainThread { + let vectorView = VectorImageView() + + vectorView.translatesAutoresizingMaskIntoConstraints = false + vectorView.vectorImage = vectorImage + + completionHandler(vectorView) + } + } else { + completionHandler(nil) + } + } +} diff --git a/ownCloudAppShared/Client/Resource Sources/ResourceSourceItemIcons.swift b/ownCloudAppShared/Client/Resource Sources/ResourceSourceItemIcons.swift new file mode 100644 index 000000000..5a4b58fed --- /dev/null +++ b/ownCloudAppShared/Client/Resource Sources/ResourceSourceItemIcons.swift @@ -0,0 +1,55 @@ +// +// ResourceSourceItemIcons.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 21.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK + +public class ResourceSourceItemIcons: OCResourceSource { + static public let identifier : OCResourceSourceIdentifier = OCResourceSourceIdentifier(rawValue: "app.item-icons") + + public override var identifier: OCResourceSourceIdentifier { + return ResourceSourceItemIcons.identifier + } + + public override var type: OCResourceType { + return .itemThumbnail + } + + public override func priority(forType type: OCResourceType) -> OCResourceSourcePriority { + return .instant + } + + public override func quality(for request: OCResourceRequest) -> OCResourceQuality { + return .fallback + } + + public override func provideResource(for request: OCResourceRequest, resultHandler: @escaping OCResourceSourceResultHandler) { + if let thumbnailRequest = request as? OCResourceRequestItemThumbnail, + let iconName = thumbnailRequest.item.iconName { + let resource = ResourceItemIcon(request: request) + + resource.iconName = iconName + resource.mimeType = OCResourceMIMEType(rawValue: "image/tvg") + resource.quality = .fallback + + resultHandler(nil, resource) + } else { + resultHandler(nil, nil) + } + } +} diff --git a/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift b/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift index 835744177..2261aa77b 100644 --- a/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift +++ b/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift @@ -413,7 +413,7 @@ open class GroupSharingTableViewController: SharingTableViewController, UISearch let avatarRequest = OCResourceRequestAvatar(for: recipientUser, maximumSize: OCAvatar.defaultSize, scale: 0, waitForConnectivity: false) { request, error, ongoing, oldResource, newResource in Log.debug("Avatar for \(String(describing: recipient.user?.userName)): ongoing=\(ongoing), resource=\(newResource.debugDescription)") } - leadingAccessoryView = OCViewHost(request: avatarRequest, fallbackView: nil, viewProviderContext: nil) + leadingAccessoryView = ResourceViewHost(request: avatarRequest, fallbackView: nil, viewProviderContext: nil) leadingAccessoryView?.translatesAutoresizingMaskIntoConstraints = false leadingAccessoryView?.widthAnchor.constraint(equalToConstant: 40).isActive = true leadingAccessoryView?.heightAnchor.constraint(equalToConstant: 30).isActive = true diff --git a/ownCloudAppShared/Client/Sharing/ShareClientItemCell.swift b/ownCloudAppShared/Client/Sharing/ShareClientItemCell.swift index e56a507c7..20bd85235 100644 --- a/ownCloudAppShared/Client/Sharing/ShareClientItemCell.swift +++ b/ownCloudAppShared/Client/Sharing/ShareClientItemCell.swift @@ -36,9 +36,9 @@ open class ShareClientItemCell: ClientItemResolvingCell { didSet { if let share = share { if share.itemType == .collection { - self.iconView.image = Theme.shared.image(for: "folder", size: iconSize) + self.iconView.activeViewProvider = ResourceItemIcon.folder } else { - self.iconView.image = Theme.shared.image(for: "file", size: iconSize) + self.iconView.activeViewProvider = ResourceItemIcon.file } self.itemResolutionPath = share.itemPath diff --git a/ownCloudAppShared/Client/User Interface/ClientItemCell.swift b/ownCloudAppShared/Client/User Interface/ClientItemCell.swift index 1359a347b..f680f86e6 100644 --- a/ownCloudAppShared/Client/User Interface/ClientItemCell.swift +++ b/ownCloudAppShared/Client/User Interface/ClientItemCell.swift @@ -18,6 +18,7 @@ import UIKit import ownCloudSDK +import ownCloudApp extension NSMutableAttributedString { var boldFont:UIFont { return UIFont.preferredFont(forTextStyle: .headline) } @@ -51,7 +52,7 @@ public protocol ClientItemCellDelegate: class { func hasMessage(for item: OCItem) -> Bool } -open class ClientItemCell: ThemeTableViewCell, ItemContainer { +open class ClientItemCell: ThemeTableViewCell { private let horizontalMargin : CGFloat = 15 private let verticalLabelMargin : CGFloat = 10 private let verticalIconMargin : CGFloat = 10 @@ -74,8 +75,7 @@ open class ClientItemCell: ThemeTableViewCell, ItemContainer { open var titleLabel : UILabel = UILabel() open var detailLabel : UILabel = UILabel() - open var iconView : UIImageView = UIImageView() - open var showingIcon : Bool = false + open var iconView : ResourceViewHost = ResourceViewHost() open var cloudStatusIconView : UIImageView = UIImageView() open var sharedStatusIconView : UIImageView = UIImageView() open var publicLinkStatusIconView : UIImageView = UIImageView() @@ -95,7 +95,7 @@ open class ClientItemCell: ThemeTableViewCell, ItemContainer { open var publicLinkStatusIconViewRightMarginConstraint : NSLayoutConstraint? open var cloudStatusIconViewRightMarginConstraint : NSLayoutConstraint? - open var activeThumbnailRequestProgress : Progress? + open var activeThumbnailRequest : OCResourceRequestItemThumbnail? open var hasMessageForItem : Bool = false @@ -347,27 +347,22 @@ open class ClientItemCell: ThemeTableViewCell, ItemContainer { } open func updateWith(_ item: OCItem) { - var iconImage : UIImage? - // Cancel any already active request - if activeThumbnailRequestProgress != nil { - activeThumbnailRequestProgress?.cancel() + if let activeThumbnailRequest = activeThumbnailRequest { + core?.vault.resourceManager?.stop(activeThumbnailRequest) + self.activeThumbnailRequest = nil } // Set has message self.hasMessageForItem = delegate?.hasMessage(for: item) ?? false // Set the icon and initiate thumbnail generation - iconImage = item.icon(fitInSize: iconSize) - self.iconView.image = iconImage + let thumbnailRequest = OCResourceRequestItemThumbnail.request(for: item, maximumSize: self.thumbnailSize, scale: 0, waitForConnectivity: true, changeHandler: nil) + + iconView.request = thumbnailRequest - if let core = core { - activeThumbnailRequestProgress = self.iconView.setThumbnailImage(using: core, from: item, with: thumbnailSize, itemContainer: self, progressHandler: { [weak self] (progress) in - if self?.activeThumbnailRequestProgress === progress { - self?.activeThumbnailRequestProgress = nil - } - }) - } + // Start new thumbnail request + core?.vault.resourceManager?.start(thumbnailRequest) self.accessoryType = .none @@ -569,10 +564,6 @@ open class ClientItemCell: ThemeTableViewCell, ItemContainer { moreButton.tintColor = collection.tableRowColors.secondaryLabelColor - if showingIcon, let item = item { - iconView.image = item.icon(fitInSize: iconSize) - } - if revealHighlight { backgroundColor = collection.tableRowHighlightColors.backgroundColor?.withAlphaComponent(0.5) } else { diff --git a/ownCloudAppShared/Client/User Interface/NamingViewController.swift b/ownCloudAppShared/Client/User Interface/NamingViewController.swift index 875650f09..d8342eac5 100644 --- a/ownCloudAppShared/Client/User Interface/NamingViewController.swift +++ b/ownCloudAppShared/Client/User Interface/NamingViewController.swift @@ -34,7 +34,7 @@ open class NamingViewController: UIViewController, Themeable { private var stackView: UIStackView private var thumbnailContainer: UIView - private var thumbnailImageView: UIImageView + private var thumbnailImageView: ResourceViewHost private var nameContainer: UIView private var nameTextField: UITextField @@ -65,8 +65,7 @@ open class NamingViewController: UIViewController, Themeable { thumbnailContainer = UIView(frame: .zero) - thumbnailImageView = UIImageView(frame: .zero) - thumbnailImageView.contentMode = .scaleAspectFit + thumbnailImageView = ResourceViewHost() nameContainer = UIView(frame: .zero) nameTextField = UITextField(frame: .zero) @@ -114,11 +113,14 @@ open class NamingViewController: UIViewController, Themeable { if let item = item, let core = core { nameTextField.text = item.name - thumbnailImageView.image = item.icon(fitInSize: thumbnailSize) - thumbnailImageView.setThumbnailImage(using: core, from: item, with: thumbnailSize) + + let thumbnailRequest = OCResourceRequestItemThumbnail.request(for: item, maximumSize: thumbnailSize, scale: 0, waitForConnectivity: true, changeHandler: nil) + thumbnailImageView.request = thumbnailRequest + + core.vault.resourceManager?.start(thumbnailRequest) } else { nameTextField.text = defaultName - thumbnailImageView.image = Theme.shared.image(for: "folder", size: thumbnailSize) + thumbnailImageView.activeViewProvider = ResourceItemIcon.folder } // Navigation buttons diff --git a/ownCloudAppShared/User Interface/More/MoreViewHeader.swift b/ownCloudAppShared/User Interface/More/MoreViewHeader.swift index 329ebf0a9..cf8bce2f6 100644 --- a/ownCloudAppShared/User Interface/More/MoreViewHeader.swift +++ b/ownCloudAppShared/User Interface/More/MoreViewHeader.swift @@ -20,13 +20,12 @@ import UIKit import ownCloudSDK open class MoreViewHeader: UIView { - private var iconView: UIImageView + private var iconView: ResourceViewHost private var labelContainerView : UIView private var titleLabel: UILabel private var detailLabel: UILabel private var favoriteButton: UIButton public var activityIndicator : UIActivityIndicatorView - private var showsIcon : Bool = true public var thumbnailSize = CGSize(width: 60, height: 60) public let favoriteSize = CGSize(width: 44, height: 44) @@ -45,7 +44,7 @@ open class MoreViewHeader: UIView { self.showFavoriteButton = favorite self.showActivityIndicator = showActivityIndicator - iconView = UIImageView() + iconView = ResourceViewHost() titleLabel = UILabel() detailLabel = UILabel() labelContainerView = UIView() @@ -69,7 +68,7 @@ open class MoreViewHeader: UIView { self.item = OCItem() self.url = url - iconView = UIImageView() + iconView = ResourceViewHost() titleLabel = UILabel() detailLabel = UILabel() labelContainerView = UIView() @@ -199,24 +198,10 @@ open class MoreViewHeader: UIView { detailLabel.attributedText = NSAttributedString(string: detail, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14, weight: .regular)]) } - self.iconView.image = item.icon(fitInSize: CGSize(width: thumbnailSize.width, height: thumbnailSize.height)) - - if item.thumbnailAvailability != .none { - let displayThumbnail = { (thumbnail: OCItemThumbnail?) in - _ = thumbnail?.request(for: CGSize(width: self.thumbnailSize.width, height: self.thumbnailSize.height), scale: 0, withCompletionHandler: { (_, error, _, image) in - if error == nil, image != nil, self.item.itemVersionIdentifier == thumbnail?.itemVersionIdentifier { - OnMainThread { - self.showsIcon = false - self.iconView.image = image - } - } - }) - } + let iconRequest = OCResourceRequestItemThumbnail.request(for: item, maximumSize: thumbnailSize, scale: 0, waitForConnectivity: true, changeHandler: nil) + self.iconView.request = iconRequest + core?.vault.resourceManager?.start(iconRequest) - _ = core?.retrieveThumbnail(for: item, maximumSize: CGSize(width: self.thumbnailSize.width, height: self.thumbnailSize.height), scale: 0, retrieveHandler: { (_, _, _, thumbnail, _, _) in - displayThumbnail(thumbnail) - }) - } titleLabel.numberOfLines = 0 } @@ -267,9 +252,5 @@ extension MoreViewHeader: Themeable { if adaptBackgroundColor { backgroundColor = collection.tableBackgroundColor } - - if showsIcon { - iconView.image = item.icon(fitInSize: CGSize(width: thumbnailSize.width, height: thumbnailSize.height)) - } } } diff --git a/ownCloudAppShared/User Interface/Theme/TVG/VectorImageView.swift b/ownCloudAppShared/User Interface/Theme/TVG/VectorImageView.swift index 81076898b..b5c6e1b35 100644 --- a/ownCloudAppShared/User Interface/Theme/TVG/VectorImageView.swift +++ b/ownCloudAppShared/User Interface/Theme/TVG/VectorImageView.swift @@ -72,6 +72,8 @@ public class VectorImageView: UIView, Themeable { if let rasterImage = _vectorImage?.image(fitInSize: viewBounds!.size, with: themeCollection.iconColors, cacheFor: themeCollection.identifier) { self.layer.contents = rasterImage.cgImage self.layer.contentsGravity = CALayerContentsGravity.resizeAspect + } else { + self.layer.contents = nil } } diff --git a/ownCloudAppShared/User Interface/Theme/UI/ResourceViewHost.swift b/ownCloudAppShared/User Interface/Theme/UI/ResourceViewHost.swift new file mode 100644 index 000000000..bc9755490 --- /dev/null +++ b/ownCloudAppShared/User Interface/Theme/UI/ResourceViewHost.swift @@ -0,0 +1,52 @@ +// +// ResourceViewHost.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 24.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK +import ownCloudApp + +public class ResourceViewHost: OCViewHost, Themeable { + private var hasRegistered : Bool = false + + deinit { + if hasRegistered { + Theme.shared.unregister(client: self) + } + } + + public override func didMoveToSuperview() { + if self.superview != nil { + if !hasRegistered { + hasRegistered = true + Theme.shared.register(client: self, applyImmediately: true) + } + } + } + + public func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { + if let themableViewProvider = activeViewProvider as? ThemableViewProvider { + if themableViewProvider.needsRefreshOnThemeChange { + reloadView() + } + } + } +} + +protocol ThemableViewProvider: OCViewProvider { + var needsRefreshOnThemeChange : Bool { get } +} diff --git a/ownCloud/Theming/ThemeView.swift b/ownCloudAppShared/User Interface/Theme/UI/ThemeView.swift similarity index 77% rename from ownCloud/Theming/ThemeView.swift rename to ownCloudAppShared/User Interface/Theme/UI/ThemeView.swift index fea1e180b..cb07a330b 100644 --- a/ownCloud/Theming/ThemeView.swift +++ b/ownCloudAppShared/User Interface/Theme/UI/ThemeView.swift @@ -17,12 +17,11 @@ */ import UIKit -import ownCloudAppShared -class ThemeView: UIView, Themeable { +open class ThemeView: UIView, Themeable { private var hasRegistered : Bool = false - init() { + public init() { super.init(frame: .zero) } @@ -32,11 +31,11 @@ class ThemeView: UIView, Themeable { } } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func didMoveToSuperview() { + override open func didMoveToSuperview() { if self.superview != nil { if !hasRegistered { hasRegistered = true @@ -47,11 +46,11 @@ class ThemeView: UIView, Themeable { private var themeAppliers : [ThemeApplier] = [] - func addThemeApplier(_ applier: @escaping ThemeApplier) { + open func addThemeApplier(_ applier: @escaping ThemeApplier) { themeAppliers.append(applier) } - func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { + open func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { for applier in themeAppliers { applier(theme, collection, event) } From d3af833cfb03be6e35c9536dfc0ff4a23baa27e6 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Mon, 24 Jan 2022 22:15:16 +0100 Subject: [PATCH 005/328] - switch deployment target to iOS 13.6 - remove all code for iOS versions < iOS 13.4 - fix deprecation warnings (partially with rewrites) --- .../DocumentActionViewController.swift | 6 +- ownCloud.xcodeproj/project.pbxproj | 30 +-- ownCloud/AppDelegate.swift | 32 +-- .../Actions+Extensions/CopyAction.swift | 49 ++-- .../Actions+Extensions/CutAction.swift | 8 +- .../DisplayExifMetadataAction.swift | 5 +- .../Actions+Extensions/FavoriteAction.swift | 6 +- .../ImportPasteboardAction.swift | 41 ++-- .../MediaEditingAction.swift | 35 --- .../Actions+Extensions/OpenInAction.swift | 6 +- .../PDFGotoPageAction.swift | 6 +- .../PresentationModeAction.swift | 6 +- .../Actions+Extensions/RenameAction.swift | 7 +- .../Client/Actions/Scanner/ScanAction.swift | 50 ++-- ownCloud/Client/ClientActivityCell.swift | 4 +- .../Client/ClientRootViewController.swift | 14 +- .../Client/ExternalBrowserBusyHandler.swift | 4 +- ...ntroller+OpenItemTableViewController.swift | 8 +- ...eListTableViewController+Multiselect.swift | 14 +- ownCloud/Client/PhotoAlbumTableViewCell.swift | 4 +- ownCloud/Client/PhotoSelectionViewCell.swift | 4 +- ownCloud/Client/Viewer/DisplayExtension.swift | 2 +- .../Client/Viewer/DisplayViewController.swift | 8 +- .../Image/ImageDisplayViewController.swift | 2 +- .../Viewer/PDF/PDFSearchResultsView.swift | 12 +- .../PDF/PDFThumbnailCollectionViewCell.swift | 4 +- .../PDF/PDFTocTableViewController.swift | 2 +- .../Viewer/PDF/PDFViewerViewController.swift | 46 ++-- .../QuickLook/PreviewViewController.swift | 8 +- .../Diagnostic/DiagnosticViewController.swift | 6 +- .../Issues/IssuesCardViewController.swift | 2 +- ownCloud/Key Commands/KeyCommands.swift | 202 +++++++-------- .../PhotoKit Extensions/PHAsset+Upload.swift | 2 +- .../Migration/MigrationActivityCell.swift | 2 +- .../ReleaseNotesHostViewController.swift | 14 +- ownCloud/SceneDelegate.swift | 2 +- .../Server List/ServerListBookmarkCell.swift | 4 +- .../ServerListTableViewController.swift | 43 +--- .../BackgroundUploadsSettingsSection.swift | 77 +++--- .../Settings/SettingsViewController.swift | 3 +- .../UserInterfaceSettingsSection.swift | 9 +- .../StaticLoginSetupViewController.swift | 2 +- ...ingleAccountServerListViewController.swift | 45 +--- .../StaticLoginStepViewController.swift | 6 +- .../Interface/StaticLoginViewController.swift | 12 +- .../Static Login/StaticLoginProfile.swift | 232 +++++++++--------- ownCloud/Tasks/ScheduledTaskManager.swift | 26 +- ownCloudAppFramework/Branding/Branding.m | 99 +------- .../AppExtensionNavigationController.swift | 6 +- .../AppLock/AppLockManager.swift | 9 +- .../AppLock/PasscodeViewController.swift | 16 +- ownCloudAppShared/Branding/Branding+App.swift | 220 ++++++++--------- .../ClientQueryViewController.swift | 24 +- .../QueryFileListTableViewController.swift | 13 +- .../GroupSharingTableViewController.swift | 17 +- .../User Interface/ClientItemCell.swift | 20 +- .../Client/User Interface/SortBar.swift | 19 +- .../UIKeyCommand+Extension.swift | 29 +++ .../Cursor Support/PointerEffect.swift | 12 +- .../User Interface/More/MoreViewHeader.swift | 8 +- .../Progress/ProgressHUDViewController.swift | 2 +- .../StaticTableView/StaticTableViewRow.swift | 14 +- .../Theme/NSObject+ThemeApplication.swift | 65 ++--- .../User Interface/Theme/Theme.swift | 16 +- .../Theme/ThemeCollection.swift | 59 ++--- .../Theme/ThemeStyle+Extensions.swift | 32 ++- .../User Interface/Theme/UI/ThemeWindow.swift | 6 +- .../Theme/UI/ThemedAlertController.swift | 8 +- 68 files changed, 679 insertions(+), 1127 deletions(-) delete mode 100644 ownCloud/Client/Actions/Actions+Extensions/MediaEditingAction.swift create mode 100644 ownCloudAppShared/UIKit Extension/UIKeyCommand+Extension.swift diff --git a/ownCloud File Provider UI/DocumentActionViewController.swift b/ownCloud File Provider UI/DocumentActionViewController.swift index 758218b71..b67200d13 100644 --- a/ownCloud File Provider UI/DocumentActionViewController.swift +++ b/ownCloud File Provider UI/DocumentActionViewController.swift @@ -181,11 +181,7 @@ class DocumentActionViewController: FPUIActionExtensionViewController { } @objc func dismissView() { - if #available(iOS 13.0, *) { - self.dismiss(animated: true) { - self.complete() - } - } else { + self.dismiss(animated: true) { self.complete() } } diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index d4f371943..c5d73e7f7 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -49,7 +49,6 @@ 391C79AE24E187C400CB6333 /* LegacyCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F2890F24BFAF0100E3D35C /* LegacyCredentials.swift */; }; 392378FF24EBD1A1006E86DE /* branding-splashscreen.png in Resources */ = {isa = PBXBuildFile; fileRef = 39F48A6624D848550000E3F9 /* branding-splashscreen.png */; }; 3923790524EBD1A5006E86DE /* branding-splashscreen-background.png in Resources */ = {isa = PBXBuildFile; fileRef = 39F48A6724D848550000E3F9 /* branding-splashscreen-background.png */; }; - 39265BB223D9987500B0C4CA /* MediaEditingAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39265BB123D9987500B0C4CA /* MediaEditingAction.swift */; }; 392CFEB72705831700631D2B /* LAContext+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 392CFEB62705831700631D2B /* LAContext+Extension.swift */; }; 392DDAE624C8923B009E5406 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 233BDEA6204FEFE500C06732 /* Assets.xcassets */; }; 392DDB1424CF024D009E5406 /* ImportFilesController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 392DDB1324CF024C009E5406 /* ImportFilesController.swift */; }; @@ -332,6 +331,7 @@ DC6428D02081406800493A01 /* CollapsibleProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC6428CF2081406800493A01 /* CollapsibleProgressBar.swift */; }; DC66A9F4279EEBF900792AC8 /* ThemeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B394482385334D00892E8D /* ThemeView.swift */; }; DC66A9F6279EEC3100792AC8 /* ResourceViewHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC66A9F5279EEC3100792AC8 /* ResourceViewHost.swift */; }; + DC66A9F8279F467200792AC8 /* UIKeyCommand+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC66A9F7279F467200792AC8 /* UIKeyCommand+Extension.swift */; }; DC66F39C239659C000CF4812 /* OCASN1.h in Headers */ = {isa = PBXBuildFile; fileRef = DC66F39A239659C000CF4812 /* OCASN1.h */; }; DC66F39D239659C000CF4812 /* OCASN1.m in Sources */ = {isa = PBXBuildFile; fileRef = DC66F39B239659C000CF4812 /* OCASN1.m */; }; DC66F3A523965A1400CF4812 /* NSDate+RFC3339.h in Headers */ = {isa = PBXBuildFile; fileRef = DC66F3A323965A1400CF4812 /* NSDate+RFC3339.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -969,7 +969,6 @@ 391220932344C30F0026C290 /* CutAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CutAction.swift; sourceTree = ""; }; 3913213722946E5E00EF88F4 /* FileListTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileListTableViewController.swift; sourceTree = ""; }; 3913214A22956D5700EF88F4 /* LibraryTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibraryTableViewController.swift; sourceTree = ""; }; - 39265BB123D9987500B0C4CA /* MediaEditingAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaEditingAction.swift; sourceTree = ""; }; 392CFEB62705831700631D2B /* LAContext+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "LAContext+Extension.swift"; sourceTree = ""; }; 392DDB1324CF024C009E5406 /* ImportFilesController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportFilesController.swift; sourceTree = ""; }; 3931206A2326451900E8DFBA /* Branding.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Branding.plist; path = ownCloud/Resources/Theming/Branding.plist; sourceTree = SOURCE_ROOT; }; @@ -1310,6 +1309,7 @@ DC63208621FCEE5D007EC0A8 /* ProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressView.swift; sourceTree = ""; }; DC6428CF2081406800493A01 /* CollapsibleProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleProgressBar.swift; sourceTree = ""; }; DC66A9F5279EEC3100792AC8 /* ResourceViewHost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceViewHost.swift; sourceTree = ""; }; + DC66A9F7279F467200792AC8 /* UIKeyCommand+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIKeyCommand+Extension.swift"; sourceTree = ""; }; DC66F39A239659C000CF4812 /* OCASN1.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCASN1.h; sourceTree = ""; }; DC66F39B239659C000CF4812 /* OCASN1.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCASN1.m; sourceTree = ""; }; DC66F3A323965A1400CF4812 /* NSDate+RFC3339.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSDate+RFC3339.h"; sourceTree = ""; }; @@ -1936,6 +1936,7 @@ 39607CBB2225D480007B386D /* UITableViewController+Extension.swift */, 398FD4502334CF66004B68A1 /* UIView+Extension.swift */, 4C235CED21F88C0300A989A8 /* UIViewController+Extension.swift */, + DC66A9F7279F467200792AC8 /* UIKeyCommand+Extension.swift */, ); path = "UIKit Extension"; sourceTree = ""; @@ -2160,7 +2161,6 @@ 391220932344C30F0026C290 /* CutAction.swift */, 399697F1260255B100E5AEBA /* PDFGotoPageAction.swift */, 391220922344C30F0026C290 /* ImportPasteboardAction.swift */, - 39265BB123D9987500B0C4CA /* MediaEditingAction.swift */, 39CD755123D787E400193950 /* DocumentEditingAction.swift */, 397754F22327A33500119FCB /* OpenSceneAction.swift */, 394E1FFE233E43F5009D2897 /* LinksAction.swift */, @@ -4139,7 +4139,6 @@ 4C51727F22DE04BD001BC97F /* ScheduledTaskManager.swift in Sources */, 39BC9C3023DB831F0097C52D /* DocumentEditingAction.swift in Sources */, DCB6C4D72453A6CA00C1EAE1 /* ClientAuthenticationUpdater.swift in Sources */, - 39265BB223D9987500B0C4CA /* MediaEditingAction.swift in Sources */, 399697F5260255B100E5AEBA /* PDFGotoPageAction.swift in Sources */, 3998F5D522411EDF00B66713 /* BorderedLabel.swift in Sources */, DCC3701624D4D365008B0DEB /* OCScanJobActivity+DiagnosticGenerator.swift in Sources */, @@ -4332,6 +4331,7 @@ 3912208223436EB80026C290 /* SortMethod.swift in Sources */, DCE4E43F24C19D370051722F /* UIAlertController+OCIssue.swift in Sources */, DC0A359524C0E5F900FB58FC /* UIImage+Extension.swift in Sources */, + DC66A9F8279F467200792AC8 /* UIKeyCommand+Extension.swift in Sources */, DCE4E43724C19A910051722F /* LicenseRequirements.swift in Sources */, DCE4E43A24C19ADC0051722F /* MoreViewHeader.swift in Sources */, 399EA74625E6575B00B6FF11 /* NotificationHUDViewController.swift in Sources */, @@ -4864,7 +4864,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_PREPROCESSOR_DEFINITIONS = "$(APP_BUILD_FLAGS)"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.6; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -4930,7 +4930,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_PREPROCESSOR_DEFINITIONS = "$(APP_BUILD_FLAGS)"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.6; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(APP_BUILD_FLAGS_SWIFT)"; @@ -4959,7 +4959,6 @@ GCC_WARN_UNUSED_PARAMETER = YES; INFOPLIST_FILE = ownCloud/Resources/Info.plist; INFOPLIST_PREPROCESS = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4993,7 +4992,6 @@ GCC_WARN_UNUSED_PARAMETER = YES; INFOPLIST_FILE = ownCloud/Resources/Info.plist; INFOPLIST_PREPROCESS = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5073,7 +5071,6 @@ ); INFOPLIST_FILE = ownCloudAppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5109,7 +5106,6 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = ownCloudAppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5141,7 +5137,6 @@ "$(inherited)", ); INFOPLIST_FILE = "ownCloud Intents/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5168,7 +5163,6 @@ CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = 4AP2STM4H5; INFOPLIST_FILE = "ownCloud Intents/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5199,7 +5193,6 @@ "$(inherited)", ); INFOPLIST_FILE = "ownCloud File Provider UI/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5226,7 +5219,6 @@ CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = 4AP2STM4H5; INFOPLIST_FILE = "ownCloud File Provider UI/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5259,7 +5251,6 @@ "$(inherited)", ); INFOPLIST_FILE = ownCloudScreenshotsTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5289,7 +5280,6 @@ "$(PROJECT_DIR)/Pods/EarlGrey/EarlGrey", ); INFOPLIST_FILE = ownCloudScreenshotsTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5358,7 +5348,6 @@ INFOPLIST_FILE = ownCloudAppFramework/Resources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; INTENTS_CODEGEN_LANGUAGE = Swift; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5398,7 +5387,6 @@ INFOPLIST_FILE = ownCloudAppFramework/Resources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; INTENTS_CODEGEN_LANGUAGE = Swift; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5427,7 +5415,6 @@ "$(inherited)", ); INFOPLIST_FILE = ownCloudAppTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5450,7 +5437,6 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 4AP2STM4H5; INFOPLIST_FILE = ownCloudAppTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5474,7 +5460,6 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 4AP2STM4H5; INFOPLIST_FILE = "ownCloud File Provider/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5498,7 +5483,6 @@ CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = 4AP2STM4H5; INFOPLIST_FILE = "ownCloud File Provider/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5527,7 +5511,6 @@ "$(inherited)", ); INFOPLIST_FILE = "ownCloud Share Extension/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5556,7 +5539,6 @@ CURRENT_PROJECT_VERSION = 161; DEVELOPMENT_TEAM = 4AP2STM4H5; INFOPLIST_FILE = "ownCloud Share Extension/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/ownCloud/AppDelegate.swift b/ownCloud/AppDelegate.swift index a1fe7fd4f..569b22cb2 100644 --- a/ownCloud/AppDelegate.swift +++ b/ownCloud/AppDelegate.swift @@ -59,11 +59,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { rootViewController = navigationController } else { if OCBookmarkManager.shared.bookmarks.count == 1 { - if #available(iOS 13.0, *) { - serverListTableViewController = StaticLoginSingleAccountServerListViewController(style: .insetGrouped) - } else { - serverListTableViewController = StaticLoginSingleAccountServerListViewController(style: .grouped) - } + serverListTableViewController = StaticLoginSingleAccountServerListViewController(style: .insetGrouped) } else { serverListTableViewController = ServerListTableViewController(style: .plain) } @@ -124,21 +120,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate { OCExtensionManager.shared.addExtension(ImportPasteboardAction.actionExtension) OCExtensionManager.shared.addExtension(CutAction.actionExtension) - if #available(iOS 13.0, *) { - if UIDevice.current.isIpad { - // iPad & iOS 13+ only - OCExtensionManager.shared.addExtension(DiscardSceneAction.actionExtension) - OCExtensionManager.shared.addExtension(OpenSceneAction.actionExtension) - } - - // iOS 13+ only - OCExtensionManager.shared.addExtension(ScanAction.actionExtension) - OCExtensionManager.shared.addExtension(DocumentEditingAction.actionExtension) - - //TODO: Enable in version 1.4 after testing this feature - //OCExtensionManager.shared.addExtension(MediaEditingAction.actionExtension) + if UIDevice.current.isIpad { + // iPad only + OCExtensionManager.shared.addExtension(DiscardSceneAction.actionExtension) + OCExtensionManager.shared.addExtension(OpenSceneAction.actionExtension) } + OCExtensionManager.shared.addExtension(ScanAction.actionExtension) + OCExtensionManager.shared.addExtension(DocumentEditingAction.actionExtension) + // Task extensions OCExtensionManager.shared.addExtension(BackgroundFetchUpdateTaskAction.taskExtension) OCExtensionManager.shared.addExtension(InstantMediaUploadTaskExtension.taskExtension) @@ -159,12 +149,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { UIView.setAnimationsEnabled(enableUIAnimations) } - // Set background refresh interval - guard #available(iOS 13, *) else { - UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum) - return true - } - // If the app was re-installed, make sure to wipe keychain data. Since iOS 10.3 keychain entries are not deleted if the app is deleted, but since everything else is lost, // it might lead to some inconsistency in the app state. Nevertheless we shall be careful here and consider that prior versions of the app didn't have the flag created upon // very first app launch in UserDefaults. Thus we will check few more factors: no bookmarks configured and no passcode is set diff --git a/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift b/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift index 95d6bd86e..2c7d57103 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift @@ -21,33 +21,41 @@ import MobileCoreServices import ownCloudSDK import ownCloudAppShared +class OCItemPasteboardValue : NSObject, NSSecureCoding { + static var supportsSecureCoding: Bool = true + var item : OCItem? + var bookmarkUUID : String? -struct OCItemPasteboardValue { - var item : OCItem - var bookmarkUUID : String -} + static func decode(data: Data) -> OCItemPasteboardValue? { + if let value = try? NSKeyedUnarchiver.unarchivedObject(ofClass: OCItemPasteboardValue.self, from: data) { + return value + } + + return nil + } -extension OCItemPasteboardValue { - func encode() -> Data { - let data = NSMutableData() - let archiver = NSKeyedArchiver(forWritingWith: data) - archiver.encode(item, forKey: "item") - archiver.encode(bookmarkUUID, forKey: "bookmarkUUID") - archiver.finishEncoding() - return data as Data + func encode(with coder: NSCoder) { + coder.encode(item, forKey: "item") + coder.encode(bookmarkUUID as NSString?, forKey: "bookmarkUUID") } - init?(data: Data) { - let unarchiver = NSKeyedUnarchiver(forReadingWith: data) - defer { - unarchiver.finishDecoding() - } - guard let item = unarchiver.decodeObject(forKey: "item") as? OCItem else { return nil } - guard let bookmarkUUID = unarchiver.decodeObject(forKey: "bookmarkUUID") as? String else { return nil } + init(item: OCItem?, bookmarkUUID: String?) { + super.init() self.item = item self.bookmarkUUID = bookmarkUUID } + + required init?(coder: NSCoder) { + super.init() + + item = coder.decodeObject(of: OCItem.self, forKey: "item") + bookmarkUUID = coder.decodeObject(of: NSString.self, forKey: "bookmarkUUID") as String? + } + + var encodedData : Data? { + return try? NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: true) + } } class CopyAction : Action { @@ -176,8 +184,7 @@ class CopyAction : Action { // Prepare Items for internal use itemProvider.registerDataRepresentation(forTypeIdentifier: ImportPasteboardAction.InternalPasteboardCopyKey, visibility: .ownProcess) { (completionBlock) -> Progress? in - let data = OCItemPasteboardValue(item: item, bookmarkUUID: uuid).encode() - completionBlock(data, nil) + completionBlock(OCItemPasteboardValue(item: item, bookmarkUUID: uuid).encodedData, nil) return nil } diff --git a/ownCloud/Client/Actions/Actions+Extensions/CutAction.swift b/ownCloud/Client/Actions/Actions+Extensions/CutAction.swift index 6d2286cbc..62fcadc76 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/CutAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/CutAction.swift @@ -62,7 +62,7 @@ class CutAction : Action { } itemProvider.registerDataRepresentation(forTypeIdentifier: ImportPasteboardAction.InternalPasteboardCutKey, visibility: .ownProcess) { (completionBlock) -> Progress? in - let data = OCItemPasteboardValue(item: item, bookmarkUUID: uuid).encode() + let data = OCItemPasteboardValue(item: item, bookmarkUUID: uuid).encodedData completionBlock(data, nil) return nil } @@ -92,11 +92,7 @@ class CutAction : Action { override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem { - if #available(iOS 13.0, *) { - return UIImage(systemName: "scissors") - } else { - return UIImage(named: "clipboard") - } + return UIImage(systemName: "scissors") } return nil diff --git a/ownCloud/Client/Actions/Actions+Extensions/DisplayExifMetadataAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DisplayExifMetadataAction.swift index 4e4469d1e..8d4d737ab 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DisplayExifMetadataAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DisplayExifMetadataAction.swift @@ -88,10 +88,7 @@ class DisplayExifMetadataAction : Action { if let item = self.context.items.first, let sourceURL = files.first?.url { let metadataViewController = ImageMetadataViewController(core: core, item: item, url: sourceURL) let navigationController = ThemeNavigationController(rootViewController: metadataViewController) - navigationController.modalPresentationStyle = .formSheet - if #available(iOS 13, *) { - navigationController.modalPresentationStyle = .automatic - } + navigationController.modalPresentationStyle = .automatic viewController.present(navigationController, animated: true) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/FavoriteAction.swift b/ownCloud/Client/Actions/Actions+Extensions/FavoriteAction.swift index 089f778e2..ce6fedd2d 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/FavoriteAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/FavoriteAction.swift @@ -56,11 +56,7 @@ class FavoriteAction : Action { override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { if location == .moreItem || location == .moreDetailItem || location == .contextMenuItem { - if #available(iOS 13.0, *) { - return UIImage(systemName: "star") - } - - return UIImage(named: "star") + return UIImage(systemName: "star") } return nil diff --git a/ownCloud/Client/Actions/Actions+Extensions/ImportPasteboardAction.swift b/ownCloud/Client/Actions/Actions+Extensions/ImportPasteboardAction.swift index 134e71177..9366d791b 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/ImportPasteboardAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/ImportPasteboardAction.swift @@ -74,10 +74,13 @@ class ImportPasteboardAction : Action { for item in generalPasteboard.itemProviders { // Copy Items Internally item.loadDataRepresentation(forTypeIdentifier: ImportPasteboardAction.InternalPasteboardCopyKey, completionHandler: { data, error in - if let data = data, let object = OCItemPasteboardValue(data: data) { - let item = object.item - let bookmarkUUID = object.bookmarkUUID - guard let name = item.name else { return } + if let data = data, let object = OCItemPasteboardValue.decode(data: data) { + guard let bookmarkUUID = object.bookmarkUUID, + let item = object.item, + let name = item.name + else { + return + } if core.bookmark.uuid.uuidString == bookmarkUUID { core.copy(item, to: rootItem, withName: name, options: nil, resultHandler: { (error, _, _, _) in @@ -105,20 +108,22 @@ class ImportPasteboardAction : Action { // Cut Item Internally item.loadDataRepresentation(forTypeIdentifier: ImportPasteboardAction.InternalPasteboardCutKey, completionHandler: { data, error in - if let data = data, let object = OCItemPasteboardValue(data: data) { - - let item = object.item - let bookmarkUUID = object.bookmarkUUID - guard let name = item.name else { return } + if let data = data, let object = OCItemPasteboardValue.decode(data: data) { + guard let bookmarkUUID = object.bookmarkUUID, + let item = object.item, + let name = item.name + else { + return + } if core.bookmark.uuid.uuidString == bookmarkUUID { core.move(item, to: rootItem, withName: name, options: nil) { (error, _, _, _) in - if error != nil { - self.completed(with: error) - } else { - generalPasteboard.items = [] - } - } + if error != nil { + self.completed(with: error) + } else { + generalPasteboard.items = [] + } + } } else { // Move between Accounts guard let sourceBookmark = OCBookmarkManager.shared.bookmark(forUUIDString: bookmarkUUID), let destinationItem = self.context.items.first else {return } @@ -239,11 +244,7 @@ class ImportPasteboardAction : Action { override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { if location == .moreItem || location == .moreFolder { - if #available(iOS 13.0, *) { - return UIImage(systemName: "doc.on.clipboard") - } else { - return UIImage(named: "clipboard") - } + return UIImage(systemName: "doc.on.clipboard") } return nil diff --git a/ownCloud/Client/Actions/Actions+Extensions/MediaEditingAction.swift b/ownCloud/Client/Actions/Actions+Extensions/MediaEditingAction.swift deleted file mode 100644 index 267fc35a0..000000000 --- a/ownCloud/Client/Actions/Actions+Extensions/MediaEditingAction.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// MediaEditingAction.swift -// ownCloud -// -// Created by Matthias Hühne on 23/01/2020. -// Copyright © 2020 ownCloud GmbH. All rights reserved. -// - -/* -* Copyright (C) 2020, ownCloud GmbH. -* -* This code is covered by the GNU Public License Version 3. -* -* For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ -* You should have received a copy of this license along with this program. If not, see . -* -*/ - -import ownCloudSDK -import ownCloudAppShared - -@available(iOS 13.0, *) -class MediaEditingAction : DocumentEditingAction { - override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.mediaediting") } - override class var name : String? { return "Crop or Rotate".localized } - override class var supportedMimeTypes : [String] { return ["video"] } - - override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder { - return UIImage(systemName: "crop.rotate")?.withRenderingMode(.alwaysTemplate) - } - - return nil - } -} diff --git a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift index 803eae522..886655710 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift @@ -170,11 +170,7 @@ class OpenInAction: Action { override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem { - if #available(iOS 13.0, *) { - return UIImage(systemName: "square.and.arrow.up")?.withRenderingMode(.alwaysTemplate) - } - - return UIImage(named: "open-in") + return UIImage(systemName: "square.and.arrow.up")?.withRenderingMode(.alwaysTemplate) } return nil diff --git a/ownCloud/Client/Actions/Actions+Extensions/PDFGotoPageAction.swift b/ownCloud/Client/Actions/Actions+Extensions/PDFGotoPageAction.swift index 5dbb8836a..6c73a05bd 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/PDFGotoPageAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/PDFGotoPageAction.swift @@ -48,11 +48,7 @@ class PDFGoToPageAction : Action { override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { if location == .moreDetailItem { - if #available(iOS 13.0, *) { - return UIImage(systemName: "arrow.up.doc")?.withRenderingMode(.alwaysTemplate) - } else { - return UIImage(named: "ic_pdf_go_to_page") - } + return UIImage(systemName: "arrow.up.doc")?.withRenderingMode(.alwaysTemplate) } return nil diff --git a/ownCloud/Client/Actions/Actions+Extensions/PresentationModeAction.swift b/ownCloud/Client/Actions/Actions+Extensions/PresentationModeAction.swift index ad2bf0112..762dfbd3e 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/PresentationModeAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/PresentationModeAction.swift @@ -82,11 +82,7 @@ class PresentationModeAction: Action { override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { if location == .moreDetailItem { - if #available(iOS 13.0, *) { - return UIImage(systemName: "tv") - } else { - return UIImage(named: "ic_pdf_go_to_page") - } + return UIImage(systemName: "tv") } return nil diff --git a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift index 5539b4bd6..1e34862cc 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift @@ -94,12 +94,7 @@ class RenameAction : Action { override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem { - - if #available(iOS 13.0, *) { - return UIImage(systemName: "pencil")?.withRenderingMode(.alwaysTemplate) - } else { - return UIImage(named: "folder") - } + return UIImage(systemName: "pencil")?.withRenderingMode(.alwaysTemplate) } return nil diff --git a/ownCloud/Client/Actions/Scanner/ScanAction.swift b/ownCloud/Client/Actions/Scanner/ScanAction.swift index f6c889521..ff9995c42 100644 --- a/ownCloud/Client/Actions/Scanner/ScanAction.swift +++ b/ownCloud/Client/Actions/Scanner/ScanAction.swift @@ -44,11 +44,7 @@ class ScanAction: Action, VNDocumentCameraViewControllerDelegate { return .none } - if #available(iOS 13.0, *) { - return .middle - } else { - return .none - } + return .middle } // MARK: - Action implementation @@ -76,41 +72,35 @@ class ScanAction: Action, VNDocumentCameraViewControllerDelegate { return } - if #available(iOS 13.0, *) { - Scanner.scan(on: viewController) { [weak core] (_, _, scan) in - if let pageCount = scan?.pageCount, pageCount > 0, let scannedPages = scan?.scannedPages { - var filename : String? = scan?.title + Scanner.scan(on: viewController) { [weak core] (_, _, scan) in + if let pageCount = scan?.pageCount, pageCount > 0, let scannedPages = scan?.scannedPages { + var filename : String? = scan?.title - if filename?.count == 0 { - filename = nil - } + if filename?.count == 0 { + filename = nil + } - let currentDate = DateFormatter.localizedString(from: Date(), dateStyle: .medium, timeStyle: .medium) // Use localized date - .replacingOccurrences(of: ":", with: "-") // Remove reserved character (":" not usable under Windows) - .replacingOccurrences(of: "/", with: "-") // Remove reserved character ("/" used to delimit paths on macOS, iOS, Linux, …) - .replacingOccurrences(of: "\\", with: "-") // Remove reserved character ("\" used to delimit paths on Windows) + let currentDate = DateFormatter.localizedString(from: Date(), dateStyle: .medium, timeStyle: .medium) // Use localized date + .replacingOccurrences(of: ":", with: "-") // Remove reserved character (":" not usable under Windows) + .replacingOccurrences(of: "/", with: "-") // Remove reserved character ("/" used to delimit paths on macOS, iOS, Linux, …) + .replacingOccurrences(of: "\\", with: "-") // Remove reserved character ("\" used to delimit paths on Windows) - core?.suggestUnusedNameBased(on: filename ?? "\("Scan".localized) \(currentDate).pdf", atPath: itemPath, isDirectory: true, using: .bracketed, filteredBy: nil, resultHandler: { (suggestedName, _) in - guard let suggestedName = suggestedName else { return } - - OnMainThread { - let navigationController = ThemeNavigationController(rootViewController: ScanViewController(with: scannedPages, core: core, fileName: suggestedName, targetFolder: targetFolderItem)) - viewController.present(navigationController, animated: true) - } - }) - } + core?.suggestUnusedNameBased(on: filename ?? "\("Scan".localized) \(currentDate).pdf", atPath: itemPath, isDirectory: true, using: .bracketed, filteredBy: nil, resultHandler: { (suggestedName, _) in + guard let suggestedName = suggestedName else { return } + OnMainThread { + let navigationController = ThemeNavigationController(rootViewController: ScanViewController(with: scannedPages, core: core, fileName: suggestedName, targetFolder: targetFolderItem)) + viewController.present(navigationController, animated: true) + } + }) } + } } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { if location == .folderAction { - if #available(iOS 13.0, *) { - return UIImage(systemName: "doc.text.viewfinder", withConfiguration: UIImage.SymbolConfiguration(pointSize: 26, weight: .regular)) - } -// Theme.shared.add(tvgResourceFor: "application-pdf") -// return Theme.shared.image(for: "application-pdf", size: CGSize(width: 30.0, height: 30.0))!.withRenderingMode(.alwaysTemplate) + return UIImage(systemName: "doc.text.viewfinder", withConfiguration: UIImage.SymbolConfiguration(pointSize: 26, weight: .regular)) } return nil diff --git a/ownCloud/Client/ClientActivityCell.swift b/ownCloud/Client/ClientActivityCell.swift index 59b3a1b1d..5d31fd0e4 100644 --- a/ownCloud/Client/ClientActivityCell.swift +++ b/ownCloud/Client/ClientActivityCell.swift @@ -62,9 +62,7 @@ class ClientActivityCell: ThemeTableViewCell { messageButton.setTitle("⚠️", for: .normal) messageButton.contentMode = .center - if #available(iOS 13.4, *) { - messageButton.isPointerInteractionEnabled = true - } + messageButton.isPointerInteractionEnabled = true messageButton.isHidden = true messageButton.addTarget(self, action: #selector(messageButtonTapped), for: .touchUpInside) diff --git a/ownCloud/Client/ClientRootViewController.swift b/ownCloud/Client/ClientRootViewController.swift index 3275d7a6c..4624cbc30 100644 --- a/ownCloud/Client/ClientRootViewController.swift +++ b/ownCloud/Client/ClientRootViewController.swift @@ -323,10 +323,9 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { if viewController == emptyViewController { closeClient() - if #available(iOS 13.0, *) { - // Prevent re-opening of items on next launch in case user has returned to the bookmark list - view.window?.windowScene?.userActivity = nil - } + + // Prevent re-opening of items on next launch in case user has returned to the bookmark list + view.window?.windowScene?.userActivity = nil } else { updateProgressBarFor(viewController: viewController, animate: animated) } @@ -380,10 +379,9 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa if viewController == emptyViewController { OnMainThread { self?.closeClient() - if #available(iOS 13.0, *) { - // Prevent re-opening of items on next launch in case user has returned to the bookmark list - self?.view.window?.windowScene?.userActivity = nil - } + + // Prevent re-opening of items on next launch in case user has returned to the bookmark list + self?.view.window?.windowScene?.userActivity = nil } } diff --git a/ownCloud/Client/ExternalBrowserBusyHandler.swift b/ownCloud/Client/ExternalBrowserBusyHandler.swift index 62d0fe238..ce6fee234 100644 --- a/ownCloud/Client/ExternalBrowserBusyHandler.swift +++ b/ownCloud/Client/ExternalBrowserBusyHandler.swift @@ -31,9 +31,7 @@ class ExternalBrowserBusyHandler: UIViewController, Themeable { } viewController.cancelHandler = cancelHandler - if #available(iOS 13.0, *) { - viewController.isModalInPresentation = true - } + viewController.isModalInPresentation = true hostViewController?.present(viewController, animated: true, completion: nil) diff --git a/ownCloud/Client/FileList Extensions/FileListTableViewController+OpenItemTableViewController.swift b/ownCloud/Client/FileList Extensions/FileListTableViewController+OpenItemTableViewController.swift index a0fb1fd4f..82cda00d6 100644 --- a/ownCloud/Client/FileList Extensions/FileListTableViewController+OpenItemTableViewController.swift +++ b/ownCloud/Client/FileList Extensions/FileListTableViewController+OpenItemTableViewController.swift @@ -23,11 +23,9 @@ import ownCloudAppShared extension FileListTableViewController : OpenItemHandling { @discardableResult public func open(item: OCItem, animated: Bool, pushViewController: Bool) -> UIViewController? { if let core = self.core { - if #available(iOS 13.0, *) { - if let bookmarkContainer = self.tabBarController as? BookmarkContainer { - let activity = OpenItemUserActivity(detailItem: item, detailBookmark: bookmarkContainer.bookmark) - view.window?.windowScene?.userActivity = activity.openItemUserActivity - } + if let bookmarkContainer = self.tabBarController as? BookmarkContainer { + let activity = OpenItemUserActivity(detailItem: item, detailBookmark: bookmarkContainer.bookmark) + view.window?.windowScene?.userActivity = activity.openItemUserActivity } switch item.type { diff --git a/ownCloud/Client/FileList Extensions/QueryFileListTableViewController+Multiselect.swift b/ownCloud/Client/FileList Extensions/QueryFileListTableViewController+Multiselect.swift index 918ec6507..a6735ddf4 100644 --- a/ownCloud/Client/FileList Extensions/QueryFileListTableViewController+Multiselect.swift +++ b/ownCloud/Client/FileList Extensions/QueryFileListTableViewController+Multiselect.swift @@ -43,11 +43,7 @@ extension QueryFileListTableViewController : MultiSelectSupport { copyMultipleBarButtonItem?.accessibilityLabel = "Copy".localized copyMultipleBarButtonItem?.isEnabled = false - var cutImage = UIImage(named: "clipboard") - if #available(iOS 13.0, *) { - cutImage = UIImage(systemName: "scissors") - } - cutMultipleBarButtonItem = UIBarButtonItem(image: cutImage, target: self as AnyObject, action: #selector(actOnMultipleItems), dropTarget: self, actionIdentifier: CutAction.identifier!) + cutMultipleBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "scissors"), target: self as AnyObject, action: #selector(actOnMultipleItems), dropTarget: self, actionIdentifier: CutAction.identifier!) cutMultipleBarButtonItem?.accessibilityLabel = "Cut".localized cutMultipleBarButtonItem?.isEnabled = false @@ -118,9 +114,7 @@ extension QueryFileListTableViewController : MultiSelectSupport { removeToolbar() sortBar?.showSelectButton = true - if #available(iOS 13, *) { - self.tableView.overrideUserInterfaceStyle = .unspecified - } + self.tableView.overrideUserInterfaceStyle = .unspecified self.navigationItem.rightBarButtonItems = self.regularRightBarButtons self.navigationItem.leftBarButtonItems = self.regularLeftBarButtons @@ -178,9 +172,7 @@ extension QueryFileListTableViewController : MultiSelectSupport { @objc public func enterMultiselection() { - if #available(iOS 13, *) { - self.tableView.overrideUserInterfaceStyle = Theme.shared.activeCollection.interfaceStyle.userInterfaceStyle - } + self.tableView.overrideUserInterfaceStyle = Theme.shared.activeCollection.interfaceStyle.userInterfaceStyle if !self.tableView.isEditing { self.regularLeftBarButtons = self.navigationItem.leftBarButtonItems diff --git a/ownCloud/Client/PhotoAlbumTableViewCell.swift b/ownCloud/Client/PhotoAlbumTableViewCell.swift index 2e45e0a89..60b9aae9a 100644 --- a/ownCloud/Client/PhotoAlbumTableViewCell.swift +++ b/ownCloud/Client/PhotoAlbumTableViewCell.swift @@ -103,9 +103,7 @@ class PhotoAlbumTableViewCell: ThemeTableViewCell { self.titleLabel.setContentCompressionResistancePriority(UILayoutPriority.defaultLow, for: NSLayoutConstraint.Axis.horizontal) self.countLabel.setContentCompressionResistancePriority(UILayoutPriority.defaultHigh, for: NSLayoutConstraint.Axis.horizontal) - if #available(iOS 13.4, *) { - PointerEffect.install(on: self, effectStyle: .hover) - } + PointerEffect.install(on: self, effectStyle: .hover) } // MARK: - Theme support diff --git a/ownCloud/Client/PhotoSelectionViewCell.swift b/ownCloud/Client/PhotoSelectionViewCell.swift index a0c02a504..2033fe732 100644 --- a/ownCloud/Client/PhotoSelectionViewCell.swift +++ b/ownCloud/Client/PhotoSelectionViewCell.swift @@ -101,8 +101,6 @@ class PhotoSelectionViewCell: UICollectionViewCell { checkmarkBadgeImageView.rightAnchor.constraint(equalTo: self.imageView.rightAnchor, constant: -badgeMargin).isActive = true checkmarkBadgeImageView.bottomAnchor.constraint(equalTo: self.imageView.bottomAnchor, constant: -badgeMargin).isActive = true - if #available(iOS 13.4, *) { - PointerEffect.install(on: self, effectStyle: .hoverScaled) - } + PointerEffect.install(on: self, effectStyle: .hoverScaled) } } diff --git a/ownCloud/Client/Viewer/DisplayExtension.swift b/ownCloud/Client/Viewer/DisplayExtension.swift index e0de7f106..976076007 100644 --- a/ownCloud/Client/Viewer/DisplayExtension.swift +++ b/ownCloud/Client/Viewer/DisplayExtension.swift @@ -31,7 +31,7 @@ protocol DisplayExtension where Self: DisplayViewController { static var customMatcher: OCExtensionCustomContextMatcher? {get} } -extension DisplayExtension where Self: DisplayViewController { +extension DisplayExtension { static var displayExtension: OCExtension { let rawIdentifier: OCExtensionIdentifier = OCExtensionIdentifier(rawValue: displayExtensionIdentifier) var locationIdentifiers: [OCExtensionLocationIdentifier] = [] diff --git a/ownCloud/Client/Viewer/DisplayViewController.swift b/ownCloud/Client/Viewer/DisplayViewController.swift index 0a08ea5d2..805d44847 100644 --- a/ownCloud/Client/Viewer/DisplayViewController.swift +++ b/ownCloud/Client/Viewer/DisplayViewController.swift @@ -183,7 +183,7 @@ class DisplayViewController: UIViewController, Themeable, OCQueryDelegate { private var metadataInfoLabel = UILabel() private var showPreviewButton = ThemeButton(type: .custom) private var infoLabel = UILabel() - private var connectionActivityView = UIActivityIndicatorView(style: .white) + private var connectionActivityView = UIActivityIndicatorView(style: .medium) // MARK: - Editing delegate @@ -219,10 +219,8 @@ class DisplayViewController: UIViewController, Themeable, OCQueryDelegate { override func loadView() { super.loadView() - if #available(iOS 13.4, *) { - PointerEffect.install(on: cancelButton, effectStyle: .highlight) - PointerEffect.install(on: showPreviewButton, effectStyle: .highlight) - } + PointerEffect.install(on: cancelButton, effectStyle: .highlight) + PointerEffect.install(on: showPreviewButton, effectStyle: .highlight) iconImageView.translatesAutoresizingMaskIntoConstraints = false diff --git a/ownCloud/Client/Viewer/Image/ImageDisplayViewController.swift b/ownCloud/Client/Viewer/Image/ImageDisplayViewController.swift index c1ff4c29c..eb8fe8a6c 100644 --- a/ownCloud/Client/Viewer/Image/ImageDisplayViewController.swift +++ b/ownCloud/Client/Viewer/Image/ImageDisplayViewController.swift @@ -31,7 +31,7 @@ class ImageDisplayViewController : DisplayViewController { var scrollView: ImageScrollView? var activityIndicatorView: UIActivityIndicatorView = { - let activityIndicator = UIActivityIndicatorView(style: UIActivityIndicatorView.Style.white) + let activityIndicator = UIActivityIndicatorView(style: .medium) activityIndicator.translatesAutoresizingMaskIntoConstraints = false return activityIndicator }() diff --git a/ownCloud/Client/Viewer/PDF/PDFSearchResultsView.swift b/ownCloud/Client/Viewer/PDF/PDFSearchResultsView.swift index ed73db85e..bab58ff5c 100644 --- a/ownCloud/Client/Viewer/PDF/PDFSearchResultsView.swift +++ b/ownCloud/Client/Viewer/PDF/PDFSearchResultsView.swift @@ -88,15 +88,9 @@ class PDFSearchResultsView : UIView { backButton.addTarget(self, action: #selector(back), for: .touchUpInside) forwardButton.addTarget(self, action: #selector(forward), for: .touchUpInside) - if #available(iOS 13, *) { - closeButtton.setImage(UIImage(systemName: "xmark")?.tinted(with: .white), for: .normal) - backButton.setImage(UIImage(systemName: "chevron.left")?.tinted(with: .white), for: .normal) - forwardButton.setImage(UIImage(systemName: "chevron.right")?.tinted(with: .white), for: .normal) - } else { - closeButtton.setImage(UIImage(named: "xmark")?.tinted(with: .white), for: .normal) - backButton.setImage(UIImage(named: "chevron.left")?.tinted(with: .white), for: .normal) - forwardButton.setImage(UIImage(named: "chevron.right")?.tinted(with: .white), for: .normal) - } + closeButtton.setImage(UIImage(systemName: "xmark")?.tinted(with: .white), for: .normal) + backButton.setImage(UIImage(systemName: "chevron.left")?.tinted(with: .white), for: .normal) + forwardButton.setImage(UIImage(systemName: "chevron.right")?.tinted(with: .white), for: .normal) searchTermButton.titleLabel?.textColor = .white searchTermButton.titleLabel?.font = UIFont.preferredFont(forTextStyle: .footnote) diff --git a/ownCloud/Client/Viewer/PDF/PDFThumbnailCollectionViewCell.swift b/ownCloud/Client/Viewer/PDF/PDFThumbnailCollectionViewCell.swift index 5e123117c..929136520 100644 --- a/ownCloud/Client/Viewer/PDF/PDFThumbnailCollectionViewCell.swift +++ b/ownCloud/Client/Viewer/PDF/PDFThumbnailCollectionViewCell.swift @@ -65,9 +65,7 @@ class PDFThumbnailCollectionViewCell: UICollectionViewCell { pageLabel!.leftAnchor.constraint(equalTo: imageView!.leftAnchor).isActive = true pageLabel!.heightAnchor.constraint(equalTo: imageView!.heightAnchor, multiplier:pageLabelHeightMultiplier).isActive = true - if #available(iOS 13.4, *) { - PointerEffect.install(on: self, effectStyle: .hoverScaled) - } + PointerEffect.install(on: self, effectStyle: .hoverScaled) } override func prepareForReuse() { diff --git a/ownCloud/Client/Viewer/PDF/PDFTocTableViewController.swift b/ownCloud/Client/Viewer/PDF/PDFTocTableViewController.swift index ac57a008b..79a0620fb 100644 --- a/ownCloud/Client/Viewer/PDF/PDFTocTableViewController.swift +++ b/ownCloud/Client/Viewer/PDF/PDFTocTableViewController.swift @@ -22,7 +22,7 @@ import ownCloudAppShared class PDFTocTableViewController: UITableViewController, Themeable { - let activityIndicatorView: UIActivityIndicatorView = UIActivityIndicatorView(style: UIActivityIndicatorView.Style.whiteLarge) + let activityIndicatorView: UIActivityIndicatorView = UIActivityIndicatorView(style: .large) fileprivate let tocTableViewCellHeight: CGFloat = 40.0 fileprivate var enableTocBuilding = true diff --git a/ownCloud/Client/Viewer/PDF/PDFViewerViewController.swift b/ownCloud/Client/Viewer/PDF/PDFViewerViewController.swift index 0cc744f6a..7e3b0bf12 100644 --- a/ownCloud/Client/Viewer/PDF/PDFViewerViewController.swift +++ b/ownCloud/Client/Viewer/PDF/PDFViewerViewController.swift @@ -187,15 +187,9 @@ class PDFViewerViewController: DisplayViewController, DisplayExtension, UIPopove self.view.addSubview(containerView) - if #available(iOS 13, *) { - self.view.backgroundColor = self.pdfView.backgroundColor - thumbnailView.backgroundColor = self.pdfView.backgroundColor - pageCountContainerView.backgroundColor = self.pdfView.backgroundColor - } else { - self.view.backgroundColor = .gray - thumbnailView.backgroundColor = .gray - pageCountContainerView.backgroundColor = .gray - } + self.view.backgroundColor = self.pdfView.backgroundColor + thumbnailView.backgroundColor = self.pdfView.backgroundColor + pageCountContainerView.backgroundColor = self.pdfView.backgroundColor setupConstraints() @@ -230,13 +224,10 @@ class PDFViewerViewController: DisplayViewController, DisplayExtension, UIPopove } } - if #available(iOS 13, *) { - let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.toggleFullscreen(_:))) - tapRecognizer.numberOfTapsRequired = 2 - pdfView.addGestureRecognizer(tapRecognizer) - supportsFullScreenMode = true - } - //pdfView.isUserInteractionEnabled = true + let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.toggleFullscreen(_:))) + tapRecognizer.numberOfTapsRequired = 2 + pdfView.addGestureRecognizer(tapRecognizer) + supportsFullScreenMode = true } @objc func toggleFullscreen(_ sender: UITapGestureRecognizer) { @@ -251,27 +242,21 @@ class PDFViewerViewController: DisplayViewController, DisplayExtension, UIPopove super.viewDidLayoutSubviews() pdfView.scaleFactor = pdfView.scaleFactorForSizeToFit pdfView.autoScales = true - if #available(iOS 13, *) { - self.calculateThumbnailSize() - } + self.calculateThumbnailSize() } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) - // Crashes on pre-iOS 13 - if #available(iOS 13, *) { - coordinator.animate(alongsideTransition: nil) { (_) in - self.calculateThumbnailSize() - } + + coordinator.animate(alongsideTransition: nil) { (_) in + self.calculateThumbnailSize() } } override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) { - if #available(iOS 13, *) { - coordinator.animate(alongsideTransition: nil) { (_) in - self.setThumbnailPosition() - self.calculateThumbnailSize() - } + coordinator.animate(alongsideTransition: nil) { (_) in + self.setThumbnailPosition() + self.calculateThumbnailSize() } } @@ -306,8 +291,7 @@ class PDFViewerViewController: DisplayViewController, DisplayExtension, UIPopove if let pageLabel = alertController.textFields?.first?.text { self.selectPage(with: pageLabel) } - self.view.endEditing(true) - + self.view.endEditing(true) })) self.present(alertController, animated: true) diff --git a/ownCloud/Client/Viewer/QuickLook/PreviewViewController.swift b/ownCloud/Client/Viewer/QuickLook/PreviewViewController.swift index 899f5bde6..f921329db 100644 --- a/ownCloud/Client/Viewer/QuickLook/PreviewViewController.swift +++ b/ownCloud/Client/Viewer/QuickLook/PreviewViewController.swift @@ -76,9 +76,7 @@ class PreviewViewController : DisplayViewController, QLPreviewControllerDataSour qlPreviewController!.view.addSubview(overlayView!) qlPreviewController!.didMove(toParent: self) - if #available(iOS 13.0, *) { - qlPreviewController?.overrideUserInterfaceStyle = Theme.shared.activeCollection.interfaceStyle.userInterfaceStyle - } + qlPreviewController?.overrideUserInterfaceStyle = Theme.shared.activeCollection.interfaceStyle.userInterfaceStyle qlPreviewController!.view.translatesAutoresizingMaskIntoConstraints = false @@ -139,9 +137,7 @@ class PreviewViewController : DisplayViewController, QLPreviewControllerDataSour override func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { super.applyThemeCollection(theme: theme, collection: collection, event: event) - if #available(iOS 13, *) { - qlPreviewController?.overrideUserInterfaceStyle = collection.interfaceStyle.userInterfaceStyle - } + qlPreviewController?.overrideUserInterfaceStyle = collection.interfaceStyle.userInterfaceStyle } } diff --git a/ownCloud/Diagnostic/DiagnosticViewController.swift b/ownCloud/Diagnostic/DiagnosticViewController.swift index 19dae7a88..4642f0870 100644 --- a/ownCloud/Diagnostic/DiagnosticViewController.swift +++ b/ownCloud/Diagnostic/DiagnosticViewController.swift @@ -51,11 +51,7 @@ class DiagnosticViewController: StaticTableViewController { self.navigationItem.title = node.label - var shareImage = UIImage(named: "open-in") - if #available(iOS 13.0, *) { - shareImage = UIImage(systemName: "square.and.arrow.up") - } - self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: shareImage, style: .plain, target: self, action: #selector(self.shareAsMarkdown(_:))) + self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "square.and.arrow.up"), style: .plain, target: self, action: #selector(self.shareAsMarkdown(_:))) self.navigationItem.rightBarButtonItem?.accessibilityLabel = "Share Diagnostics".localized self.nodes = node.children diff --git a/ownCloud/Issues/IssuesCardViewController.swift b/ownCloud/Issues/IssuesCardViewController.swift index 68b7a9ded..5dabe7554 100644 --- a/ownCloud/Issues/IssuesCardViewController.swift +++ b/ownCloud/Issues/IssuesCardViewController.swift @@ -212,7 +212,7 @@ class IssuesCardViewController: StaticTableViewController { ]) }, accessoryType: (issue.type == .certificate) ? .disclosureIndicator : .none) - if #available(iOS 13.0, *), row.cell?.accessoryType == .disclosureIndicator { + if row.cell?.accessoryType == .disclosureIndicator { // On iOS 13+, chevrons created via .accessoryType are not using the .tintColor anymore let chevronImageView = UIImageView(image: UIImage(systemName: "chevron.right")) (row.cell as? ThemeTableViewCell)?.accessoryView = chevronImageView diff --git a/ownCloud/Key Commands/KeyCommands.swift b/ownCloud/Key Commands/KeyCommands.swift index 0c4cb17b5..4a1e6d71b 100644 --- a/ownCloud/Key Commands/KeyCommands.swift +++ b/ownCloud/Key Commands/KeyCommands.swift @@ -23,23 +23,23 @@ import CoreMedia extension ServerListTableViewController { override var keyCommands: [UIKeyCommand]? { - let nextObjectCommand = UIKeyCommand(input: UIKeyCommand.inputDownArrow, modifierFlags: [], action: #selector(selectNext), discoverabilityTitle: "Select Next".localized) - let previousObjectCommand = UIKeyCommand(input: UIKeyCommand.inputUpArrow, modifierFlags: [], action: #selector(selectPrevious), discoverabilityTitle: "Select Previous".localized) - let selectObjectCommand = UIKeyCommand(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(selectCurrent), discoverabilityTitle: "Open Selected".localized) - let addAccountCommand = UIKeyCommand(input: "+", modifierFlags: [.command], action: #selector(addBookmark), discoverabilityTitle: "Add account".localized.localized) - let openSettingsCommand = UIKeyCommand(input: ",", modifierFlags: [.command], action: #selector(settings), discoverabilityTitle: "Settings".localized.localized) + let nextObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputDownArrow, modifierFlags: [], action: #selector(selectNext), discoverabilityTitle: "Select Next".localized) + let previousObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputUpArrow, modifierFlags: [], action: #selector(selectPrevious), discoverabilityTitle: "Select Previous".localized) + let selectObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(selectCurrent), discoverabilityTitle: "Open Selected".localized) + let addAccountCommand = UIKeyCommand.ported(input: "+", modifierFlags: [.command], action: #selector(addBookmark), discoverabilityTitle: "Add account".localized.localized) + let openSettingsCommand = UIKeyCommand.ported(input: ",", modifierFlags: [.command], action: #selector(settings), discoverabilityTitle: "Settings".localized.localized) - let editSettingsCommand = UIKeyCommand(input: ",", modifierFlags: [.command, .shift], action: #selector(editBookmark), discoverabilityTitle: "Edit".localized) - let manageSettingsCommand = UIKeyCommand(input: "M", modifierFlags: [.command, .shift], action: #selector(manageBookmark), discoverabilityTitle: "Manage".localized) - let deleteSettingsCommand = UIKeyCommand(input: "\u{08}", modifierFlags: [.command, .shift], action: #selector(deleteBookmarkCommand), discoverabilityTitle: "Delete".localized) + let editSettingsCommand = UIKeyCommand.ported(input: ",", modifierFlags: [.command, .shift], action: #selector(editBookmark), discoverabilityTitle: "Edit".localized) + let manageSettingsCommand = UIKeyCommand.ported(input: "M", modifierFlags: [.command, .shift], action: #selector(manageBookmark), discoverabilityTitle: "Manage".localized) + let deleteSettingsCommand = UIKeyCommand.ported(input: "\u{08}", modifierFlags: [.command, .shift], action: #selector(deleteBookmarkCommand), discoverabilityTitle: "Delete".localized) var shortcuts = [UIKeyCommand]() if let selectedRow = self.tableView?.indexPathForSelectedRow?.row { shortcuts.append(editSettingsCommand) shortcuts.append(manageSettingsCommand) shortcuts.append(deleteSettingsCommand) - if #available(iOS 13.0, *), UIDevice.current.isIpad { - let openWindowCommand = UIKeyCommand(input: "W", modifierFlags: [.command, .shift], action: #selector(openSelectedBookmarkInWindow), discoverabilityTitle: "Open in new Window".localized) + if UIDevice.current.isIpad { + let openWindowCommand = UIKeyCommand.ported(input: "W", modifierFlags: [.command, .shift], action: #selector(openSelectedBookmarkInWindow), discoverabilityTitle: "Open in new Window".localized) shortcuts.append(openWindowCommand) } @@ -56,7 +56,7 @@ extension ServerListTableViewController { for (index, bookmark) in OCBookmarkManager.shared.bookmarks.enumerated() { let accountIndex = String(index + 1) - let selectAccountCommand = UIKeyCommand(input: accountIndex, modifierFlags: [.command, .shift], action: #selector(selectBookmark), discoverabilityTitle: bookmark.shortName) + let selectAccountCommand = UIKeyCommand.ported(input: accountIndex, modifierFlags: [.command, .shift], action: #selector(selectBookmark), discoverabilityTitle: bookmark.shortName) shortcuts.append(selectAccountCommand) } @@ -115,9 +115,9 @@ extension ServerListTableViewController { extension StaticLoginSingleAccountServerListViewController { override var keyCommands: [UIKeyCommand]? { - let nextObjectCommand = UIKeyCommand(input: UIKeyCommand.inputDownArrow, modifierFlags: [], action: #selector(selectNext), discoverabilityTitle: "Select Next".localized) - let previousObjectCommand = UIKeyCommand(input: UIKeyCommand.inputUpArrow, modifierFlags: [], action: #selector(selectPrevious), discoverabilityTitle: "Select Previous".localized) - let selectObjectCommand = UIKeyCommand(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(selectCurrent), discoverabilityTitle: "Open Selected".localized) + let nextObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputDownArrow, modifierFlags: [], action: #selector(selectNext), discoverabilityTitle: "Select Next".localized) + let previousObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputUpArrow, modifierFlags: [], action: #selector(selectPrevious), discoverabilityTitle: "Select Previous".localized) + let selectObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(selectCurrent), discoverabilityTitle: "Open Selected".localized) var shortcuts = [UIKeyCommand]() if let selectedIndexPath = self.tableView.indexPathForSelectedRow { @@ -144,10 +144,10 @@ extension BookmarkViewController { if let superKeyCommands = super.keyCommands { shortcuts.append(contentsOf: superKeyCommands) } - let cancelCommand = UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismiss), discoverabilityTitle: "Cancel".localized) + let cancelCommand = UIKeyCommand.ported(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismiss), discoverabilityTitle: "Cancel".localized) shortcuts.append(cancelCommand) - let continueCommand = UIKeyCommand(input: "C", modifierFlags: [.command], action: #selector(handleContinue), discoverabilityTitle: "Continue".localized) + let continueCommand = UIKeyCommand.ported(input: "C", modifierFlags: [.command], action: #selector(handleContinue), discoverabilityTitle: "Continue".localized) shortcuts.append(continueCommand) return shortcuts @@ -167,7 +167,7 @@ extension UIAlertController { var counter = 1 for action in actions { if let title = action.title { - let command = UIKeyCommand(input: String(counter), modifierFlags: [.command], action: #selector(tapActionButton), discoverabilityTitle: title) + let command = UIKeyCommand.ported(input: String(counter), modifierFlags: [.command], action: #selector(tapActionButton), discoverabilityTitle: title) shortcuts.append(command) counter += 1 } @@ -201,7 +201,7 @@ extension BookmarkInfoViewController { shortcuts.append(contentsOf: superKeyCommands) } - let doneCommand = UIKeyCommand(input: "D", modifierFlags: [.command], action: #selector(userActionDone), discoverabilityTitle: "Done".localized) + let doneCommand = UIKeyCommand.ported(input: "D", modifierFlags: [.command], action: #selector(userActionDone), discoverabilityTitle: "Done".localized) shortcuts.append(doneCommand) return shortcuts @@ -218,7 +218,7 @@ extension ThemeNavigationController { var shortcuts = [UIKeyCommand]() if self.viewControllers.count > 1, !(self.visibleViewController?.isKind(of: UIAlertController.self) ?? true) { - let backCommand = UIKeyCommand(input: UIKeyCommand.inputUpArrow, modifierFlags: [.command], action: #selector(popViewControllerAnimated), discoverabilityTitle: "Back".localized) + let backCommand = UIKeyCommand.ported(input: UIKeyCommand.inputUpArrow, modifierFlags: [.command], action: #selector(popViewControllerAnimated), discoverabilityTitle: "Back".localized) shortcuts.append(backCommand) } @@ -239,11 +239,11 @@ extension NamingViewController { var shortcuts = [UIKeyCommand]() if let leftItem = self.navigationItem.leftBarButtonItem, let action = leftItem.action { - let dismissCommand = UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: action, discoverabilityTitle: "Cancel".localized) + let dismissCommand = UIKeyCommand.ported(input: UIKeyCommand.inputEscape, modifierFlags: [], action: action, discoverabilityTitle: "Cancel".localized) shortcuts.append(dismissCommand) } if let rightItem = self.navigationItem.rightBarButtonItem, let action = rightItem.action { - let doneCommand = UIKeyCommand(input: "D", modifierFlags: [.command], action: action, discoverabilityTitle: "Done".localized) + let doneCommand = UIKeyCommand.ported(input: "D", modifierFlags: [.command], action: action, discoverabilityTitle: "Done".localized) shortcuts.append(doneCommand) } @@ -260,10 +260,10 @@ extension PDFSearchViewController { override var keyCommands: [UIKeyCommand]? { var shortcuts = [UIKeyCommand]() - let nextObjectCommand = UIKeyCommand(input: UIKeyCommand.inputDownArrow, modifierFlags: [], action: #selector(selectNext), discoverabilityTitle: "Select Next".localized) - let previousObjectCommand = UIKeyCommand(input: UIKeyCommand.inputUpArrow, modifierFlags: [], action: #selector(selectPrevious), discoverabilityTitle: "Select Previous".localized) - let selectObjectCommand = UIKeyCommand(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(selectCurrent), discoverabilityTitle: "Open Selected".localized) - let cancelCommand = UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismiss), discoverabilityTitle: "Cancel".localized) + let nextObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputDownArrow, modifierFlags: [], action: #selector(selectNext), discoverabilityTitle: "Select Next".localized) + let previousObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputUpArrow, modifierFlags: [], action: #selector(selectPrevious), discoverabilityTitle: "Select Previous".localized) + let selectObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(selectCurrent), discoverabilityTitle: "Open Selected".localized) + let cancelCommand = UIKeyCommand.ported(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismiss), discoverabilityTitle: "Cancel".localized) if self.tableView.numberOfRows(inSection: 0) > 0 { shortcuts.append(nextObjectCommand) @@ -299,13 +299,13 @@ extension ClientRootViewController { } if let navigationController = self.selectedViewController as? ThemeNavigationController, navigationController.visibleViewController?.navigationItem.searchController?.isActive ?? false { - let cancelCommand = UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismissSearch), discoverabilityTitle: "Cancel".localized) + let cancelCommand = UIKeyCommand.ported(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismissSearch), discoverabilityTitle: "Cancel".localized) shortcuts.append(cancelCommand) if let visibleViewController = navigationController.visibleViewController, let keyCommands = visibleViewController.keyCommands { let newKeyCommands = keyCommands.map { (keyCommand) -> UIKeyCommand in if let input = keyCommand.input, let discoverabilityTitle = keyCommand.discoverabilityTitle { - return UIKeyCommand(input: input, modifierFlags: keyCommand.modifierFlags, action: #selector(performActionOnVisibleViewController), discoverabilityTitle: discoverabilityTitle) + return UIKeyCommand.ported(input: input, modifierFlags: keyCommand.modifierFlags, action: #selector(performActionOnVisibleViewController), discoverabilityTitle: discoverabilityTitle) } return UIKeyCommand(input: keyCommand.input!, modifierFlags: keyCommand.modifierFlags, action: #selector(performActionOnVisibleViewController)) @@ -318,7 +318,7 @@ extension ClientRootViewController { if let navigationController = self.selectedViewController as? ThemeNavigationController, !((navigationController.visibleViewController as? UIAlertController) != nil) { let keyCommands = self.tabBar.items?.enumerated().map { (index, item) -> UIKeyCommand in let tabIndex = String(index + 1) - return UIKeyCommand(input: tabIndex, modifierFlags: .command, action:#selector(selectTab), discoverabilityTitle: item.title ?? String(format: "Tab %@".localized, tabIndex)) + return UIKeyCommand.ported(input: tabIndex, modifierFlags: .command, action:#selector(selectTab), discoverabilityTitle: item.title ?? String(format: "Tab %@".localized, tabIndex)) } if let keyCommands = keyCommands, self.presentedViewController == nil { shortcuts.append(contentsOf: keyCommands) @@ -326,7 +326,7 @@ extension ClientRootViewController { } if let availableStyles = ThemeStyle.availableStyles, availableStyles.count > 1 { - let switchThemeCommand = UIKeyCommand(input: "T", modifierFlags: [.alternate], action: #selector(switchTheme), discoverabilityTitle: "Switch Theme Style".localized) + let switchThemeCommand = UIKeyCommand.ported(input: "T", modifierFlags: [.alternate], action: #selector(switchTheme), discoverabilityTitle: "Switch Theme Style".localized) shortcuts.append(switchThemeCommand) } @@ -459,8 +459,8 @@ extension GroupSharingTableViewController { if let superKeyCommands = super.keyCommands { shortcuts.append(contentsOf: superKeyCommands) } - let searchCommand = UIKeyCommand(input: "F", modifierFlags: [.command], action: #selector(enableSearch), discoverabilityTitle: "Search".localized) - let doneCommand = UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismiss), discoverabilityTitle: "Done".localized) + let searchCommand = UIKeyCommand.ported(input: "F", modifierFlags: [.command], action: #selector(enableSearch), discoverabilityTitle: "Search".localized) + let doneCommand = UIKeyCommand.ported(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismiss), discoverabilityTitle: "Done".localized) shortcuts.append(searchCommand) shortcuts.append(doneCommand) @@ -483,13 +483,13 @@ extension GroupSharingEditTableViewController { if let superKeyCommands = super.keyCommands { shortcuts.append(contentsOf: superKeyCommands) } - let dismissCommand = UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismiss), discoverabilityTitle: "Cancel".localized) - let createCommand = UIKeyCommand(input: "S", modifierFlags: [.command], action: #selector(createShareAndDismiss), discoverabilityTitle: "Save".localized) + let dismissCommand = UIKeyCommand.ported(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismiss), discoverabilityTitle: "Cancel".localized) + let createCommand = UIKeyCommand.ported(input: "S", modifierFlags: [.command], action: #selector(createShareAndDismiss), discoverabilityTitle: "Save".localized) shortcuts.append(dismissCommand) shortcuts.append(createCommand) if createShare { - let showInfoObjectCommand = UIKeyCommand(input: "H", modifierFlags: [.command, .alternate], action: #selector(showInfoSubtitles), discoverabilityTitle: "Help".localized) + let showInfoObjectCommand = UIKeyCommand.ported(input: "H", modifierFlags: [.command, .alternate], action: #selector(showInfoSubtitles), discoverabilityTitle: "Help".localized) shortcuts.append(showInfoObjectCommand) } @@ -507,7 +507,7 @@ extension PublicLinkTableViewController { if let superKeyCommands = super.keyCommands { shortcuts.append(contentsOf: superKeyCommands) } - let doneCommand = UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismiss), discoverabilityTitle: "Done".localized) + let doneCommand = UIKeyCommand.ported(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismiss), discoverabilityTitle: "Done".localized) shortcuts.append(doneCommand) return shortcuts @@ -524,16 +524,16 @@ extension PublicLinkEditTableViewController { if let superKeyCommands = super.keyCommands { shortcuts.append(contentsOf: superKeyCommands) } - let dismissCommand = UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismiss), discoverabilityTitle: "Cancel".localized) - let createCommand = UIKeyCommand(input: "S", modifierFlags: [.command], action: #selector(createPublicLink), discoverabilityTitle: "Create".localized) + let dismissCommand = UIKeyCommand.ported(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismiss), discoverabilityTitle: "Cancel".localized) + let createCommand = UIKeyCommand.ported(input: "S", modifierFlags: [.command], action: #selector(createPublicLink), discoverabilityTitle: "Create".localized) shortcuts.append(dismissCommand) shortcuts.append(createCommand) if createLink { - let showInfoObjectCommand = UIKeyCommand(input: "H", modifierFlags: [.command, .alternate], action: #selector(showInfoSubtitles), discoverabilityTitle: "Help".localized) + let showInfoObjectCommand = UIKeyCommand.ported(input: "H", modifierFlags: [.command, .alternate], action: #selector(showInfoSubtitles), discoverabilityTitle: "Help".localized) shortcuts.append(showInfoObjectCommand) } else { - let shareObjectCommand = UIKeyCommand(input: "S", modifierFlags: [.command], action: #selector(shareLinkURL), discoverabilityTitle: "Share".localized) + let shareObjectCommand = UIKeyCommand.ported(input: "S", modifierFlags: [.command], action: #selector(shareLinkURL), discoverabilityTitle: "Share".localized) shortcuts.append(shareObjectCommand) } @@ -548,16 +548,16 @@ extension PublicLinkEditTableViewController { extension StaticTableViewController { open override var keyCommands: [UIKeyCommand]? { - let nextObjectCommand = UIKeyCommand(input: UIKeyCommand.inputDownArrow, modifierFlags: [], action: #selector(selectNext), discoverabilityTitle: "Select Next".localized) - let previousObjectCommand = UIKeyCommand(input: UIKeyCommand.inputUpArrow, modifierFlags: [], action: #selector(selectPrevious), discoverabilityTitle: "Select Previous".localized) - let selectObjectCommand = UIKeyCommand(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(selectCurrent), discoverabilityTitle: "Open Selected".localized) + let nextObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputDownArrow, modifierFlags: [], action: #selector(selectNext), discoverabilityTitle: "Select Next".localized) + let previousObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputUpArrow, modifierFlags: [], action: #selector(selectPrevious), discoverabilityTitle: "Select Previous".localized) + let selectObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(selectCurrent), discoverabilityTitle: "Open Selected".localized) var shortcuts = [UIKeyCommand]() if let visibleViewController = self.presentedViewController as? UIAlertController, let keyCommands = visibleViewController.keyCommands { let newKeyCommands = keyCommands.map { (keyCommand) -> UIKeyCommand in if let input = keyCommand.input, let discoverabilityTitle = keyCommand.discoverabilityTitle { - return UIKeyCommand(input: input, modifierFlags: keyCommand.modifierFlags, action: #selector(performActionOnVisibleViewController), discoverabilityTitle: discoverabilityTitle) + return UIKeyCommand.ported(input: input, modifierFlags: keyCommand.modifierFlags, action: #selector(performActionOnVisibleViewController), discoverabilityTitle: discoverabilityTitle) } return UIKeyCommand(input: keyCommand.input!, modifierFlags: keyCommand.modifierFlags, action: #selector(performActionOnVisibleViewController)) @@ -580,8 +580,8 @@ extension StaticTableViewController { if let slider = sliders?.first as? UISlider { slider.thumbTintColor = Theme.shared.activeCollection.tableRowHighlightColors.backgroundColor } - let sliderDownCommand = UIKeyCommand(input: UIKeyCommand.inputLeftArrow, modifierFlags: [], action: #selector(sliderDown), discoverabilityTitle: "Decrease Slider Value".localized) - let sliderUpCommand = UIKeyCommand(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(sliderUp), discoverabilityTitle: "Increase Slider Value".localized) + let sliderDownCommand = UIKeyCommand.ported(input: UIKeyCommand.inputLeftArrow, modifierFlags: [], action: #selector(sliderDown), discoverabilityTitle: "Decrease Slider Value".localized) + let sliderUpCommand = UIKeyCommand.ported(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(sliderUp), discoverabilityTitle: "Increase Slider Value".localized) shortcuts.append(sliderDownCommand) shortcuts.append(sliderUpCommand) } else { @@ -739,7 +739,7 @@ extension ClientQueryViewController { open override var keyCommands: [UIKeyCommand]? { var shortcuts = [UIKeyCommand]() - let scopeCommand = UIKeyCommand(input: "F", modifierFlags: [.command], action: #selector(changeSearchScope(_:)), discoverabilityTitle: "Toggle Search Scope".localized) + let scopeCommand = UIKeyCommand.ported(input: "F", modifierFlags: [.command], action: #selector(changeSearchScope(_:)), discoverabilityTitle: "Toggle Search Scope".localized) if let searchController = searchController, searchController.isActive { shortcuts.append(scopeCommand) } @@ -749,13 +749,13 @@ extension ClientQueryViewController { } if searchController?.isActive ?? false, searchScope == .global, hasSearchResults, self.tableView?.indexPathForSelectedRow != nil { - let revealObjectCommand = UIKeyCommand(input: UIKeyCommand.inputRightArrow, modifierFlags: [.command], action: #selector(revealItem), discoverabilityTitle: "Reveal in folder".localized) + let revealObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputRightArrow, modifierFlags: [.command], action: #selector(revealItem), discoverabilityTitle: "Reveal in folder".localized) shortcuts.append(revealObjectCommand) } - let nextObjectCommand = UIKeyCommand(input: UIKeyCommand.inputDownArrow, modifierFlags: [], action: #selector(selectNext), discoverabilityTitle: "Select Next".localized) - let previousObjectCommand = UIKeyCommand(input: UIKeyCommand.inputUpArrow, modifierFlags: [], action: #selector(selectPrevious), discoverabilityTitle: "Select Previous".localized) - let selectObjectCommand = UIKeyCommand(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(selectCurrent), discoverabilityTitle: "Open Selected".localized) + let nextObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputDownArrow, modifierFlags: [], action: #selector(selectNext), discoverabilityTitle: "Select Next".localized) + let previousObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputUpArrow, modifierFlags: [], action: #selector(selectPrevious), discoverabilityTitle: "Select Previous".localized) + let selectObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(selectCurrent), discoverabilityTitle: "Open Selected".localized) if let selectedIndexPath = self.tableView?.indexPathForSelectedRow { if selectedIndexPath.row < self.items.count - 1 { @@ -780,7 +780,7 @@ extension ClientQueryViewController { actions.forEach({ if let keyCommand = $0.actionExtension.keyCommand, let keyModifierFlags = $0.actionExtension.keyModifierFlags { - let actionCommand = UIKeyCommand(input: keyCommand, modifierFlags: keyModifierFlags, action: #selector(performFolderAction), discoverabilityTitle: $0.actionExtension.name) + let actionCommand = UIKeyCommand.ported(input: keyCommand, modifierFlags: keyModifierFlags, action: #selector(performFolderAction), discoverabilityTitle: $0.actionExtension.name) shortcuts.append(actionCommand) } }) @@ -838,9 +838,9 @@ extension LibrarySharesTableViewController { var shortcuts = [UIKeyCommand]() - let nextObjectCommand = UIKeyCommand(input: UIKeyCommand.inputDownArrow, modifierFlags: [], action: #selector(selectNext), discoverabilityTitle: "Select Next".localized) - let previousObjectCommand = UIKeyCommand(input: UIKeyCommand.inputUpArrow, modifierFlags: [], action: #selector(selectPrevious), discoverabilityTitle: "Select Previous".localized) - let selectObjectCommand = UIKeyCommand(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(selectCurrent), discoverabilityTitle: "Open Selected".localized) + let nextObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputDownArrow, modifierFlags: [], action: #selector(selectNext), discoverabilityTitle: "Select Next".localized) + let previousObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputUpArrow, modifierFlags: [], action: #selector(selectPrevious), discoverabilityTitle: "Select Previous".localized) + let selectObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(selectCurrent), discoverabilityTitle: "Open Selected".localized) if let selectedRow = self.tableView?.indexPathForSelectedRow?.row { if selectedRow < self.shares.count - 1 { @@ -868,14 +868,14 @@ extension QueryFileListTableViewController { var shortcuts = [UIKeyCommand]() - let nextObjectCommand = UIKeyCommand(input: UIKeyCommand.inputDownArrow, modifierFlags: [], action: #selector(selectNext), discoverabilityTitle: "Select Next".localized) - let selectLastPageObjectCommand = UIKeyCommand(input: UIKeyCommand.inputDownArrow, modifierFlags: [.command], action: #selector(selectLastPageObject), discoverabilityTitle: "Select Last Item on Page".localized) - let previousObjectCommand = UIKeyCommand(input: UIKeyCommand.inputUpArrow, modifierFlags: [], action: #selector(selectPrevious), discoverabilityTitle: "Select Previous".localized) - let selectObjectCommand = UIKeyCommand(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(selectCurrent), discoverabilityTitle: "Open Selected".localized) - let scrollTopCommand = UIKeyCommand(input: UIKeyCommand.inputUpArrow, modifierFlags: [.command, .shift], action: #selector(scrollToFirstRow), discoverabilityTitle: "Scroll to Top".localized) - let scrollBottomCommand = UIKeyCommand(input: UIKeyCommand.inputDownArrow, modifierFlags: [.command, .shift], action: #selector(scrollToLastRow), discoverabilityTitle: "Scroll to Bottom".localized) - let toggleSortCommand = UIKeyCommand(input: "S", modifierFlags: [.alternate], action: #selector(toggleSortOrder), discoverabilityTitle: "Change Sort Order".localized) - let searchCommand = UIKeyCommand(input: "F", modifierFlags: [.command], action: #selector(enableSearch), discoverabilityTitle: "Search".localized) + let nextObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputDownArrow, modifierFlags: [], action: #selector(selectNext), discoverabilityTitle: "Select Next".localized) + let selectLastPageObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputDownArrow, modifierFlags: [.command], action: #selector(selectLastPageObject), discoverabilityTitle: "Select Last Item on Page".localized) + let previousObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputUpArrow, modifierFlags: [], action: #selector(selectPrevious), discoverabilityTitle: "Select Previous".localized) + let selectObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(selectCurrent), discoverabilityTitle: "Open Selected".localized) + let scrollTopCommand = UIKeyCommand.ported(input: UIKeyCommand.inputUpArrow, modifierFlags: [.command, .shift], action: #selector(scrollToFirstRow), discoverabilityTitle: "Scroll to Top".localized) + let scrollBottomCommand = UIKeyCommand.ported(input: UIKeyCommand.inputDownArrow, modifierFlags: [.command, .shift], action: #selector(scrollToLastRow), discoverabilityTitle: "Scroll to Bottom".localized) + let toggleSortCommand = UIKeyCommand.ported(input: "S", modifierFlags: [.alternate], action: #selector(toggleSortOrder), discoverabilityTitle: "Change Sort Order".localized) + let searchCommand = UIKeyCommand.ported(input: "F", modifierFlags: [.command], action: #selector(enableSearch), discoverabilityTitle: "Search".localized) // Add key commands for file name letters if sortMethod == .alphabetically, let searchController = searchController, !searchController.isActive { let indexTitles = Array( Set( self.items.map { String(( $0.name?.first!.uppercased())!) })).sorted() @@ -896,7 +896,7 @@ extension QueryFileListTableViewController { actionsCollaborate.forEach({ if let keyCommand = $0.actionExtension.keyCommand, let keyModifierFlags = $0.actionExtension.keyModifierFlags { - let actionCommand = UIKeyCommand(input: keyCommand, modifierFlags: keyModifierFlags, action: #selector(performMoreItemAction), discoverabilityTitle: $0.actionExtension.name) + let actionCommand = UIKeyCommand.ported(input: keyCommand, modifierFlags: keyModifierFlags, action: #selector(performMoreItemAction), discoverabilityTitle: $0.actionExtension.name) shortcuts.append(actionCommand) } }) @@ -909,7 +909,7 @@ extension QueryFileListTableViewController { for (index, method) in SortMethod.all.enumerated() { let sortTitle = String(format: "Sort by %@".localized, method.localizedName) - let sortCommand = UIKeyCommand(input: String(index + 1), modifierFlags: [.alternate], action: #selector(changeSortMethod), discoverabilityTitle: sortTitle) + let sortCommand = UIKeyCommand.ported(input: String(index + 1), modifierFlags: [.alternate], action: #selector(changeSortMethod), discoverabilityTitle: sortTitle) shortcuts.append(sortCommand) } @@ -1015,13 +1015,13 @@ extension ClientDirectoryPickerViewController { } if let selectButtonTitle = selectButton?.title, let selector = selectButton?.action { - let doCommand = UIKeyCommand(input: "\r", modifierFlags: [.command], action: selector, discoverabilityTitle: selectButtonTitle) + let doCommand = UIKeyCommand.ported(input: "\r", modifierFlags: [.command], action: selector, discoverabilityTitle: selectButtonTitle) shortcuts.append(doCommand) } - let createFolder = UIKeyCommand(input: "N", modifierFlags: [.command], action: #selector(createFolderButtonPressed), discoverabilityTitle: "Create Folder".localized) + let createFolder = UIKeyCommand.ported(input: "N", modifierFlags: [.command], action: #selector(createFolderButtonPressed), discoverabilityTitle: "Create Folder".localized) shortcuts.append(createFolder) - let dismissCommand = UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismiss), discoverabilityTitle: "Cancel".localized) + let dismissCommand = UIKeyCommand.ported(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismiss), discoverabilityTitle: "Cancel".localized) shortcuts.append(dismissCommand) return shortcuts @@ -1039,9 +1039,9 @@ extension PhotoAlbumTableViewController { shortcuts.append(contentsOf: superKeyCommands) } - let nextObjectCommand = UIKeyCommand(input: UIKeyCommand.inputDownArrow, modifierFlags: [], action: #selector(selectNext), discoverabilityTitle: "Select Next".localized) - let previousObjectCommand = UIKeyCommand(input: UIKeyCommand.inputUpArrow, modifierFlags: [], action: #selector(selectPrevious), discoverabilityTitle: "Select Previous".localized) - let selectObjectCommand = UIKeyCommand(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(selectCurrent), discoverabilityTitle: "Open Selected".localized) + let nextObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputDownArrow, modifierFlags: [], action: #selector(selectNext), discoverabilityTitle: "Select Next".localized) + let previousObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputUpArrow, modifierFlags: [], action: #selector(selectPrevious), discoverabilityTitle: "Select Previous".localized) + let selectObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(selectCurrent), discoverabilityTitle: "Open Selected".localized) if let selectedRow = self.tableView?.indexPathForSelectedRow?.row { if selectedRow < self.albums.count - 1 { @@ -1055,7 +1055,7 @@ extension PhotoAlbumTableViewController { shortcuts.append(nextObjectCommand) } - let dismissCommand = UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismiss), discoverabilityTitle: "Cancel".localized) + let dismissCommand = UIKeyCommand.ported(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismiss), discoverabilityTitle: "Cancel".localized) shortcuts.append(dismissCommand) return shortcuts @@ -1074,13 +1074,13 @@ extension PhotoSelectionViewController { shortcuts.append(contentsOf: superKeyCommands) } - let nextObjectCommand = UIKeyCommand(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(selectNext), discoverabilityTitle: "Select Next".localized) - let previousObjectCommand = UIKeyCommand(input: UIKeyCommand.inputLeftArrow, modifierFlags: [], action: #selector(selectPrevious), discoverabilityTitle: "Select Previous".localized) - let selectObjectCommand = UIKeyCommand(input: " ", modifierFlags: [], action: #selector(selectCurrent), discoverabilityTitle: "Select".localized) - let selectAllCommand = UIKeyCommand(input: "A", modifierFlags: [.command], action: #selector(selectAllItems), discoverabilityTitle: "Select All".localized) - let deselectAllCommand = UIKeyCommand(input: "D", modifierFlags: [.command], action: #selector(deselectAllItems), discoverabilityTitle: "Deselect All".localized) - let uploadCommand = UIKeyCommand(input: "U", modifierFlags: [.command], action: #selector(upload), discoverabilityTitle: "Upload".localized) - let dismissCommand = UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismiss), discoverabilityTitle: "Cancel".localized) + let nextObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(selectNext), discoverabilityTitle: "Select Next".localized) + let previousObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputLeftArrow, modifierFlags: [], action: #selector(selectPrevious), discoverabilityTitle: "Select Previous".localized) + let selectObjectCommand = UIKeyCommand.ported(input: " ", modifierFlags: [], action: #selector(selectCurrent), discoverabilityTitle: "Select".localized) + let selectAllCommand = UIKeyCommand.ported(input: "A", modifierFlags: [.command], action: #selector(selectAllItems), discoverabilityTitle: "Select All".localized) + let deselectAllCommand = UIKeyCommand.ported(input: "D", modifierFlags: [.command], action: #selector(deselectAllItems), discoverabilityTitle: "Deselect All".localized) + let uploadCommand = UIKeyCommand.ported(input: "U", modifierFlags: [.command], action: #selector(upload), discoverabilityTitle: "Upload".localized) + let dismissCommand = UIKeyCommand.ported(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismiss), discoverabilityTitle: "Cancel".localized) shortcuts.append(nextObjectCommand) shortcuts.append(previousObjectCommand) @@ -1157,7 +1157,7 @@ extension PasscodeViewController { var keyCommands : [UIKeyCommand] = [] for i in 0 ..< 10 { keyCommands.append( - UIKeyCommand(input:String(i), + UIKeyCommand.ported(input:String(i), modifierFlags: [], action: #selector(self.performKeyCommand(sender:)), discoverabilityTitle: String(i)) @@ -1165,7 +1165,7 @@ extension PasscodeViewController { } keyCommands.append( - UIKeyCommand(input: "\u{8}", + UIKeyCommand.ported(input: "\u{8}", modifierFlags: [], action: #selector(self.performKeyCommand(sender:)), discoverabilityTitle: "Delete".localized) @@ -1174,7 +1174,7 @@ extension PasscodeViewController { if cancelButton?.isHidden == false { keyCommands.append( - UIKeyCommand(input: UIKeyCommand.inputEscape, + UIKeyCommand.ported(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(self.performKeyCommand(sender:)), discoverabilityTitle: "Cancel".localized) @@ -1213,41 +1213,41 @@ extension DisplayHostViewController { shortcuts.append(contentsOf: superKeyCommands) } - let nextObjectCommand = UIKeyCommand(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(selectNext), discoverabilityTitle: "Next".localized) - let previousObjectCommand = UIKeyCommand(input: UIKeyCommand.inputLeftArrow, modifierFlags: [], action: #selector(selectPrevious), discoverabilityTitle: "Previous".localized) + let nextObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(selectNext), discoverabilityTitle: "Next".localized) + let previousObjectCommand = UIKeyCommand.ported(input: UIKeyCommand.inputLeftArrow, modifierFlags: [], action: #selector(selectPrevious), discoverabilityTitle: "Previous".localized) var showCommands = false if let pdfViewController = self.viewControllers?.first as? PDFViewerViewController { showCommands = true - let searchCommand = UIKeyCommand(input: "S", modifierFlags: [.command], action: #selector(search), discoverabilityTitle: "Search".localized) - let gotoCommand = UIKeyCommand(input: "G", modifierFlags: [.control], action: #selector(goToPage), discoverabilityTitle: "Go to Page".localized) + let searchCommand = UIKeyCommand.ported(input: "S", modifierFlags: [.command], action: #selector(search), discoverabilityTitle: "Search".localized) + let gotoCommand = UIKeyCommand.ported(input: "G", modifierFlags: [.control], action: #selector(goToPage), discoverabilityTitle: "Go to Page".localized) if !pdfViewController.searchResultsView.isHidden, pdfViewController.searchResultsView.matches?.count ?? 0 > 0 { if pdfViewController.searchResultsView.forwardButton.isEnabled { - let findNextCommand = UIKeyCommand(input: "G", modifierFlags: [.command], action: #selector(findNext), discoverabilityTitle: "Find Next".localized) + let findNextCommand = UIKeyCommand.ported(input: "G", modifierFlags: [.command], action: #selector(findNext), discoverabilityTitle: "Find Next".localized) shortcuts.append(findNextCommand) } if pdfViewController.searchResultsView.backButton.isEnabled { - let findPreviousCommand = UIKeyCommand(input: "G", modifierFlags: [.command, .shift], action: #selector(findPrevious), discoverabilityTitle: "Find Previous".localized) + let findPreviousCommand = UIKeyCommand.ported(input: "G", modifierFlags: [.command, .shift], action: #selector(findPrevious), discoverabilityTitle: "Find Previous".localized) shortcuts.append(findPreviousCommand) } - let closeFindCommand = UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(closeFind), discoverabilityTitle: "Close Search".localized) + let closeFindCommand = UIKeyCommand.ported(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(closeFind), discoverabilityTitle: "Close Search".localized) shortcuts.append(closeFindCommand) } shortcuts.append(searchCommand) shortcuts.append(gotoCommand) } else if let viewController = (self.viewControllers?.first as? MediaDisplayViewController) { - let fullscreenCommand = UIKeyCommand(input: "F", modifierFlags: [], action: #selector(enterFullScreen), discoverabilityTitle: "Full Screen".localized) - let playbackCommand = UIKeyCommand(input: " ", modifierFlags: [], action: #selector(tooglePlayback), discoverabilityTitle: "Play/Pause".localized) - let seekBackwardCommand = UIKeyCommand(input: UIKeyCommand.inputLeftArrow, modifierFlags: [.control], action: #selector(seek), discoverabilityTitle: "Skip Back".localized) - let seekForwardCommand = UIKeyCommand(input: UIKeyCommand.inputRightArrow, modifierFlags: [.control], action: #selector(seek), discoverabilityTitle: "Skip Ahead".localized) - let replayCommand = UIKeyCommand(input: UIKeyCommand.inputLeftArrow, modifierFlags: [.command], action: #selector(replay), discoverabilityTitle: "Go to Beginning".localized) - let muteCommand = UIKeyCommand(input: "M", modifierFlags: [], action: #selector(toggleMute), discoverabilityTitle: "Mute/Unmute".localized) + let fullscreenCommand = UIKeyCommand.ported(input: "F", modifierFlags: [], action: #selector(enterFullScreen), discoverabilityTitle: "Full Screen".localized) + let playbackCommand = UIKeyCommand.ported(input: " ", modifierFlags: [], action: #selector(tooglePlayback), discoverabilityTitle: "Play/Pause".localized) + let seekBackwardCommand = UIKeyCommand.ported(input: UIKeyCommand.inputLeftArrow, modifierFlags: [.control], action: #selector(seek), discoverabilityTitle: "Skip Back".localized) + let seekForwardCommand = UIKeyCommand.ported(input: UIKeyCommand.inputRightArrow, modifierFlags: [.control], action: #selector(seek), discoverabilityTitle: "Skip Ahead".localized) + let replayCommand = UIKeyCommand.ported(input: UIKeyCommand.inputLeftArrow, modifierFlags: [.command], action: #selector(replay), discoverabilityTitle: "Go to Beginning".localized) + let muteCommand = UIKeyCommand.ported(input: "M", modifierFlags: [], action: #selector(toggleMute), discoverabilityTitle: "Mute/Unmute".localized) if viewController.canEnterFullScreen() { shortcuts.append(fullscreenCommand) @@ -1259,7 +1259,7 @@ extension DisplayHostViewController { shortcuts.append(muteCommand) } if let viewController = (self.viewControllers?.first as? DisplayViewController), (viewController.navigationController?.isNavigationBarHidden ?? false) { - let closeCommand = UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(closePresentationMode), discoverabilityTitle: "Exit Full Screen".localized) + let closeCommand = UIKeyCommand.ported(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(closePresentationMode), discoverabilityTitle: "Exit Full Screen".localized) shortcuts.append(closeCommand) } @@ -1282,7 +1282,7 @@ extension DisplayHostViewController { actionsCollaborate.forEach({ if let keyCommand = $0.actionExtension.keyCommand, let keyModifierFlags = $0.actionExtension.keyModifierFlags { - let actionCommand = UIKeyCommand(input: keyCommand, modifierFlags: keyModifierFlags, action: #selector(performMoreItemAction), discoverabilityTitle: $0.actionExtension.name) + let actionCommand = UIKeyCommand.ported(input: keyCommand, modifierFlags: keyModifierFlags, action: #selector(performMoreItemAction), discoverabilityTitle: $0.actionExtension.name) shortcuts.append(actionCommand) } }) @@ -1441,13 +1441,13 @@ extension AlertViewController { var defaultCommand : UIKeyCommand? if options.count == 1, let option = options.first { - defaultCommand = UIKeyCommand(input: "\r", modifierFlags: [], action: #selector(chooseDefaultOption), discoverabilityTitle: option.label) + defaultCommand = UIKeyCommand.ported(input: "\r", modifierFlags: [], action: #selector(chooseDefaultOption), discoverabilityTitle: option.label) } else { for option in options { if option.type == .default { - defaultCommand = UIKeyCommand(input: "\r", modifierFlags: [], action: #selector(chooseDefaultOption), discoverabilityTitle: option.label) + defaultCommand = UIKeyCommand.ported(input: "\r", modifierFlags: [], action: #selector(chooseDefaultOption), discoverabilityTitle: option.label) } else { - let command = UIKeyCommand(input: "\(index)", modifierFlags: .command, action: #selector(chooseOption(_:)), discoverabilityTitle: option.label) + let command = UIKeyCommand.ported(input: "\(index)", modifierFlags: .command, action: #selector(chooseOption(_:)), discoverabilityTitle: option.label) commands.append(command) } @@ -1497,14 +1497,14 @@ extension FrameViewController { var counter = 1 for button in buttons { if let buttonTitle = button.currentTitle { - let command = UIKeyCommand(input: String(counter), modifierFlags: [.command], action: #selector(issueButtonPressed), discoverabilityTitle: buttonTitle) + let command = UIKeyCommand.ported(input: String(counter), modifierFlags: [.command], action: #selector(issueButtonPressed), discoverabilityTitle: buttonTitle) shortcuts.append(command) } counter += 1 } } } else { - let cancelCommand = UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismissCard), discoverabilityTitle: "Close".localized) + let cancelCommand = UIKeyCommand.ported(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(dismissCard), discoverabilityTitle: "Close".localized) shortcuts.append(cancelCommand) } diff --git a/ownCloud/Media Uploads/PhotoKit Extensions/PHAsset+Upload.swift b/ownCloud/Media Uploads/PhotoKit Extensions/PHAsset+Upload.swift index 66fc0e923..eb31cd126 100644 --- a/ownCloud/Media Uploads/PhotoKit Extensions/PHAsset+Upload.swift +++ b/ownCloud/Media Uploads/PhotoKit Extensions/PHAsset+Upload.swift @@ -315,7 +315,7 @@ extension PHAsset { requestOptions.version = .current requestOptions.resizeMode = .none - PHImageManager.default().requestImageData(for: self, options: requestOptions) { (imageData, utiIdentifier, _, info) in + PHImageManager.default().requestImageDataAndOrientation(for: self, options: requestOptions) { imageData, utiIdentifier, _, info in outError = info?[PHImageErrorKey] as? Error if let data = imageData, let uti = utiIdentifier { if utisToConvert.contains(uti) { diff --git a/ownCloud/Migration/MigrationActivityCell.swift b/ownCloud/Migration/MigrationActivityCell.swift index fade281e3..e183cf751 100644 --- a/ownCloud/Migration/MigrationActivityCell.swift +++ b/ownCloud/Migration/MigrationActivityCell.swift @@ -61,7 +61,7 @@ class MigrationActivityCell: ThemeTableViewCell { var titleLabel = UILabel() var descriptionLabel = UILabel() - var activityView = UIActivityIndicatorView(style: .white) + var activityView = UIActivityIndicatorView(style: .medium) var statusImageView = UIImageView() var activityTypeImageView = UIImageView() diff --git a/ownCloud/Release Notes/ReleaseNotesHostViewController.swift b/ownCloud/Release Notes/ReleaseNotesHostViewController.swift index e82f00fc2..26b22bed9 100644 --- a/ownCloud/Release Notes/ReleaseNotesHostViewController.swift +++ b/ownCloud/Release Notes/ReleaseNotesHostViewController.swift @@ -224,18 +224,8 @@ class ReleaseNotesDatasource : NSObject, OCClassSettingsUserPreferencesSupport { } func image(for key: String) -> UIImage? { - if #available(iOS 13.0, *) { - let homeSymbolConfiguration = UIImage.SymbolConfiguration(pointSize: 32, weight: .thin) - return UIImage(systemName: key, withConfiguration: homeSymbolConfiguration)?.withRenderingMode(.alwaysTemplate) - } else if let path = Bundle.main.path(forResource: "ReleaseNotes", ofType: "plist"), let releaseNotesValues = NSDictionary(contentsOfFile: path), let imageValues = releaseNotesValues["ImageData"] as? NSDictionary, let base64Image = imageValues[key] as? String { - let dataDecoded : Data = Data(base64Encoded: base64Image, options: .ignoreUnknownCharacters)! - - if let decodedimage = UIImage(data: dataDecoded)?.scaledImageFitting(in: CGSize(width: 50.0, height: 44.0))?.withRenderingMode(.alwaysTemplate) { - return decodedimage - } - } - - return nil + let homeSymbolConfiguration = UIImage.SymbolConfiguration(pointSize: 32, weight: .thin) + return UIImage(systemName: key, withConfiguration: homeSymbolConfiguration)?.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloud/SceneDelegate.swift b/ownCloud/SceneDelegate.swift index 2204d8afc..e43622786 100644 --- a/ownCloud/SceneDelegate.swift +++ b/ownCloud/SceneDelegate.swift @@ -28,7 +28,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // Set up HTTP pipelines OCHTTPPipelineManager.setupPersistentPipelines() - + if let windowScene = scene as? UIWindowScene { window = ThemeWindow(windowScene: windowScene) var navigationController: UINavigationController? diff --git a/ownCloud/Server List/ServerListBookmarkCell.swift b/ownCloud/Server List/ServerListBookmarkCell.swift index 81f556ad5..89dbefed1 100644 --- a/ownCloud/Server List/ServerListBookmarkCell.swift +++ b/ownCloud/Server List/ServerListBookmarkCell.swift @@ -42,9 +42,7 @@ class ServerListBookmarkCell : ThemeTableViewCell { func prepareViewAndConstraints() { self.selectionStyle = .default - if #available(iOS 13.4, *) { - PointerEffect.install(on: self.contentView, effectStyle: .hover) - } + PointerEffect.install(on: self.contentView, effectStyle: .hover) titleLabel.translatesAutoresizingMaskIntoConstraints = false detailLabel.translatesAutoresizingMaskIntoConstraints = false diff --git a/ownCloud/Server List/ServerListTableViewController.swift b/ownCloud/Server List/ServerListTableViewController.swift index 83ad1dc65..ad6a78d57 100644 --- a/ownCloud/Server List/ServerListTableViewController.swift +++ b/ownCloud/Server List/ServerListTableViewController.swift @@ -157,11 +157,6 @@ class ServerListTableViewController: UITableViewController, Themeable, StateRest self.navigationItem.largeTitleDisplayMode = .never self.navigationItem.titleView = logoWrapperView - if #available(iOS 13, *) { } else { - // Log in automatically on iOS 12 (handled by scene restoration in iOS 13+) - NotificationCenter.default.addObserver(self, selector: #selector(considerAutoLogin), name: UIApplication.didBecomeActiveNotification, object: nil) - } - if ReleaseNotesDatasource().shouldShowReleaseNotes { let releaseNotesHostController = ReleaseNotesHostViewController() releaseNotesHostController.modalPresentationStyle = .formSheet @@ -281,24 +276,7 @@ class ServerListTableViewController: UITableViewController, Themeable, StateRest VendorServices.shared.considerReviewPrompt() } - if #available(iOS 13, *) { - view.window?.windowScene?.userActivity = ServerListTableViewController.showServerListActivity - } - } - - @objc func considerAutoLogin() -> Bool { - if shownFirstTime, UIApplication.shared.applicationState != .background { - shownFirstTime = false - - if #available(iOS 13.0, *) { /* this will be handled automatically by scene restoration */ } else { - if let bookmark = OCBookmarkManager.lastBookmarkSelectedForConnection { - connect(to: bookmark, lastVisibleItemId: nil, animated: true) - return true - } - } - } - - return false + view.window?.windowScene?.userActivity = ServerListTableViewController.showServerListActivity } func considerBetaWarning() { @@ -673,10 +651,7 @@ class ServerListTableViewController: UITableViewController, Themeable, StateRest // Set up custom push transition for presentation let transitionDelegate = PushTransitionDelegate(with: { (toViewController, window) in window.addSubview(toViewController.view) - - if #available(iOS 13, *) { - window.windowScene?.userActivity = ServerListTableViewController.showServerListActivity - } + window.windowScene?.userActivity = ServerListTableViewController.showServerListActivity }) clientRootViewController.pushTransition = transitionDelegate // Keep a reference, so it's still around on dismissal @@ -704,11 +679,7 @@ class ServerListTableViewController: UITableViewController, Themeable, StateRest if !VendorServices.shared.isBranded { if OCBookmarkManager.shared.bookmarks.count == 1 { var serverListTableViewController : ServerListTableViewController? - if #available(iOS 13.0, *) { - serverListTableViewController = StaticLoginSingleAccountServerListViewController(style: .insetGrouped) - } else { - serverListTableViewController = StaticLoginSingleAccountServerListViewController(style: .grouped) - } + serverListTableViewController = StaticLoginSingleAccountServerListViewController(style: .insetGrouped) guard let serverListTableViewController = serverListTableViewController else { return } @@ -901,11 +872,9 @@ class ServerListTableViewController: UITableViewController, Themeable, StateRest } }) - if #available(iOS 13.0, *), UIDevice.current.isIpad { - let openAccountAction = UITableViewRowAction(style: .normal, - title: "Open in Window".localized, - handler: { (_, indexPath) in - self.openAccountInWindow(at: indexPath) + if UIDevice.current.isIpad { + let openAccountAction = UITableViewRowAction(style: .normal, title: "Open in Window".localized, handler: { (_, indexPath) in + self.openAccountInWindow(at: indexPath) }) openAccountAction.backgroundColor = .orange diff --git a/ownCloud/Settings/BackgroundUploadsSettingsSection.swift b/ownCloud/Settings/BackgroundUploadsSettingsSection.swift index 82f0b5fc1..87426530d 100644 --- a/ownCloud/Settings/BackgroundUploadsSettingsSection.swift +++ b/ownCloud/Settings/BackgroundUploadsSettingsSection.swift @@ -73,37 +73,24 @@ class BackgroundUploadsSettingsSection: SettingsSection { self.headerTitle = "Background uploads (Lab Version)".localized // Add option for iOS13 to use BackgroundTasks framework for background uploads - if #available(iOS 13, *) { - backgroundUploadsRow = StaticTableViewRow(switchWithAction: { (_, sender) in - if let enableSwitch = sender as? UISwitch { - userDefaults.backgroundMediaUploadsEnabled = enableSwitch.isOn - } - }, title: "Use background refresh".localized, subtitle: "Allow this app to refresh the content when on Wi-Fi or mobile network in background.".localized, value: self.userDefaults.backgroundMediaUploadsEnabled, identifier: "background-refresh") + backgroundUploadsRow = StaticTableViewRow(switchWithAction: { (_, sender) in + if let enableSwitch = sender as? UISwitch { + userDefaults.backgroundMediaUploadsEnabled = enableSwitch.isOn + } + }, title: "Use background refresh".localized, subtitle: "Allow this app to refresh the content when on Wi-Fi or mobile network in background.".localized, value: self.userDefaults.backgroundMediaUploadsEnabled, identifier: "background-refresh") - self.add(row: backgroundUploadsRow!) - } + self.add(row: backgroundUploadsRow!) // Add option to enable background location updates which will trigger background media uploads #if !DISABLE_BACKGROUND_LOCATION var locationServicesRowTitle: String = "" - if #available(iOS 13, *) { - locationServicesRowTitle = "Use background location updates".localized - } else { - locationServicesRowTitle = "Enable background uploads".localized - } + locationServicesRowTitle = "Use background location updates".localized // Add section footer with detailed explanations var locationServicesRowSubtitle = "" - if #available(iOS 13, *) { - locationServicesRowSubtitle += "If you would like background media uploads to be more reliable, you should enable background location updates.".localized - } else { - locationServicesRowSubtitle += "Background media uploads rely on location updates and will stop working if location acquisition permissions are revoked.".localized - } - - if #available(iOS 13, *) { - locationServicesRowSubtitle += " " - locationServicesRowSubtitle += "Otherwise background media uploads using background refresh technology would depend on how frequently you use the app.".localized - } + locationServicesRowSubtitle += "If you would like background media uploads to be more reliable, you should enable background location updates.".localized + locationServicesRowSubtitle += " " + locationServicesRowSubtitle += "Otherwise background media uploads using background refresh technology would depend on how frequently you use the app.".localized let currentAuthStatus = CLLocationManager.authorizationStatus() == .authorizedAlways backgroundLocationRow = StaticTableViewRow(switchWithAction: { (_, sender) in @@ -124,33 +111,31 @@ class BackgroundUploadsSettingsSection: SettingsSection { #endif /* !DISABLE_BACKGROUND_LOCATION */ // Add option to enable local notifications reporting that some number of media files got enqueued for upload - if #available(iOS 13, *) { - notificationsRow = StaticTableViewRow(switchWithAction: { (_, sender) in - if let enableSwitch = sender as? UISwitch { - if enableSwitch.isOn { - // Request authorization for notifications - NotificationManager.shared.getNotificationSettings(completionHandler: { (settings) in - if settings.authorizationStatus == .notDetermined { - NotificationManager.shared.requestAuthorization(options: [.alert]) { (granted, _) in - OnMainThread { - enableSwitch.isOn = granted - userDefaults.backgroundMediaUploadsNotificationsEnabled = granted - } + notificationsRow = StaticTableViewRow(switchWithAction: { (_, sender) in + if let enableSwitch = sender as? UISwitch { + if enableSwitch.isOn { + // Request authorization for notifications + NotificationManager.shared.getNotificationSettings(completionHandler: { (settings) in + if settings.authorizationStatus == .notDetermined { + NotificationManager.shared.requestAuthorization(options: [.alert]) { (granted, _) in + OnMainThread { + enableSwitch.isOn = granted + userDefaults.backgroundMediaUploadsNotificationsEnabled = granted } - } else if settings.authorizationStatus == .authorized { - userDefaults.backgroundMediaUploadsNotificationsEnabled = true - } else { - userDefaults.backgroundMediaUploadsNotificationsEnabled = false } - }) - } else { - userDefaults.backgroundMediaUploadsNotificationsEnabled = false - } + } else if settings.authorizationStatus == .authorized { + userDefaults.backgroundMediaUploadsNotificationsEnabled = true + } else { + userDefaults.backgroundMediaUploadsNotificationsEnabled = false + } + }) + } else { + userDefaults.backgroundMediaUploadsNotificationsEnabled = false } - }, title: "Background upload notifications".localized, value: userDefaults.backgroundMediaUploadsNotificationsEnabled, identifier: "background-upload-notifications") + } + }, title: "Background upload notifications".localized, value: userDefaults.backgroundMediaUploadsNotificationsEnabled, identifier: "background-upload-notifications") - self.add(row: notificationsRow!) - } + self.add(row: notificationsRow!) // Update notifications option NotificationManager.shared.getNotificationSettings(completionHandler: { (settings) in diff --git a/ownCloud/Settings/SettingsViewController.swift b/ownCloud/Settings/SettingsViewController.swift index 9a4ef7c8e..01198a9d6 100644 --- a/ownCloud/Settings/SettingsViewController.swift +++ b/ownCloud/Settings/SettingsViewController.swift @@ -42,8 +42,7 @@ class SettingsViewController: StaticTableViewController { self.addSection(MediaFilesSettingsSection(userDefaults: userDefaults)) #if !DISABLE_APPSTORE_LICENSING - if #available(iOS 13, *), // Require iOS 13 - !OCLicenseEMMProvider.isEMMVersion, // Do not show purchases in the EMM version + if !OCLicenseEMMProvider.isEMMVersion, // Do not show purchases in the EMM version // Do only show purchases section if there's at least one non-Enterprise account OCLicenseEnterpriseProvider.numberOfEnterpriseAccounts < OCBookmarkManager.shared.bookmarks.count, !VendorServices.shared.isBranded // Do not show purchases in branded app { diff --git a/ownCloud/Settings/UserInterfaceSettingsSection.swift b/ownCloud/Settings/UserInterfaceSettingsSection.swift index 28c7f17da..b0d4c6e03 100644 --- a/ownCloud/Settings/UserInterfaceSettingsSection.swift +++ b/ownCloud/Settings/UserInterfaceSettingsSection.swift @@ -68,11 +68,10 @@ class UserInterfaceSettingsSection: SettingsSection { if let availableStyles = ThemeStyle.availableStyles { var themeIdentifiersByName : [[String:Any]] = [] var selectedValue = ThemeStyle.preferredStyle.identifier - if #available(iOS 13.0, *) { - themeIdentifiersByName = [["System Appeareance".localized : "com.owncloud.system"]] - if ThemeStyle.followSystemAppearance { - selectedValue = "com.owncloud.system" - } + + themeIdentifiersByName = [["System Appeareance".localized : "com.owncloud.system"]] + if ThemeStyle.followSystemAppearance { + selectedValue = "com.owncloud.system" } for style in availableStyles { diff --git a/ownCloud/Static Login/Interface/StaticLoginSetupViewController.swift b/ownCloud/Static Login/Interface/StaticLoginSetupViewController.swift index e9d4786c9..58112c3a8 100644 --- a/ownCloud/Static Login/Interface/StaticLoginSetupViewController.swift +++ b/ownCloud/Static Login/Interface/StaticLoginSetupViewController.swift @@ -356,7 +356,7 @@ class StaticLoginSetupViewController : StaticLoginStepViewController { options[.presentingViewControllerKey] = self - let spinner = UIActivityIndicatorView(style: .white) + let spinner = UIActivityIndicatorView(style: .medium) if let button = sender as? ThemeButton { button.setTitle("Authenticating…".localized, for: .normal) button.isEnabled = false diff --git a/ownCloud/Static Login/Interface/StaticLoginSingleAccountServerListViewController.swift b/ownCloud/Static Login/Interface/StaticLoginSingleAccountServerListViewController.swift index 7104c6337..3784ab397 100644 --- a/ownCloud/Static Login/Interface/StaticLoginSingleAccountServerListViewController.swift +++ b/ownCloud/Static Login/Interface/StaticLoginSingleAccountServerListViewController.swift @@ -165,11 +165,7 @@ class StaticLoginSingleAccountServerListViewController: ServerListTableViewContr bookmarkCell.textLabel?.text = "Access Files".localized bookmarkCell.accessibilityIdentifier = "access-files" bookmarkCell.textLabel?.font = UIFont.preferredFont(forTextStyle: .headline) - if #available(iOS 13.0, *) { - bookmarkCell.imageView?.image = UIImage(systemName: "folder") - } else { - bookmarkCell.imageView?.image = UIImage(named: "folder")?.scaledImageFitting(in: CGSize(width: 28, height: 28)) - } + bookmarkCell.imageView?.image = UIImage(systemName: "folder") themeApplierToken = Theme.shared.add(applier: { (_, themeCollection, _) in bookmarkCell.imageView?.tintColor = themeCollection.tableRowColors.labelColor @@ -185,30 +181,15 @@ class StaticLoginSingleAccountServerListViewController: ServerListTableViewContr switch actionRows[indexPath.row] { case .editLogin: bookmarkCell.textLabel?.text = "Edit Login".localized - - if #available(iOS 13.0, *) { - bookmarkCell.imageView?.image = UIImage(systemName: "square.and.pencil") - } else { - bookmarkCell.imageView?.image = UIImage(named: "square.and.pencil")?.scaledImageFitting(in: CGSize(width: 28, height: 28)) - } + bookmarkCell.imageView?.image = UIImage(systemName: "square.and.pencil") case .manageStorage: bookmarkCell.textLabel?.text = "Manage Storage".localized - - if #available(iOS 13.0, *) { - bookmarkCell.imageView?.image = UIImage(systemName: "arrow.3.trianglepath") - } else { - bookmarkCell.imageView?.image = UIImage(named: "arrow.3.trianglepath")?.scaledImageFitting(in: CGSize(width: 28, height: 28)) - } + bookmarkCell.imageView?.image = UIImage(systemName: "arrow.3.trianglepath") case .logout: bookmarkCell.textLabel?.text = "Log out".localized - - if #available(iOS 13.0, *) { - bookmarkCell.imageView?.image = UIImage(systemName: "power") - } else { - bookmarkCell.imageView?.image = UIImage(named: "power")?.scaledImageFitting(in: CGSize(width: 28, height: 28)) - } + bookmarkCell.imageView?.image = UIImage(systemName: "power") } rowCell = bookmarkCell @@ -220,24 +201,14 @@ class StaticLoginSingleAccountServerListViewController: ServerListTableViewContr } switch settingsRows[indexPath.row] { + case .settings: - case .settings: - - bookmarkCell.textLabel?.text = "Settings".localized - if #available(iOS 13.0, *) { + bookmarkCell.textLabel?.text = "Settings".localized bookmarkCell.imageView?.image = UIImage(systemName: "gear") - } else { - bookmarkCell.imageView?.image = UIImage(named: "gear")?.scaledImageFitting(in: CGSize(width: 28, height: 28)) - } - case .addAccount: - bookmarkCell.textLabel?.text = "Add Account".localized - if #available(iOS 13.0, *) { + case .addAccount: + bookmarkCell.textLabel?.text = "Add Account".localized bookmarkCell.imageView?.image = UIImage(systemName: "plus") - } else { - bookmarkCell.imageView?.image = UIImage(named: "round-add-button")?.scaledImageFitting(in: CGSize(width: 28, height: 28)) - } - } rowCell = bookmarkCell diff --git a/ownCloud/Static Login/Interface/StaticLoginStepViewController.swift b/ownCloud/Static Login/Interface/StaticLoginStepViewController.swift index 429a2c84a..a25fc5664 100644 --- a/ownCloud/Static Login/Interface/StaticLoginStepViewController.swift +++ b/ownCloud/Static Login/Interface/StaticLoginStepViewController.swift @@ -31,11 +31,7 @@ class StaticLoginStepViewController : StaticTableViewController { } init(loginViewController theLoginViewController: StaticLoginViewController) { - if #available(iOS 13.0, *) { - super.init(style: .insetGrouped) - } else { - super.init(style: .grouped) - } + super.init(style: .insetGrouped) loginViewController = theLoginViewController self.tableView.backgroundColor = .clear diff --git a/ownCloud/Static Login/Interface/StaticLoginViewController.swift b/ownCloud/Static Login/Interface/StaticLoginViewController.swift index a88762eb7..a398baa05 100644 --- a/ownCloud/Static Login/Interface/StaticLoginViewController.swift +++ b/ownCloud/Static Login/Interface/StaticLoginViewController.swift @@ -316,18 +316,10 @@ class StaticLoginViewController: UIViewController, Themeable, StateRestorationCo if OCBookmarkManager.shared.bookmarks.count > 1 { //if OCBookmarkManager.shared.bookmarks.count > 1 || VendorServices.shared.canAddAccount { - if #available(iOS 13.0, *) { - serverList = StaticLoginServerListViewController(style: .insetGrouped) - } else { - serverList = StaticLoginServerListViewController(style: .grouped) - } + serverList = StaticLoginServerListViewController(style: .insetGrouped) (serverList as? StaticLoginServerListViewController)?.staticLoginViewController = self } else { - if #available(iOS 13.0, *) { - serverList = StaticLoginSingleAccountServerListViewController(style: .insetGrouped) - } else { - serverList = StaticLoginSingleAccountServerListViewController(style: .grouped) - } + serverList = StaticLoginSingleAccountServerListViewController(style: .insetGrouped) (serverList as? StaticLoginSingleAccountServerListViewController)?.staticLoginViewController = self } diff --git a/ownCloud/Static Login/StaticLoginProfile.swift b/ownCloud/Static Login/StaticLoginProfile.swift index bed78fe6d..4d3d5ee9e 100644 --- a/ownCloud/Static Login/StaticLoginProfile.swift +++ b/ownCloud/Static Login/StaticLoginProfile.swift @@ -176,136 +176,134 @@ class StaticLoginProfile: NSObject { extension Branding : StaticProfileBridge { public static func initializeStaticProfileBridge() { - if #available(iOS 13, *) { - self.registerOCClassSettingsDefaults([:], metadata: [ - StaticLoginProfile.Key.identifier.settingsKey : [ - .type : OCClassSettingsMetadataType.string, - .label : "Identifier", - .description : "Identifier uniquely identifying the profile.", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding", - .subCategory : "Profile" - ], + self.registerOCClassSettingsDefaults([:], metadata: [ + StaticLoginProfile.Key.identifier.settingsKey : [ + .type : OCClassSettingsMetadataType.string, + .label : "Identifier", + .description : "Identifier uniquely identifying the profile.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding", + .subCategory : "Profile" + ], - StaticLoginProfile.Key.name.settingsKey : [ - .type : OCClassSettingsMetadataType.string, - .label : "Name", - .description : "Name of the profile during setup.", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding", - .subCategory : "Profile" - ], + StaticLoginProfile.Key.name.settingsKey : [ + .type : OCClassSettingsMetadataType.string, + .label : "Name", + .description : "Name of the profile during setup.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding", + .subCategory : "Profile" + ], - StaticLoginProfile.Key.promptForPasswordAuth.settingsKey : [ - .type : OCClassSettingsMetadataType.string, - .label : "Password prompt", - .description : "Text that is shown when asking the user to enter their password.", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding", - .subCategory : "Profile" - ], + StaticLoginProfile.Key.promptForPasswordAuth.settingsKey : [ + .type : OCClassSettingsMetadataType.string, + .label : "Password prompt", + .description : "Text that is shown when asking the user to enter their password.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding", + .subCategory : "Profile" + ], - StaticLoginProfile.Key.promptForTokenAuth.settingsKey : [ - .type : OCClassSettingsMetadataType.string, - .label : "Token authentication prompt", - .description : "Text that is shown to the user before opening the authentication web view (f.ex. for OAuth2, OIDC).", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding", - .subCategory : "Profile" - ], + StaticLoginProfile.Key.promptForTokenAuth.settingsKey : [ + .type : OCClassSettingsMetadataType.string, + .label : "Token authentication prompt", + .description : "Text that is shown to the user before opening the authentication web view (f.ex. for OAuth2, OIDC).", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding", + .subCategory : "Profile" + ], - StaticLoginProfile.Key.promptForURL.settingsKey : [ - .type : OCClassSettingsMetadataType.string, - .label : "URL prompt", - .description : "Text shown above the URL field when setting up an account.", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding", - .subCategory : "Profile" - ], + StaticLoginProfile.Key.promptForURL.settingsKey : [ + .type : OCClassSettingsMetadataType.string, + .label : "URL prompt", + .description : "Text shown above the URL field when setting up an account.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding", + .subCategory : "Profile" + ], - StaticLoginProfile.Key.welcome.settingsKey : [ - .type : OCClassSettingsMetadataType.string, - .label : "Welcome Message", - .description : "Welcome message shown during account setup.", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding", - .subCategory : "Profile" - ], + StaticLoginProfile.Key.welcome.settingsKey : [ + .type : OCClassSettingsMetadataType.string, + .label : "Welcome Message", + .description : "Welcome message shown during account setup.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding", + .subCategory : "Profile" + ], - StaticLoginProfile.Key.bookmarkName.settingsKey : [ - .type : OCClassSettingsMetadataType.string, - .label : "Bookmark Name", - .description : "The name that should be used for the bookmark that's generated from this profile and appears in the account list.", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding", - .subCategory : "Profile" - ], + StaticLoginProfile.Key.bookmarkName.settingsKey : [ + .type : OCClassSettingsMetadataType.string, + .label : "Bookmark Name", + .description : "The name that should be used for the bookmark that's generated from this profile and appears in the account list.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding", + .subCategory : "Profile" + ], - StaticLoginProfile.Key.url.settingsKey : [ - .type : OCClassSettingsMetadataType.urlString, - .label : "URL", - .description : "The URL of the server targeted by this profile.", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding", - .subCategory : "Profile" - ], + StaticLoginProfile.Key.url.settingsKey : [ + .type : OCClassSettingsMetadataType.urlString, + .label : "URL", + .description : "The URL of the server targeted by this profile.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding", + .subCategory : "Profile" + ], - StaticLoginProfile.Key.helpURL.settingsKey : [ - .type : OCClassSettingsMetadataType.urlString, - .label : "Onboarding URL", - .description : "Optional URL to onboarding resources.", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding", - .subCategory : "Profile" - ], + StaticLoginProfile.Key.helpURL.settingsKey : [ + .type : OCClassSettingsMetadataType.urlString, + .label : "Onboarding URL", + .description : "Optional URL to onboarding resources.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding", + .subCategory : "Profile" + ], - StaticLoginProfile.Key.promptForHelpURL.settingsKey : [ - .type : OCClassSettingsMetadataType.string, - .label : "Open onboarding URL message", - .description : "Message shown in an alert before opening the onboarding URL.", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding", - .subCategory : "Profile" - ], + StaticLoginProfile.Key.promptForHelpURL.settingsKey : [ + .type : OCClassSettingsMetadataType.string, + .label : "Open onboarding URL message", + .description : "Message shown in an alert before opening the onboarding URL.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding", + .subCategory : "Profile" + ], - StaticLoginProfile.Key.helpURLButtonString.settingsKey : [ - .type : OCClassSettingsMetadataType.string, - .label : "Onboarding button title", - .description : "Text used for the onboarding button title", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding", - .subCategory : "Profile" - ], + StaticLoginProfile.Key.helpURLButtonString.settingsKey : [ + .type : OCClassSettingsMetadataType.string, + .label : "Onboarding button title", + .description : "Text used for the onboarding button title", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding", + .subCategory : "Profile" + ], - StaticLoginProfile.Key.canConfigureURL.settingsKey : [ - .type : OCClassSettingsMetadataType.boolean, - .label : "Allow URL configuration", - .description : "Indicates if the user can change the server URL for the account.", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding", - .subCategory : "Profile" - ], + StaticLoginProfile.Key.canConfigureURL.settingsKey : [ + .type : OCClassSettingsMetadataType.boolean, + .label : "Allow URL configuration", + .description : "Indicates if the user can change the server URL for the account.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding", + .subCategory : "Profile" + ], - StaticLoginProfile.Key.allowedAuthenticationMethods.settingsKey : [ - .type : OCClassSettingsMetadataType.stringArray, - .label : "Allowed authentication methods", - .description : "The identifiers of the authentication methods allowed for this profile. Allows to f.ex. force OAuth2, or to use Basic Auth even if OAuth2 is available.", - .status : OCClassSettingsKeyStatus.advanced, - .possibleValues : OCConnection.authenticationMethodIdentifierMetadata(), - .category : "Branding", - .subCategory : "Profile" - ], + StaticLoginProfile.Key.allowedAuthenticationMethods.settingsKey : [ + .type : OCClassSettingsMetadataType.stringArray, + .label : "Allowed authentication methods", + .description : "The identifiers of the authentication methods allowed for this profile. Allows to f.ex. force OAuth2, or to use Basic Auth even if OAuth2 is available.", + .status : OCClassSettingsKeyStatus.advanced, + .possibleValues : OCConnection.authenticationMethodIdentifierMetadata(), + .category : "Branding", + .subCategory : "Profile" + ], - StaticLoginProfile.Key.allowedHosts.settingsKey : [ - .type : OCClassSettingsMetadataType.stringArray, - .label : "Allowed Hosts", - .description : "Domain names (can also include subdomain name), which are allowed as server url when adding a new account.", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding", - .subCategory : "Profile" - ] - ]) - } + StaticLoginProfile.Key.allowedHosts.settingsKey : [ + .type : OCClassSettingsMetadataType.stringArray, + .label : "Allowed Hosts", + .description : "Domain names (can also include subdomain name), which are allowed as server url when adding a new account.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding", + .subCategory : "Profile" + ] + ]) } static public func composeBrandingDict() -> [String : Any]? { diff --git a/ownCloud/Tasks/ScheduledTaskManager.swift b/ownCloud/Tasks/ScheduledTaskManager.swift index b85555746..c830dca55 100644 --- a/ownCloud/Tasks/ScheduledTaskManager.swift +++ b/ownCloud/Tasks/ScheduledTaskManager.swift @@ -145,11 +145,9 @@ class ScheduledTaskManager : NSObject { locationManager.delegate = self #endif - if #available(iOS 13, *) { - BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.owncloud.background-refresh-task", using: nil) { (task) in - if let refreshTask = task as? BGAppRefreshTask { - self.handleBackgroundRefresh(task: refreshTask) - } + BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.owncloud.background-refresh-task", using: nil) { (task) in + if let refreshTask = task as? BGAppRefreshTask { + self.handleBackgroundRefresh(task: refreshTask) } } } @@ -163,14 +161,14 @@ class ScheduledTaskManager : NSObject { #if !DISABLE_BACKGROUND_LOCATION stopLocationMonitoring() #endif + case UIApplication.didEnterBackgroundNotification: state = .background #if !DISABLE_BACKGROUND_LOCATION startLocationMonitoringIfAuthorized() #endif - if #available(iOS 13, *) { - scheduleBackgroundRefreshTask() - } + scheduleBackgroundRefreshTask() + default: break } @@ -279,15 +277,13 @@ class ScheduledTaskManager : NSObject { fetchCompletion?(.noData) } - if #available(iOS 13, *) { - if let activeTask = self.activeRefreshTask as? BGAppRefreshTask { - // Schedule the next refresh - self.scheduleBackgroundRefreshTask() + if let activeTask = self.activeRefreshTask as? BGAppRefreshTask { + // Schedule the next refresh + self.scheduleBackgroundRefreshTask() - activeTask.setTaskCompleted(success: true) + activeTask.setTaskCompleted(success: true) - self.activeRefreshTask = nil - } + self.activeRefreshTask = nil } } Log.debug(tagged: ["TASK_MANAGER"], "All tasks executed") diff --git a/ownCloudAppFramework/Branding/Branding.m b/ownCloudAppFramework/Branding/Branding.m index c66bcdb37..52b26d5b4 100644 --- a/ownCloudAppFramework/Branding/Branding.m +++ b/ownCloudAppFramework/Branding/Branding.m @@ -34,104 +34,7 @@ + (void)load // Provide hook to allow Swift extensions in the app to register defaults and metadata if ([self conformsToProtocol:@protocol(BrandingInitialization)]) { - if (@available(iOS 13, *)) - { - [((Class)self) initializeBranding]; - } - else - { - // BEGIN: Workaround for iOS 12 Swift crash bug - this code is usually in +initializeBranding in ownCloudAppShared.framework - remove when dropping iOS 12 support - [self registerOCClassSettingsDefaults:@{ - @"url-documentation" : @"https://doc.owncloud.com/ios-app/", - @"url-help" : @"https://owncloud.com/docs-guides/", - @"url-privacy" : @"https://owncloud.org/privacy-policy/", - @"url-terms-of-use" : @"https://raw.githubusercontent.com/owncloud/ios-app/master/LICENSE", - - @"send-feedback-address" : @"ios-app@owncloud.com", - - @"can-add-account" : @(YES), - @"can-edit-account" : @(YES), - @"enable-review-prompt" : @(NO) - } metadata:@{ - @"url-documentation" : @{ - OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeURLString, - OCClassSettingsMetadataKeyDescription : @"URL to documentation for the app. Opened when selecting \"Documentation\" in the settings.", - OCClassSettingsMetadataKeyStatus : OCClassSettingsKeyStatusAdvanced, - OCClassSettingsMetadataKeyCategory : @"Branding" - }, - - @"url-help" : @{ - OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeURLString, - OCClassSettingsMetadataKeyDescription : @"URL to help for the app. Opened when selecting \"Help\" in the settings.", - OCClassSettingsMetadataKeyStatus : OCClassSettingsKeyStatusAdvanced, - OCClassSettingsMetadataKeyCategory : @"Branding" - }, - - @"url-privacy" : @{ - OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeURLString, - OCClassSettingsMetadataKeyDescription : @"URL to privacy information for the app. Opened when selecting \"Privacy\" in the settings.", - OCClassSettingsMetadataKeyStatus : OCClassSettingsKeyStatusAdvanced, - OCClassSettingsMetadataKeyCategory : @"Branding" - }, - - @"url-terms-of-use" : @{ - OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeURLString, - OCClassSettingsMetadataKeyDescription : @"URL to terms of use for the app. Opened when selecting \"Terms Of Use\" in the settings.", - OCClassSettingsMetadataKeyStatus : OCClassSettingsKeyStatusAdvanced, - OCClassSettingsMetadataKeyCategory : @"Branding" - }, - - @"send-feedback-address" : @{ - OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeString, - OCClassSettingsMetadataKeyDescription : @"Email address to send feedback to. Set to `null` to disable this feature.", - OCClassSettingsMetadataKeyCategory : @"Branding", - OCClassSettingsMetadataKeyStatus : OCClassSettingsKeyStatusAdvanced - }, - - @"can-add-account" : @{ - OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeBoolean, - OCClassSettingsMetadataKeyDescription : @"Controls whether the user can add accounts.", - OCClassSettingsMetadataKeyCategory : @"Branding", - OCClassSettingsMetadataKeyStatus : OCClassSettingsKeyStatusAdvanced - }, - - @"can-edit-account" : @{ - OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeBoolean, - OCClassSettingsMetadataKeyDescription : @"Controls whether the user can edit accounts.", - OCClassSettingsMetadataKeyCategory : @"Branding", - OCClassSettingsMetadataKeyStatus : OCClassSettingsKeyStatusAdvanced - }, - - @"enable-review-prompt" : @{ - OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeBoolean, - OCClassSettingsMetadataKeyDescription : @"Controls whether the app should prompt for an App Store review.", - OCClassSettingsMetadataKeyCategory : @"Branding", - OCClassSettingsMetadataKeyStatus : OCClassSettingsKeyStatusAdvanced - }, - - @"profile-definitions" : @{ - OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeDictionaryArray, - OCClassSettingsMetadataKeyDescription : @"Array of dictionaries, each specifying a static profile.", - OCClassSettingsMetadataKeyCategory : @"Branding", - OCClassSettingsMetadataKeyStatus : OCClassSettingsKeyStatusAdvanced - }, - - @"theme-generic-colors" : @{ - OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeDictionary, - OCClassSettingsMetadataKeyDescription : @"Dictionary defining generic colors that can be used in the definitions.", - OCClassSettingsMetadataKeyCategory : @"Branding", - OCClassSettingsMetadataKeyStatus : OCClassSettingsKeyStatusAdvanced - }, - - @"theme-definitions" : @{ - OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeDictionaryArray, - OCClassSettingsMetadataKeyDescription : @"Array of dictionaries, each specifying a theme.", - OCClassSettingsMetadataKeyCategory : @"Branding", - OCClassSettingsMetadataKeyStatus : OCClassSettingsKeyStatusAdvanced - } - }]; - // END: Workaround for iOS 12 Swift crash bug - this code is usually in +initializeBranding in ownCloudAppShared.framework - remove when dropping iOS 12 support - } + [((Class)self) initializeBranding]; } // Provide hook to allow Swift extensions in the app to register defaults and metadata diff --git a/ownCloudAppShared/App Extensions/AppExtensionNavigationController.swift b/ownCloudAppShared/App Extensions/AppExtensionNavigationController.swift index d02730018..d2de3b264 100644 --- a/ownCloudAppShared/App Extensions/AppExtensionNavigationController.swift +++ b/ownCloudAppShared/App Extensions/AppExtensionNavigationController.swift @@ -33,10 +33,8 @@ open class AppExtensionNavigationController: ThemeNavigationController { public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) - if #available(iOS 13.0, *) { - if self.traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { - ThemeStyle.considerAppearanceUpdate() - } + if self.traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { + ThemeStyle.considerAppearanceUpdate() } } diff --git a/ownCloudAppShared/AppLock/AppLockManager.swift b/ownCloudAppShared/AppLock/AppLockManager.swift index 19736b4dc..904bb764f 100644 --- a/ownCloudAppShared/AppLock/AppLockManager.swift +++ b/ownCloudAppShared/AppLock/AppLockManager.swift @@ -228,15 +228,12 @@ public class AppLockManager: NSObject { var appLockWindow : AppLockWindow let passcodeViewController = passwordViewController() - if #available(iOS 13, *) { - if let windowScene = themeWindow.windowScene { - appLockWindow = AppLockWindow(windowScene: windowScene) - } else { - appLockWindow = AppLockWindow(frame: UIScreen.main.bounds) - } + if let windowScene = themeWindow.windowScene { + appLockWindow = AppLockWindow(windowScene: windowScene) } else { appLockWindow = AppLockWindow(frame: UIScreen.main.bounds) } + /* Workaround to the lack of status bar animation when returning true for prefersStatusBarHidden in PasscodeViewController. diff --git a/ownCloudAppShared/AppLock/PasscodeViewController.swift b/ownCloudAppShared/AppLock/PasscodeViewController.swift index 7fb5ae2b1..5d0a653c8 100644 --- a/ownCloudAppShared/AppLock/PasscodeViewController.swift +++ b/ownCloudAppShared/AppLock/PasscodeViewController.swift @@ -159,13 +159,11 @@ public class PasscodeViewController: UIViewController, Themeable { self.errorMessageLabel?.adjustsFontSizeToFitWidth = true updateKeypadButtons() - if #available(iOS 13.4, *) { - for button in keypadButtons! { - PointerEffect.install(on: button, effectStyle: .highlight) - } - PointerEffect.install(on: cancelButton!, effectStyle: .highlight) - PointerEffect.install(on: deleteButton!, effectStyle: .highlight) + for button in keypadButtons! { + PointerEffect.install(on: button, effectStyle: .highlight) } + PointerEffect.install(on: cancelButton!, effectStyle: .highlight) + PointerEffect.install(on: deleteButton!, effectStyle: .highlight) } public override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { @@ -286,11 +284,7 @@ public class PasscodeViewController: UIViewController, Themeable { // MARK: - Themeing public override var preferredStatusBarStyle : UIStatusBarStyle { if VendorServices.shared.isBranded { - if #available(iOSApplicationExtension 13.0, *) { - return .darkContent - } else { - return .default - } + return .darkContent } return Theme.shared.activeCollection.statusBarStyle diff --git a/ownCloudAppShared/Branding/Branding+App.swift b/ownCloudAppShared/Branding/Branding+App.swift index 71cc251de..084b6b265 100644 --- a/ownCloudAppShared/Branding/Branding+App.swift +++ b/ownCloudAppShared/Branding/Branding+App.swift @@ -36,117 +36,115 @@ extension OCClassSettingsKey { extension Branding : BrandingInitialization { public static func initializeBranding() { - if #available(iOS 13, *) { - self.registerOCClassSettingsDefaults([ - .documentationURL : "https://doc.owncloud.com/ios-app/latest/", - .helpURL : "https://owncloud.com/docs-guides/", - .privacyURL : "https://owncloud.org/privacy-policy/", - .termsOfUseURL : "https://raw.githubusercontent.com/owncloud/ios-app/master/LICENSE", - - .sendFeedbackAddress : "ios-app@owncloud.com", - - .canAddAccount : true, - .canEditAccount : true, - .enableReviewPrompt : false - - // .profileDefinitions : [], - // .themeGenericColors : [:], - // .themeDefinitions : [:] - ], metadata: [ - .documentationURL : [ - .type : OCClassSettingsMetadataType.urlString, - .label : "Documentation URL", - .description : "URL to documentation for the app. Opened when selecting \"Documentation\" in the settings.", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding" - ], - - .helpURL : [ - .type : OCClassSettingsMetadataType.urlString, - .label : "Help URL", - .description : "URL to get help for the app. Opened when selecting \"Help\" in the settings.", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding" - ], - - .privacyURL : [ - .type : OCClassSettingsMetadataType.urlString, - .label : "Privacy URL", - .description : "URL to get privacy information for the app. Opened when selecting \"Privacy\" in the settings.", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding" - ], - - .termsOfUseURL : [ - .type : OCClassSettingsMetadataType.urlString, - .label : "Terms of use URL", - .description : "URL to terms of use for the app. Opened when selecting \"Terms Of Use\" in the settings.", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding" - ], - - .sendFeedbackAddress : [ - .type : OCClassSettingsMetadataType.string, - .label : "Feedback Email address", - .description : "Email address to send feedback to. Set to `null` to disable this feature.", - .category : "Branding", - .status : OCClassSettingsKeyStatus.advanced - ], - - .sendFeedbackURL : [ - .type : OCClassSettingsMetadataType.string, - .label : "Feedback URL", - .description : "URL to open when selecting the \"Send feedback\" option. Allows the use of all placeholders provided in `http.user-agent`.", - .category : "Branding", - .status : OCClassSettingsKeyStatus.advanced - ], - - .canAddAccount : [ - .type : OCClassSettingsMetadataType.boolean, - .label : "Allow adding accounts", - .description : "Controls whether the user can add accounts.", - .category : "Branding", - .status : OCClassSettingsKeyStatus.advanced - ], - - .canEditAccount : [ - .type : OCClassSettingsMetadataType.boolean, - .label : "Allow editing accounts", - .description : "Controls whether the user can edit accounts.", - .category : "Branding", - .status : OCClassSettingsKeyStatus.advanced - ], - - .enableReviewPrompt : [ - .type : OCClassSettingsMetadataType.boolean, - .description : "Controls whether the app should prompt for an App Store review. Only applies if the app is branded.", - .category : "Branding", - .status : OCClassSettingsKeyStatus.advanced - ], - - .profileDefinitions : [ - .type : OCClassSettingsMetadataType.dictionaryArray, - .label : "Profile definitions", - .description : "Array of dictionaries, each specifying a profile. All `Profile` keys can be used in the profile dictionaries.", - .category : "Branding", - .status : OCClassSettingsKeyStatus.advanced - ], - - .themeGenericColors : [ - .type : OCClassSettingsMetadataType.dictionary, - .description : "Dictionary defining generic colors that can be used in the definitions.", - .category : "Branding", - .status : OCClassSettingsKeyStatus.advanced - ], - - .themeDefinitions : [ - .type : OCClassSettingsMetadataType.dictionaryArray, - .description : "Array of dictionaries, each specifying a theme.", - .category : "Branding", - .status : OCClassSettingsKeyStatus.advanced - ] - ]) - } + self.registerOCClassSettingsDefaults([ + .documentationURL : "https://doc.owncloud.com/ios-app/latest/", + .helpURL : "https://owncloud.com/docs-guides/", + .privacyURL : "https://owncloud.org/privacy-policy/", + .termsOfUseURL : "https://raw.githubusercontent.com/owncloud/ios-app/master/LICENSE", + + .sendFeedbackAddress : "ios-app@owncloud.com", + + .canAddAccount : true, + .canEditAccount : true, + .enableReviewPrompt : false + +// .profileDefinitions : [], +// .themeGenericColors : [:], +// .themeDefinitions : [:] + ], metadata: [ + .documentationURL : [ + .type : OCClassSettingsMetadataType.urlString, + .label : "Documentation URL", + .description : "URL to documentation for the app. Opened when selecting \"Documentation\" in the settings.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding" + ], + + .helpURL : [ + .type : OCClassSettingsMetadataType.urlString, + .label : "Help URL", + .description : "URL to get help for the app. Opened when selecting \"Help\" in the settings.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding" + ], + + .privacyURL : [ + .type : OCClassSettingsMetadataType.urlString, + .label : "Privacy URL", + .description : "URL to get privacy information for the app. Opened when selecting \"Privacy\" in the settings.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding" + ], + + .termsOfUseURL : [ + .type : OCClassSettingsMetadataType.urlString, + .label : "Terms of use URL", + .description : "URL to terms of use for the app. Opened when selecting \"Terms Of Use\" in the settings.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding" + ], + + .sendFeedbackAddress : [ + .type : OCClassSettingsMetadataType.string, + .label : "Feedback Email address", + .description : "Email address to send feedback to. Set to `null` to disable this feature.", + .category : "Branding", + .status : OCClassSettingsKeyStatus.advanced + ], + + .sendFeedbackURL : [ + .type : OCClassSettingsMetadataType.string, + .label : "Feedback URL", + .description : "URL to open when selecting the \"Send feedback\" option. Allows the use of all placeholders provided in `http.user-agent`.", + .category : "Branding", + .status : OCClassSettingsKeyStatus.advanced + ], + + .canAddAccount : [ + .type : OCClassSettingsMetadataType.boolean, + .label : "Allow adding accounts", + .description : "Controls whether the user can add accounts.", + .category : "Branding", + .status : OCClassSettingsKeyStatus.advanced + ], + + .canEditAccount : [ + .type : OCClassSettingsMetadataType.boolean, + .label : "Allow editing accounts", + .description : "Controls whether the user can edit accounts.", + .category : "Branding", + .status : OCClassSettingsKeyStatus.advanced + ], + + .enableReviewPrompt : [ + .type : OCClassSettingsMetadataType.boolean, + .description : "Controls whether the app should prompt for an App Store review. Only applies if the app is branded.", + .category : "Branding", + .status : OCClassSettingsKeyStatus.advanced + ], + + .profileDefinitions : [ + .type : OCClassSettingsMetadataType.dictionaryArray, + .label : "Profile definitions", + .description : "Array of dictionaries, each specifying a profile. All `Profile` keys can be used in the profile dictionaries.", + .category : "Branding", + .status : OCClassSettingsKeyStatus.advanced + ], + + .themeGenericColors : [ + .type : OCClassSettingsMetadataType.dictionary, + .description : "Dictionary defining generic colors that can be used in the definitions.", + .category : "Branding", + .status : OCClassSettingsKeyStatus.advanced + ], + + .themeDefinitions : [ + .type : OCClassSettingsMetadataType.dictionaryArray, + .description : "Array of dictionaries, each specifying a theme.", + .category : "Branding", + .status : OCClassSettingsKeyStatus.advanced + ] + ]) } public func initializeSharedBranding() { diff --git a/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift b/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift index 93d3c9d52..68ec8efbb 100644 --- a/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift +++ b/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift @@ -527,15 +527,13 @@ open class ClientQueryViewController: QueryFileListTableViewController, UIDropIn } } - if #available(iOS 13, *) { - // On iOS 13.0/13.1, the table view's content needs to be inset by the height of the arrow - // (this can hopefully be removed again in the future, if/when Apple addresses the issue) - let popoverArrowHeight : CGFloat = 13 + // On iOS 13.0/13.1, the table view's content needs to be inset by the height of the arrow + // (this can hopefully be removed again in the future, if/when Apple addresses the issue) + let popoverArrowHeight : CGFloat = 13 - tableViewController.tableView.contentInsetAdjustmentBehavior = .never - tableViewController.tableView.contentInset = UIEdgeInsets(top: popoverArrowHeight, left: 0, bottom: 0, right: 0) - tableViewController.tableView.separatorInset = UIEdgeInsets() - } + tableViewController.tableView.contentInsetAdjustmentBehavior = .never + tableViewController.tableView.contentInset = UIEdgeInsets(top: popoverArrowHeight, left: 0, bottom: 0, right: 0) + tableViewController.tableView.separatorInset = UIEdgeInsets() let popoverPresentationController = tableViewController.popoverPresentationController popoverPresentationController?.sourceView = sender @@ -588,12 +586,10 @@ open class ClientQueryViewController: QueryFileListTableViewController, UIDropIn self.updateFooter(text: totalSize) } - if #available(iOS 13.0, *) { - if let bookmarkContainer = self.tabBarController as? BookmarkContainer { - // Use parent folder for UI state restoration - let activity = OpenItemUserActivity(detailItem: rootItem, detailBookmark: bookmarkContainer.bookmark) - view.window?.windowScene?.userActivity = activity.openItemUserActivity - } + if let bookmarkContainer = self.tabBarController as? BookmarkContainer { + // Use parent folder for UI state restoration + let activity = OpenItemUserActivity(detailItem: rootItem, detailBookmark: bookmarkContainer.bookmark) + view.window?.windowScene?.userActivity = activity.openItemUserActivity } } else { self.updateFooter(text: nil) diff --git a/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift b/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift index fb9c309ca..25f0311ac 100644 --- a/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift +++ b/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift @@ -377,15 +377,10 @@ open class QueryFileListTableViewController: FileListTableViewController, SortBa // Needs to be done here, because of an iOS 13 bug. Do not move to viewDidLoad! let placeholderString = (searchScope == .global) ? "Search account".localized : "Search folder".localized - if #available(iOS 13.0, *) { - let attributedStringColor = [NSAttributedString.Key.foregroundColor : Theme.shared.activeCollection.searchBarColors.secondaryLabelColor] - let attributedString = NSAttributedString(string: placeholderString, attributes: attributedStringColor) - searchController?.searchBar.searchTextField.attributedPlaceholder = attributedString - searchController?.searchBar.searchTextField.textColor = Theme.shared.activeCollection.searchBarColors.labelColor - } else { - // Fallback on earlier versions - searchController?.searchBar.placeholder = placeholderString - } + let attributedStringColor = [NSAttributedString.Key.foregroundColor : Theme.shared.activeCollection.searchBarColors.secondaryLabelColor] + let attributedString = NSAttributedString(string: placeholderString, attributes: attributedStringColor) + searchController?.searchBar.searchTextField.attributedPlaceholder = attributedString + searchController?.searchBar.searchTextField.textColor = Theme.shared.activeCollection.searchBarColors.labelColor } open override func viewWillAppear(_ animated: Bool) { diff --git a/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift b/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift index 2261aa77b..0301a304a 100644 --- a/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift +++ b/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift @@ -151,15 +151,10 @@ open class GroupSharingTableViewController: SharingTableViewController, UISearch super.viewDidLayoutSubviews() // Needs to be done here, because of an iOS 13 bug. Do not move to viewDidLoad! - if #available(iOS 13.0, *) { - let attributedStringColor = [NSAttributedString.Key.foregroundColor : Theme.shared.activeCollection.searchBarColors.secondaryLabelColor] - let attributedString = NSAttributedString(string: "Add email or name".localized, attributes: attributedStringColor) - searchController?.searchBar.searchTextField.attributedPlaceholder = attributedString - searchController?.searchBar.searchTextField.textColor = Theme.shared.activeCollection.searchBarColors.labelColor - } else { - // Fallback on earlier versions - searchController?.searchBar.placeholder = "Add email or name".localized - } + let attributedStringColor = [NSAttributedString.Key.foregroundColor : Theme.shared.activeCollection.searchBarColors.secondaryLabelColor] + let attributedString = NSAttributedString(string: "Add email or name".localized, attributes: attributedStringColor) + searchController?.searchBar.searchTextField.attributedPlaceholder = attributedString + searchController?.searchBar.searchTextField.textColor = Theme.shared.activeCollection.searchBarColors.labelColor } open override func viewDidAppear(_ animated: Bool) { @@ -521,8 +516,6 @@ open class GroupSharingTableViewController: SharingTableViewController, UISearch open override func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { super.applyThemeCollection(theme: theme, collection: collection, event: event) - if #available(iOS 13, *) { - self.searchController?.searchBar.overrideUserInterfaceStyle = collection.interfaceStyle.userInterfaceStyle - } + self.searchController?.searchBar.overrideUserInterfaceStyle = collection.interfaceStyle.userInterfaceStyle } } diff --git a/ownCloudAppShared/Client/User Interface/ClientItemCell.swift b/ownCloudAppShared/Client/User Interface/ClientItemCell.swift index f680f86e6..a4242198b 100644 --- a/ownCloudAppShared/Client/User Interface/ClientItemCell.swift +++ b/ownCloudAppShared/Client/User Interface/ClientItemCell.swift @@ -135,9 +135,7 @@ open class ClientItemCell: ThemeTableViewCell { NotificationCenter.default.addObserver(self, selector: #selector(updateHasMessage(_:)), name: .ClientSyncRecordIDsWithMessagesChanged, object: nil) - if #available(iOS 13.4, *) { - PointerEffect.install(on: self.contentView, effectStyle: .hover) - } + PointerEffect.install(on: self.contentView, effectStyle: .hover) } required public init?(coder aDecoder: NSCoder) { @@ -198,25 +196,17 @@ open class ClientItemCell: ThemeTableViewCell { moreButton.setImage(UIImage(named: "more-dots"), for: .normal) moreButton.contentMode = .center - if #available(iOS 13.4, *) { - moreButton.isPointerInteractionEnabled = true - } + moreButton.isPointerInteractionEnabled = true - if #available(iOS 13.4, *) { - revealButton.setImage(UIImage(systemName: "arrow.right.circle.fill"), for: .normal) - revealButton.isPointerInteractionEnabled = true - } else { - revealButton.setTitle("→", for: .normal) - } + revealButton.setImage(UIImage(systemName: "arrow.right.circle.fill"), for: .normal) + revealButton.isPointerInteractionEnabled = true revealButton.contentMode = .center revealButton.isHidden = !showRevealButton revealButton.accessibilityLabel = "Reveal in folder".localized messageButton.setTitle("⚠️", for: .normal) messageButton.contentMode = .center - if #available(iOS 13.4, *) { - messageButton.isPointerInteractionEnabled = true - } + messageButton.isPointerInteractionEnabled = true messageButton.isHidden = true moreButton.addTarget(self, action: #selector(moreButtonTapped), for: .touchUpInside) diff --git a/ownCloudAppShared/Client/User Interface/SortBar.swift b/ownCloudAppShared/Client/User Interface/SortBar.swift index e1d77638f..3a8b9d82f 100644 --- a/ownCloudAppShared/Client/User Interface/SortBar.swift +++ b/ownCloudAppShared/Client/User Interface/SortBar.swift @@ -244,9 +244,7 @@ public class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate selectButton.tintColor = Theme.shared.activeCollection.favoriteEnabledColor selectButton.addTarget(self, action: #selector(toggleSelectMode), for: .touchUpInside) selectButton.accessibilityLabel = "Enter multiple selection".localized - if #available(iOS 13.4, *) { - selectButton.isPointerInteractionEnabled = true - } + selectButton.isPointerInteractionEnabled = true NSLayoutConstraint.activate([ selectButton.centerYAnchor.constraint(equalTo: self.centerYAnchor), @@ -359,15 +357,12 @@ public class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate tableViewController.sortBarDelegate = self.delegate tableViewController.sortBar = self - if #available(iOS 13, *) { - // On iOS 13.0/13.1, the table view's content needs to be inset by the height of the arrow - // (this can hopefully be removed again in the future, if/when Apple addresses the issue) - let popoverArrowHeight : CGFloat = 13 - - tableViewController.tableView.contentInsetAdjustmentBehavior = .never - tableViewController.tableView.contentInset = UIEdgeInsets(top: popoverArrowHeight, left: 0, bottom: 0, right: 0) - tableViewController.tableView.separatorInset = UIEdgeInsets() - } + // On iOS 13.0/13.1, the table view's content needs to be inset by the height of the arrow + // (this can hopefully be removed again in the future, if/when Apple addresses the issue) + let popoverArrowHeight : CGFloat = 13 + tableViewController.tableView.contentInsetAdjustmentBehavior = .never + tableViewController.tableView.contentInset = UIEdgeInsets(top: popoverArrowHeight, left: 0, bottom: 0, right: 0) + tableViewController.tableView.separatorInset = UIEdgeInsets() let popoverPresentationController = tableViewController.popoverPresentationController popoverPresentationController?.sourceView = sender diff --git a/ownCloudAppShared/UIKit Extension/UIKeyCommand+Extension.swift b/ownCloudAppShared/UIKit Extension/UIKeyCommand+Extension.swift new file mode 100644 index 000000000..61d050281 --- /dev/null +++ b/ownCloudAppShared/UIKit Extension/UIKeyCommand+Extension.swift @@ -0,0 +1,29 @@ +// +// UIKeyCommand+Extension.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 24.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit + +public extension UIKeyCommand { + static func ported(input: String, modifierFlags: UIKeyModifierFlags, action: Selector, discoverabilityTitle: String) -> UIKeyCommand { + let keyCommand = UIKeyCommand(input: input, modifierFlags: modifierFlags, action: action) + + keyCommand.discoverabilityTitle = discoverabilityTitle + + return keyCommand + } +} diff --git a/ownCloudAppShared/User Interface/Cursor Support/PointerEffect.swift b/ownCloudAppShared/User Interface/Cursor Support/PointerEffect.swift index 060a2a439..a2e580f1e 100644 --- a/ownCloudAppShared/User Interface/Cursor Support/PointerEffect.swift +++ b/ownCloudAppShared/User Interface/Cursor Support/PointerEffect.swift @@ -26,11 +26,9 @@ public enum PointerEffectStyle : Int { public class PointerEffect : NSObject, UIPointerInteractionDelegate { public static func install(on view: UIView, effectStyle: PointerEffectStyle) { - if #available(iOS 13.4, *) { - let effect = PointerEffect(for: view, effectStyle: effectStyle) + let effect = PointerEffect(for: view, effectStyle: effectStyle) - objc_setAssociatedObject(view, &effect.objcAssociationHandle, effect, .OBJC_ASSOCIATION_RETAIN) - } + objc_setAssociatedObject(view, &effect.objcAssociationHandle, effect, .OBJC_ASSOCIATION_RETAIN) } private var objcAssociationHandle = 1 @@ -40,19 +38,15 @@ public class PointerEffect : NSObject, UIPointerInteractionDelegate { self.effectStyle = effectStyle super.init() - if #available(iOS 13.4, *) { - customPointerInteraction(on: view, pointerInteractionDelegate: self) - } + customPointerInteraction(on: view, pointerInteractionDelegate: self) } // MARK: - UIPointerInteractionDelegate - @available(iOS 13.4, *) private func customPointerInteraction(on view: UIView, pointerInteractionDelegate: UIPointerInteractionDelegate) { let pointerInteraction = UIPointerInteraction(delegate: pointerInteractionDelegate) view.addInteraction(pointerInteraction) } - @available(iOS 13.4, *) private func pointerInteraction(_ interaction: UIPointerInteraction, styleFor region: UIPointerRegion) -> UIPointerStyle? { var pointerStyle: UIPointerStyle? diff --git a/ownCloudAppShared/User Interface/More/MoreViewHeader.swift b/ownCloudAppShared/User Interface/More/MoreViewHeader.swift index cf8bce2f6..2b9ea0a5a 100644 --- a/ownCloudAppShared/User Interface/More/MoreViewHeader.swift +++ b/ownCloudAppShared/User Interface/More/MoreViewHeader.swift @@ -49,7 +49,7 @@ open class MoreViewHeader: UIView { detailLabel = UILabel() labelContainerView = UIView() favoriteButton = UIButton() - activityIndicator = UIActivityIndicatorView(style: .white) + activityIndicator = UIActivityIndicatorView(style: .medium) self.adaptBackgroundColor = adaptBackgroundColor super.init(frame: .zero) @@ -73,7 +73,7 @@ open class MoreViewHeader: UIView { detailLabel = UILabel() labelContainerView = UIView() favoriteButton = UIButton() - activityIndicator = UIActivityIndicatorView(style: .white) + activityIndicator = UIActivityIndicatorView(style: .medium) super.init(frame: .zero) @@ -139,9 +139,7 @@ open class MoreViewHeader: UIView { updateFavoriteButtonImage() favoriteButton.addTarget(self, action: #selector(toogleFavoriteState), for: UIControl.Event.touchUpInside) self.addSubview(favoriteButton) - if #available(iOS 13.4, *) { - favoriteButton.isPointerInteractionEnabled = true - } + favoriteButton.isPointerInteractionEnabled = true NSLayoutConstraint.activate([ favoriteButton.widthAnchor.constraint(equalToConstant: favoriteSize.width), diff --git a/ownCloudAppShared/User Interface/Progress/ProgressHUDViewController.swift b/ownCloudAppShared/User Interface/Progress/ProgressHUDViewController.swift index 0a466e824..52dff3a14 100644 --- a/ownCloudAppShared/User Interface/Progress/ProgressHUDViewController.swift +++ b/ownCloudAppShared/User Interface/Progress/ProgressHUDViewController.swift @@ -36,7 +36,7 @@ open class ProgressHUDViewController: UIViewController { progressContainer = UIView() progressContainer?.translatesAutoresizingMaskIntoConstraints = false - progressSpinner = UIActivityIndicatorView(style: .whiteLarge) + progressSpinner = UIActivityIndicatorView(style: .large) progressSpinner?.translatesAutoresizingMaskIntoConstraints = false progressLabel = UILabel() diff --git a/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift b/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift index 72766f3ef..d9314c558 100644 --- a/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift +++ b/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift @@ -169,7 +169,7 @@ open class StaticTableViewRow : NSObject, UITextFieldDelegate { themeCell.accessoryView = accessoryView } - if #available(iOS 13.4, *), let cell = self.cell { + if let cell = self.cell { PointerEffect.install(on: cell.contentView, effectStyle: .hover) } @@ -199,7 +199,7 @@ open class StaticTableViewRow : NSObject, UITextFieldDelegate { self.cell?.accessoryView = accessoryView self.cell?.accessibilityIdentifier = identifier - if #available(iOS 13.4, *), let cell = self.cell { + if let cell = self.cell { PointerEffect.install(on: cell.contentView, effectStyle: .hover) } @@ -272,7 +272,7 @@ open class StaticTableViewRow : NSObject, UITextFieldDelegate { ]) } - if #available(iOS 13.4, *), let cell = self.cell { + if let cell = self.cell { PointerEffect.install(on: cell.contentView, effectStyle: .hover) } @@ -293,7 +293,7 @@ open class StaticTableViewRow : NSObject, UITextFieldDelegate { self.cell?.accessibilityIdentifier = identifier - if #available(iOS 13.4, *), let cell = self.cell { + if let cell = self.cell { PointerEffect.install(on: cell.contentView, effectStyle: .hover) } @@ -345,7 +345,7 @@ open class StaticTableViewRow : NSObject, UITextFieldDelegate { self.cell?.accessibilityIdentifier = groupIdentifier + "." + accessibilityIdentifier } - if #available(iOS 13.4, *), let cell = self.cell { + if let cell = self.cell { PointerEffect.install(on: cell.contentView, effectStyle: .hover) } @@ -380,7 +380,7 @@ open class StaticTableViewRow : NSObject, UITextFieldDelegate { self.cell?.detailTextLabel?.numberOfLines = 0 } - if #available(iOS 13.4, *), let cell = self.cell { + if let cell = self.cell { PointerEffect.install(on: cell.contentView, effectStyle: .hover) } @@ -761,7 +761,7 @@ open class StaticTableViewRow : NSObject, UITextFieldDelegate { self.cell?.accessibilityIdentifier = identifier - if #available(iOS 13.4, *), let cell = self.cell { + if let cell = self.cell { PointerEffect.install(on: cell.contentView, effectStyle: .hover) } diff --git a/ownCloudAppShared/User Interface/Theme/NSObject+ThemeApplication.swift b/ownCloudAppShared/User Interface/Theme/NSObject+ThemeApplication.swift index cadbb5b4c..4b7d743d0 100644 --- a/ownCloudAppShared/User Interface/Theme/NSObject+ThemeApplication.swift +++ b/ownCloudAppShared/User Interface/Theme/NSObject+ThemeApplication.swift @@ -115,13 +115,12 @@ public extension NSObject { navigationBar.titleTextAttributes = [ .foregroundColor : collection.navigationBarColors.labelColor ] navigationBar.largeTitleTextAttributes = [ .foregroundColor : collection.navigationBarColors.labelColor ] navigationBar.isTranslucent = false - if #available(iOS 13, *) { - let navigationBarAppearance = collection.navigationBarAppearance - navigationBar.standardAppearance = navigationBarAppearance - navigationBar.compactAppearance = navigationBarAppearance - navigationBar.scrollEdgeAppearance = navigationBarAppearance - } + let navigationBarAppearance = collection.navigationBarAppearance + + navigationBar.standardAppearance = navigationBarAppearance + navigationBar.compactAppearance = navigationBarAppearance + navigationBar.scrollEdgeAppearance = navigationBarAppearance } if let toolbar = self as? UIToolbar { @@ -156,26 +155,18 @@ public extension NSObject { searchBar.tintColor = collection.searchBarColors.tintColor searchBar.barStyle = collection.barStyle - if #available(iOS 13, *) { - searchBar.searchTextField.textColor = collection.searchBarColors.labelColor - // Ensure search bar icon color is correct - searchBar.overrideUserInterfaceStyle = collection.interfaceStyle.userInterfaceStyle - searchBar.searchTextField.backgroundColor = collection.searchBarColors.backgroundColor + searchBar.searchTextField.textColor = collection.searchBarColors.labelColor + // Ensure search bar icon color is correct + searchBar.overrideUserInterfaceStyle = collection.interfaceStyle.userInterfaceStyle + searchBar.searchTextField.backgroundColor = collection.searchBarColors.backgroundColor - if let glassIconView = searchBar.searchTextField.leftView as? UIImageView { - glassIconView.image = glassIconView.image?.withRenderingMode(.alwaysTemplate) - glassIconView.tintColor = collection.searchBarColors.secondaryLabelColor - } - if let clearButton = searchBar.searchTextField.value(forKey: "clearButton") as? UIButton { - clearButton.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal) - clearButton.tintColor = collection.searchBarColors.secondaryLabelColor - } - } else { - if let textField = searchBar.subviews.first?.subviews.last as? UITextField, let backgroundview = textField.subviews.first { - backgroundview.backgroundColor = collection.searchBarColors.backgroundColor - backgroundview.layer.cornerRadius = 10 - backgroundview.clipsToBounds = true - } + if let glassIconView = searchBar.searchTextField.leftView as? UIImageView { + glassIconView.image = glassIconView.image?.withRenderingMode(.alwaysTemplate) + glassIconView.tintColor = collection.searchBarColors.secondaryLabelColor + } + if let clearButton = searchBar.searchTextField.value(forKey: "clearButton") as? UIButton { + clearButton.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal) + clearButton.tintColor = collection.searchBarColors.secondaryLabelColor } } @@ -253,9 +244,7 @@ public extension NSObject { } } - if #available(iOS 13, *) { - cell.overrideUserInterfaceStyle = collection.interfaceStyle.userInterfaceStyle - } + cell.overrideUserInterfaceStyle = collection.interfaceStyle.userInterfaceStyle } if let progressView = self as? UIProgressView { @@ -265,22 +254,18 @@ public extension NSObject { if let segmentedControl = self as? UISegmentedControl { var tintColor = collection.tintColor - if let navigationTintColor = collection.navigationBarColors.tintColor { - tintColor = navigationTintColor - } - if #available(iOS 13, *) { - segmentedControl.setTitleTextAttributes([NSAttributedString.Key.foregroundColor : tintColor], for: .normal) - segmentedControl.setTitleTextAttributes([NSAttributedString.Key.foregroundColor : collection.navigationBarColors.backgroundColor!], for: .selected) - segmentedControl.selectedSegmentTintColor = tintColor - } else { - segmentedControl.tintColor = tintColor + + if let navigationTintColor = collection.navigationBarColors.tintColor { + tintColor = navigationTintColor } + + segmentedControl.setTitleTextAttributes([NSAttributedString.Key.foregroundColor : tintColor], for: .normal) + segmentedControl.setTitleTextAttributes([NSAttributedString.Key.foregroundColor : collection.navigationBarColors.backgroundColor!], for: .selected) + segmentedControl.selectedSegmentTintColor = tintColor } if let visualEffectView = self as? UIVisualEffectView { - if #available(iOS 13, *) { - visualEffectView.overrideUserInterfaceStyle = collection.interfaceStyle.userInterfaceStyle - } + visualEffectView.overrideUserInterfaceStyle = collection.interfaceStyle.userInterfaceStyle } } } diff --git a/ownCloudAppShared/User Interface/Theme/Theme.swift b/ownCloudAppShared/User Interface/Theme/Theme.swift index f7d6ef888..0146866bd 100644 --- a/ownCloudAppShared/User Interface/Theme/Theme.swift +++ b/ownCloudAppShared/User Interface/Theme/Theme.swift @@ -229,21 +229,11 @@ public class Theme: NSObject { } // Globally change color values for UI elements - if #available(iOS 13, *) { - } else { - UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).backgroundColor = collection.searchBarColors.backgroundColor - UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).defaultTextAttributes = [NSAttributedString.Key.foregroundColor: collection.searchBarColors.secondaryLabelColor] - UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).tintColor = collection.searchBarColors.tintColor - } UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).keyboardAppearance = collection.keyboardAppearance - if #available(iOS 13, *) { - if VendorServices.shared.isBranded { - UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = .black - } else { - UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = collection.tintColor - } - } else { + if VendorServices.shared.isBranded { UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = .black + } else { + UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = collection.tintColor } UITextField.appearance().tintColor = collection.searchBarColors.tintColor } diff --git a/ownCloudAppShared/User Interface/Theme/ThemeCollection.swift b/ownCloudAppShared/User Interface/Theme/ThemeCollection.swift index 58e5bf405..583cf5f91 100644 --- a/ownCloudAppShared/User Interface/Theme/ThemeCollection.swift +++ b/ownCloudAppShared/User Interface/Theme/ThemeCollection.swift @@ -228,16 +228,9 @@ public class ThemeCollection : NSObject { // Table view self.tableBackgroundColor = colors.resolveColor("Table.tableBackgroundColor", UIColor.white) - if #available(iOS 13, *) { - self.tableGroupBackgroundColor = colors.resolveColor("Table.tableGroupBackgroundColor", UIColor.groupTableViewBackground.resolvedColor(with: UITraitCollection(userInterfaceStyle: .light))) - let color = colors.resolveColor("Table.tableSeparatorColor", UIColor.separator) - self.tableSeparatorColor = color - - } else { - self.tableGroupBackgroundColor = colors.resolveColor("Table.tableGroupBackgroundColor", UIColor.groupTableViewBackground) - let color = colors.resolveColor("Table.tableSeparatorColor", UIColor.lightGray) - self.tableSeparatorColor = color - } + self.tableGroupBackgroundColor = colors.resolveColor("Table.tableGroupBackgroundColor", UIColor.systemGroupedBackground.resolvedColor(with: UITraitCollection(userInterfaceStyle: .light))) + let color = colors.resolveColor("Table.tableSeparatorColor", UIColor.separator) + self.tableSeparatorColor = color self.tableSectionHeaderColor = UIColor.gray self.tableSectionFooterColor = UIColor.gray @@ -318,8 +311,8 @@ public class ThemeCollection : NSObject { self.progressColors = colors.resolveThemeColorPair("Progress", ThemeColorPair(foreground: self.lightBrandColor, background: self.lightBrandColor.withAlphaComponent(0.3))) // Activity - self.activityIndicatorViewStyle = styleResolver.resolveActivityIndicatorViewStyle(for: "activityIndicatorViewStyle", fallback: .white) - self.searchBarActivityIndicatorViewStyle = styleResolver.resolveActivityIndicatorViewStyle(for: "searchBarActivityIndicatorViewStyle", fallback: .white) + self.activityIndicatorViewStyle = styleResolver.resolveActivityIndicatorViewStyle(for: "activityIndicatorViewStyle", fallback: .medium) + self.searchBarActivityIndicatorViewStyle = styleResolver.resolveActivityIndicatorViewStyle(for: "searchBarActivityIndicatorViewStyle", fallback: .medium) // Logo fill color let logoColor : UIColor? = UIColor.white @@ -346,11 +339,7 @@ public class ThemeCollection : NSObject { self.loginColors = colors.resolveThemeColorCollection("Login", self.darkBrandColors) // Bar styles - if #available(iOS 13, *) { - self.statusBarStyle = styleResolver.resolveStatusBarStyle(for: "statusBarStyle", fallback: .darkContent) - } else { - self.statusBarStyle = styleResolver.resolveStatusBarStyle(for: "statusBarStyle", fallback: .default) - } + self.statusBarStyle = styleResolver.resolveStatusBarStyle(for: "statusBarStyle", fallback: .darkContent) self.loginStatusBarStyle = styleResolver.resolveStatusBarStyle(for: "loginStatusBarStyle", fallback: self.statusBarStyle) self.barStyle = styleResolver.resolveBarStyle(fallback: .default) @@ -358,8 +347,8 @@ public class ThemeCollection : NSObject { self.progressColors = colors.resolveThemeColorPair("Progress", ThemeColorPair(foreground: self.lightBrandColor, background: UIColor.lightGray.withAlphaComponent(0.3))) // Activity - self.activityIndicatorViewStyle = styleResolver.resolveActivityIndicatorViewStyle(for: "activityIndicatorViewStyle", fallback: .gray) - self.searchBarActivityIndicatorViewStyle = styleResolver.resolveActivityIndicatorViewStyle(for: "searchBarActivityIndicatorViewStyle", fallback: .gray) + self.activityIndicatorViewStyle = styleResolver.resolveActivityIndicatorViewStyle(for: "activityIndicatorViewStyle", fallback: .medium) + self.searchBarActivityIndicatorViewStyle = styleResolver.resolveActivityIndicatorViewStyle(for: "searchBarActivityIndicatorViewStyle", fallback: .medium) // Logo fill color let logoColor : UIColor? = UIColor.lightGray @@ -400,8 +389,8 @@ public class ThemeCollection : NSObject { self.progressColors = colors.resolveThemeColorPair("Progress", ThemeColorPair(foreground: self.lightBrandColor, background: UIColor.lightGray.withAlphaComponent(0.3))) // Activity - self.activityIndicatorViewStyle = styleResolver.resolveActivityIndicatorViewStyle(for: "activityIndicatorViewStyle", fallback: .gray) - self.searchBarActivityIndicatorViewStyle = styleResolver.resolveActivityIndicatorViewStyle(for: "searchBarActivityIndicatorViewStyle", fallback: .white) + self.activityIndicatorViewStyle = styleResolver.resolveActivityIndicatorViewStyle(for: "activityIndicatorViewStyle", fallback: .medium) + self.searchBarActivityIndicatorViewStyle = styleResolver.resolveActivityIndicatorViewStyle(for: "searchBarActivityIndicatorViewStyle", fallback: .medium) // Logo fill color logoFillColor = UIColor.lightGray @@ -447,11 +436,7 @@ class ThemeStyleValueResolver : NSObject { case "lightContent": return .lightContent case "darkContent": - if #available(iOS 13.0, *) { - return .darkContent - } else { - return fallback - } + return .darkContent default: return fallback } @@ -476,24 +461,10 @@ class ThemeStyleValueResolver : NSObject { func resolveActivityIndicatorViewStyle(for key: String, fallback: UIActivityIndicatorView.Style) -> UIActivityIndicatorView.Style { if let styleValue = styles?.value(forKeyPath: key) as? String { switch styleValue { - case "medium": - if #available(iOS 13.0, *) { - return .medium - } else { - return fallback - } - case "large": - if #available(iOS 13.0, *) { - return .large - } else { - return fallback - } - case "whiteLarge": - return .whiteLarge - case "white": - return .white - case "gray": - return .gray + case "medium", "white", "gray": + return .medium + case "whiteLarge", "large": + return .large default: return fallback } diff --git a/ownCloudAppShared/User Interface/Theme/ThemeStyle+Extensions.swift b/ownCloudAppShared/User Interface/Theme/ThemeStyle+Extensions.swift index eaf5d010e..7d4fb7513 100644 --- a/ownCloudAppShared/User Interface/Theme/ThemeStyle+Extensions.swift +++ b/ownCloudAppShared/User Interface/Theme/ThemeStyle+Extensions.swift @@ -93,7 +93,7 @@ extension ThemeStyle { } static public var displayName : String { - if #available(iOS 13, *), ThemeStyle.followSystemAppearance { + if ThemeStyle.followSystemAppearance { return "System".localized } @@ -109,18 +109,16 @@ extension ThemeStyle { let rootView : UIView? = UserInterfaceContext.shared.rootView var applyStyle : ThemeStyle? = ThemeStyle.preferredStyle - if #available(iOS 13, *) { - if self.followSystemAppearance { - if ThemeStyle.userInterfaceStyle() == .dark { - if let darkStyleIdentifier = ThemeStyle.preferredStyle.darkStyleIdentifier, let style = ThemeStyle.forIdentifier(darkStyleIdentifier) { - ThemeStyle.preferredStyle = style - applyStyle = style - } - } else { - if ThemeStyle.preferredStyle.themeStyle == .dark, let style = ThemeStyle.availableStyles(for: [.contrast])?.first { - ThemeStyle.preferredStyle = style - applyStyle = style - } + if self.followSystemAppearance { + if ThemeStyle.userInterfaceStyle() == .dark { + if let darkStyleIdentifier = ThemeStyle.preferredStyle.darkStyleIdentifier, let style = ThemeStyle.forIdentifier(darkStyleIdentifier) { + ThemeStyle.preferredStyle = style + applyStyle = style + } + } else { + if ThemeStyle.preferredStyle.themeStyle == .dark, let style = ThemeStyle.availableStyles(for: [.contrast])?.first { + ThemeStyle.preferredStyle = style + applyStyle = style } } } @@ -128,11 +126,9 @@ extension ThemeStyle { if let applyStyle = applyStyle { let themeCollection = ThemeCollection(with: applyStyle) - if #available(iOS 13, *) { - if let themeWindowSubviews = rootView?.subviews { - for view in themeWindowSubviews { - view.overrideUserInterfaceStyle = themeCollection.interfaceStyle.userInterfaceStyle - } + if let themeWindowSubviews = rootView?.subviews { + for view in themeWindowSubviews { + view.overrideUserInterfaceStyle = themeCollection.interfaceStyle.userInterfaceStyle } } diff --git a/ownCloudAppShared/User Interface/Theme/UI/ThemeWindow.swift b/ownCloudAppShared/User Interface/Theme/UI/ThemeWindow.swift index 26bcf478c..9bd032b96 100644 --- a/ownCloudAppShared/User Interface/Theme/UI/ThemeWindow.swift +++ b/ownCloudAppShared/User Interface/Theme/UI/ThemeWindow.swift @@ -75,10 +75,8 @@ public class ThemeWindow : UIWindow { public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) - if #available(iOS 13.0, *) { - if self.traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { - ThemeStyle.considerAppearanceUpdate() - } + if self.traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { + ThemeStyle.considerAppearanceUpdate() } } } diff --git a/ownCloudAppShared/User Interface/Theme/UI/ThemedAlertController.swift b/ownCloudAppShared/User Interface/Theme/UI/ThemedAlertController.swift index 0e1752c30..8f6e628a9 100644 --- a/ownCloudAppShared/User Interface/Theme/UI/ThemedAlertController.swift +++ b/ownCloudAppShared/User Interface/Theme/UI/ThemedAlertController.swift @@ -24,9 +24,7 @@ public class ThemedAlertController: UIAlertController, Themeable { override open func viewDidLoad() { super.viewDidLoad() - if #available(iOSApplicationExtension 13.0, *) { - self.overrideUserInterfaceStyle = Theme.shared.activeCollection.interfaceStyle.userInterfaceStyle - } + self.overrideUserInterfaceStyle = Theme.shared.activeCollection.interfaceStyle.userInterfaceStyle view.tintColor = Theme.shared.activeCollection.tableRowColors.labelColor } @@ -36,9 +34,7 @@ public class ThemedAlertController: UIAlertController, Themeable { } open func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { - if #available(iOS 13, *) { - self.overrideUserInterfaceStyle = collection.interfaceStyle.userInterfaceStyle - } + self.overrideUserInterfaceStyle = collection.interfaceStyle.userInterfaceStyle view.tintColor = collection.tableRowColors.labelColor } From 647df31470145bd8c17f9d82f3bf25914ce3867e Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Mon, 24 Jan 2022 22:30:02 +0100 Subject: [PATCH 006/328] - replace deprecated UITableViewRowAction with UIContextualAction across the board --- .../PendingSharesTableViewController.swift | 14 +++++----- .../ServerListTableViewController.swift | 26 +++++++++++-------- .../Settings/LogFilesViewController.swift | 10 +++---- ...ingleAccountServerListViewController.swift | 2 +- .../GroupSharingTableViewController.swift | 12 +++++---- .../PublicLinkTableViewController.swift | 17 +++++++----- 6 files changed, 47 insertions(+), 34 deletions(-) diff --git a/ownCloud/Client/Sharing/PendingSharesTableViewController.swift b/ownCloud/Client/Sharing/PendingSharesTableViewController.swift index 59048c1ad..ec23230b6 100644 --- a/ownCloud/Client/Sharing/PendingSharesTableViewController.swift +++ b/ownCloud/Client/Sharing/PendingSharesTableViewController.swift @@ -188,21 +188,23 @@ class PendingSharesTableViewController: StaticTableViewController { } // MARK: - TableView Delegate - override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { + override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { let row = self.staticRowForIndexPath(indexPath) - guard let share = row.representedObject as? OCShare else { return [] } + guard let share = row.representedObject as? OCShare else { return nil } - let acceptAction = UITableViewRowAction(style: .normal, title: "Accept".localized, handler: { [weak self] (_, _) in + let acceptAction = UIContextualAction(style: .normal, title: "Accept".localized, handler: { [weak self] (_, _, completionHandler) in self?.handleDecision(on: share, accept: true) + completionHandler(true) }) - let declineAction = UITableViewRowAction(style: .destructive, title: "Decline".localized, handler: { [weak self] (_, _) in + let declineAction = UIContextualAction(style: .destructive, title: "Decline".localized, handler: { [weak self] (_, _, completionHandler) in self?.handleDecision(on: share, accept: false) + completionHandler(true) }) if share.state != .rejected { - return [acceptAction, declineAction] + return UISwipeActionsConfiguration(actions: [acceptAction, declineAction]) } else { - return [acceptAction] + return UISwipeActionsConfiguration(actions: [acceptAction]) } } diff --git a/ownCloud/Server List/ServerListTableViewController.swift b/ownCloud/Server List/ServerListTableViewController.swift index ad6a78d57..439ce1f4f 100644 --- a/ownCloud/Server List/ServerListTableViewController.swift +++ b/ownCloud/Server List/ServerListTableViewController.swift @@ -829,10 +829,10 @@ class ServerListTableViewController: UITableViewController, Themeable, StateRest return bookmarkCell } - override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { + override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { if self.isEditing { - return [] + return nil } var destructiveTitle = "Delete".localized @@ -840,7 +840,7 @@ class ServerListTableViewController: UITableViewController, Themeable, StateRest destructiveTitle = "Log out".localized } - let deleteRowAction = UITableViewRowAction(style: .destructive, title: destructiveTitle, handler: { (_, indexPath) in + let deleteRowAction = UIContextualAction(style: .destructive, title: destructiveTitle, handler: { (_, _, completionHandler) in if let bookmark = OCBookmarkManager.shared.bookmark(at: UInt(indexPath.row)) { self.delete(bookmark: bookmark, at: indexPath ) { OnMainThread { @@ -853,41 +853,45 @@ class ServerListTableViewController: UITableViewController, Themeable, StateRest self.didUpdateServerList() }) } + completionHandler(true) } } }) - let editRowAction = UITableViewRowAction(style: .normal, title: "Edit".localized, handler: { [weak self] (_, indexPath) in + let editRowAction = UIContextualAction(style: .normal, title: "Edit".localized, handler: { [weak self] (_, _, completionHandler) in if let bookmark = OCBookmarkManager.shared.bookmark(at: UInt(indexPath.row)) { self?.showBookmarkUI(edit: bookmark) } + completionHandler(true) }) editRowAction.backgroundColor = .blue - let manageRowAction = UITableViewRowAction(style: .normal, + let manageRowAction = UIContextualAction(style: .normal, title: "Manage".localized, - handler: { [weak self] (_, indexPath) in + handler: { [weak self] (_, _, completionHandler) in if let bookmark = OCBookmarkManager.shared.bookmark(at: UInt(indexPath.row)) { self?.showBookmarkInfoUI(bookmark) } + completionHandler(true) }) if UIDevice.current.isIpad { - let openAccountAction = UITableViewRowAction(style: .normal, title: "Open in Window".localized, handler: { (_, indexPath) in + let openAccountAction = UIContextualAction(style: .normal, title: "Open in Window".localized, handler: { (_, _, completionHandler) in self.openAccountInWindow(at: indexPath) + completionHandler(true) }) openAccountAction.backgroundColor = .orange if VendorServices.shared.canEditAccount { - return [deleteRowAction, editRowAction, manageRowAction, openAccountAction] + return UISwipeActionsConfiguration(actions: [deleteRowAction, editRowAction, manageRowAction, openAccountAction]) } - return [deleteRowAction, manageRowAction, openAccountAction] + return UISwipeActionsConfiguration(actions: [deleteRowAction, manageRowAction, openAccountAction]) } if VendorServices.shared.canEditAccount { - return [deleteRowAction, editRowAction, manageRowAction] + return UISwipeActionsConfiguration(actions: [deleteRowAction, editRowAction, manageRowAction]) } - return [deleteRowAction, manageRowAction] + return UISwipeActionsConfiguration(actions: [deleteRowAction, manageRowAction]) } override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { diff --git a/ownCloud/Settings/LogFilesViewController.swift b/ownCloud/Settings/LogFilesViewController.swift index 418234da4..feb02b546 100644 --- a/ownCloud/Settings/LogFilesViewController.swift +++ b/ownCloud/Settings/LogFilesViewController.swift @@ -161,13 +161,13 @@ class LogFilesViewController : UITableViewController, UITableViewDragDelegate, T } } - override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { - - return [ - UITableViewRowAction(style: .destructive, title: "Delete".localized, handler: { [weak self] (_, indexPath) in + override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + return UISwipeActionsConfiguration(actions: [ + UIContextualAction(style: .destructive, title: "Delete".localized, handler: { [weak self] (_, _, completionHandler) in self?.removeLogRecord(at: indexPath) + completionHandler(true) }) - ] + ]) } // MARK: - Table view drag & drop support diff --git a/ownCloud/Static Login/Interface/StaticLoginSingleAccountServerListViewController.swift b/ownCloud/Static Login/Interface/StaticLoginSingleAccountServerListViewController.swift index 3784ab397..89509c6a8 100644 --- a/ownCloud/Static Login/Interface/StaticLoginSingleAccountServerListViewController.swift +++ b/ownCloud/Static Login/Interface/StaticLoginSingleAccountServerListViewController.swift @@ -314,7 +314,7 @@ class StaticLoginSingleAccountServerListViewController: ServerListTableViewContr } } - override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { + override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { return nil } diff --git a/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift b/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift index 0301a304a..f1640bb3b 100644 --- a/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift +++ b/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift @@ -477,10 +477,10 @@ open class GroupSharingTableViewController: SharingTableViewController, UISearch // MARK: TableView Delegate - open override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { + open override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { if let shareAtPath = share(at: indexPath), self.canEdit(share: shareAtPath) { - return [ - UITableViewRowAction(style: .destructive, title: "Delete".localized, handler: { (_, _) in + return UISwipeActionsConfiguration(actions: [ + UIContextualAction(style: .destructive, title: "Delete".localized, handler: { (_, _, completionHandler) in var presentationStyle: UIAlertController.Style = .actionSheet if UIDevice.current.isIpad { presentationStyle = .alert @@ -505,11 +505,13 @@ open class GroupSharingTableViewController: SharingTableViewController, UISearch })) self.present(alertController, animated: true, completion: nil) + + completionHandler(true) }) - ] + ]) } - return [] + return nil } // MARK: Themeing diff --git a/ownCloudAppShared/Client/Sharing/PublicLinkTableViewController.swift b/ownCloudAppShared/Client/Sharing/PublicLinkTableViewController.swift index 205c6715c..f86cc6545 100644 --- a/ownCloudAppShared/Client/Sharing/PublicLinkTableViewController.swift +++ b/ownCloudAppShared/Client/Sharing/PublicLinkTableViewController.swift @@ -200,11 +200,12 @@ open class PublicLinkTableViewController: SharingTableViewController { // MARK: TableView Delegate - open override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { + open override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { if let share = share(at: indexPath), self.canEdit(share: share) { - return [ - UITableViewRowAction(style: .destructive, title: "Delete".localized, handler: { (_, _) in + return UISwipeActionsConfiguration(actions: [ + UIContextualAction(style: .destructive, title: "Delete".localized, handler: { (_, _, completionHandler) in var presentationStyle: UIAlertController.Style = .actionSheet + if UIDevice.current.isIpad { presentationStyle = .alert } @@ -230,19 +231,23 @@ open class PublicLinkTableViewController: SharingTableViewController { })) self.present(alertController, animated: true, completion: nil) + + completionHandler(true) }), - UITableViewRowAction(style: .normal, title: "Copy".localized, handler: { (_, _) in + UIContextualAction(style: .normal, title: "Copy".localized, handler: { (_, _, completionHandler) in if let shareURL = share.url { UIPasteboard.general.url = shareURL _ = NotificationHUDViewController(on: self, title: share.name ?? "Public Link".localized, subtitle: "URL was copied to the clipboard".localized) } + + completionHandler(true) }) - ] + ]) } - return [] + return nil } // MARK: Add New Link Share From 1aea9b657df97635722c5b548f43c92b1a086920 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Mon, 24 Jan 2022 22:50:09 +0100 Subject: [PATCH 007/328] - remove `doc/CONFIGURATION.md` - remove `doc/APP_BUILD_FLAGS.md` --- doc/APP_BUILD_FLAGS.md | 32 -------------- doc/BUILD_CUSTOMIZATION.md | 2 +- doc/CONFIGURATION.md | 86 -------------------------------------- 3 files changed, 1 insertion(+), 119 deletions(-) delete mode 100644 doc/APP_BUILD_FLAGS.md delete mode 100644 doc/CONFIGURATION.md diff --git a/doc/APP_BUILD_FLAGS.md b/doc/APP_BUILD_FLAGS.md deleted file mode 100644 index 7aa76ff4f..000000000 --- a/doc/APP_BUILD_FLAGS.md +++ /dev/null @@ -1,32 +0,0 @@ -# App Build Flags - -## Description - -App Build Flags can be used to control the inclusion or exclusion of certain functionality or features. - -## Usage in Branding - -A space-separated list of flags can be specified in the `Branding.plist` with the key `app.build-flags`, f.ex.: - -```xml -app.build-flags -DISABLE_BACKGROUND_LOCATION -``` - -## Flags - -The following options can be used as `APP_BUILD_FLAGS`: - -### `DISABLE_BACKGROUND_LOCATION` - -Removes the following from the app: -- the option for location-triggered background uploads from Settings -- the location description keys from the app's `Info.plist` - -Not used by default. - -### `DISABLE_APPSTORE_LICENSING` - -Removes the following from the app: -- App Store integration for OCLicense -- App Store related view controllers and settings section diff --git a/doc/BUILD_CUSTOMIZATION.md b/doc/BUILD_CUSTOMIZATION.md index e758f3ef7..7eaf4c53d 100644 --- a/doc/BUILD_CUSTOMIZATION.md +++ b/doc/BUILD_CUSTOMIZATION.md @@ -15,7 +15,7 @@ A space-separated list of flags can be specified in the `Branding.plist` with th ## Flags -The following options can be used as `build.flags`: +The following options can be used as `build.flags` / `APP_BUILD_FLAGS`: ### `DISABLE_BACKGROUND_LOCATION` diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md deleted file mode 100644 index fda1d0449..000000000 --- a/doc/CONFIGURATION.md +++ /dev/null @@ -1,86 +0,0 @@ -# Configuration - -## Introduction - -The ownCloud iOS App provides a flexible mechanism for configuration. While it currently only returns the default values defined by the classes itself, MDM and branding support can be added in the future with relatively little effort. - -This document provides an overview over the available sections and variables. - -## App - -- **Section ID**: `app` - -- **Variables**: - - `show-beta-warning`: Controls whether a warning should be shown on the first run of a beta version. - - type: Bool - - default: `true` - - `is-beta-build`: Controls if the app is built for beta or release purposes. - - type: Bool - - default: `false` - - `app-store-link` : Points to the app's link in the app store. - - type: String - - default: `https://itunes.apple.com/app/id1359583808?mt=8` - - `feedback-email` : Email to send the feedback mail. - - type: String - - default: `ios-app@owncloud.com` - - `recommend-to-friend-enabled` : Option to send en email with the App Store link. - - type: Bool - - default: `true` - - `send-feedback-enabled`: Send an email to feedback-email with some feedback. - - type: Bool - - default: `true` - -## Bookmarks - -- **Section ID**: `bookmark` - -- **Variables**: - - `default-url`: Set a default server URL. - - type: String - - default: `""` - - `url-editable`: Being able to edit the server URL in the URL TextField. - - type: Bool - - default: `true` - - -## Diagnostics - -- **Section ID**: `diagnostics` - -- **Variables**: - - `enabled`: Controls whether additional diagnostic options and information is available throughout the user interface. - - type: Bool - - default: `false` - -## Display Settings - -- **Section ID**: `display` - -- **Variables**: - - `show-hidden-files`: Controls whether hidden files (i.e. files starting with `.` ) should also be shown - - type: Bool - - default: `false` - - `sort-folders-first`: Controls whether folders are shown at the top - - type: Bool - - default: `false` - - `prevent-dragging-files`: Controls whether drag and drop should be prevented for items inside the app - - type: Bool - - default: `false` - -## File Provider - -- **Section ID**: `file-provider` - -- **Variables**: - - `skip-local-error-checks` : If TRUE, skips some local error checks in the FileProvider to easily provoke errors. (for testing only) - - type: Bool - - default: `false` - -## Shortcuts - -- **Section ID**: `shortcuts` - -- **Variables**: - - `enabled`: Controls whether Shortcuts support is enabled - - type: Bool - - default: `true` From de3ca4175b40a3af69771d72c230cdb2f9c88d01 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Mon, 14 Feb 2022 23:59:10 +0100 Subject: [PATCH 008/328] - switch to feature/graph-api SDK branch - FileProviderExtension: change comment to reflect drive-dependant path changes --- ios-sdk | 2 +- ownCloud File Provider/FileProviderExtension.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ios-sdk b/ios-sdk index d081e5f58..a2efd2df1 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit d081e5f584dcb8a53d2178847ee76d6c90c64ac0 +Subproject commit a2efd2df162ed43d7ed4a7bfc005a0ab1a3ab9f3 diff --git a/ownCloud File Provider/FileProviderExtension.m b/ownCloud File Provider/FileProviderExtension.m index 95d1dd7a0..e4187a32d 100644 --- a/ownCloud File Provider/FileProviderExtension.m +++ b/ownCloud File Provider/FileProviderExtension.m @@ -262,7 +262,7 @@ - (NSFileProviderItemIdentifier)persistentIdentifierForItemAtURL:(NSURL *)url NSArray *pathComponents = [url pathComponents]; // exploit the fact that the path structure has been defined as - // // above + // /[Drives//]/ above NSParameterAssert(pathComponents.count > 2); // OCLogDebug(@"-persistentIdentifierForItemAtURL: %@", (pathComponents[pathComponents.count - 2])); From 101d3b485d9d0cb86f9d400426efe63816ec3bab Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Wed, 23 Feb 2022 23:29:54 +0100 Subject: [PATCH 009/328] - adapt to new and updated APIs (OCPath -> OCDriveID + OCLocation) - update SDK --- ios-sdk | 2 +- .../FileProviderEnumerator.m | 12 +-- .../FileProviderExtension.m | 4 +- .../CreateFolderIntentHandler.swift | 4 +- .../DeletePathItemIntentHandler.swift | 2 +- .../GetDirectoryListingIntentHandler.swift | 2 +- .../GetFileInfoIntentHandler.swift | 2 +- ownCloud Intents/GetFileIntentHandler.swift | 2 +- .../PathExistsIntentHandler.swift | 2 +- ownCloud Intents/SaveFileIntentHandler.swift | 4 +- .../ShareViewController.swift | 2 +- .../Actions+Extensions/CopyAction.swift | 2 +- .../Actions+Extensions/DuplicateAction.swift | 4 +- .../MakeUnavailableOfflineAction.swift | 4 +- .../Actions+Extensions/MoveAction.swift | 2 +- .../Actions/EditDocumentViewController.swift | 4 +- .../Client/Actions/Scanner/ScanAction.swift | 5 +- .../Client/ClientRootViewController.swift | 4 +- ...ntroller+OpenItemTableViewController.swift | 4 +- .../Item Policies/ItemPolicyCell.swift | 12 +-- .../ItemPolicyTableViewController.swift | 4 +- .../Library/LibraryTableViewController.swift | 6 +- .../PendingSharesTableViewController.swift | 12 +-- ownCloud/Import/ImportFilesController.swift | 2 +- .../Media Uploads/MediaUploadOperation.swift | 2 +- ownCloud/Migration/Migration.swift | 4 +- .../Settings/AutoUploadSettingsSection.swift | 6 +- .../Actions/ClientItemResolvingCell.swift | 8 +- .../Client/Actions/CreateFolderAction.swift | 4 +- .../ClientDirectoryPickerViewController.swift | 74 +++++++++---------- .../ClientQueryViewController.swift | 16 ++-- .../FileListTableViewController.swift | 4 +- .../QueryFileListTableViewController.swift | 8 +- .../GroupSharingEditTableViewController.swift | 2 +- .../GroupSharingTableViewController.swift | 4 +- .../PublicLinkEditTableViewController.swift | 2 +- .../PublicLinkTableViewController.swift | 16 ++-- .../Client/Sharing/ShareClientItemCell.swift | 4 +- .../BreadCrumbTableViewController.swift | 5 +- .../SDK Extensions/OCCore+Extension.swift | 6 +- .../SDK Extensions/OCItem+Extension.swift | 6 +- .../SDK Extensions/OCItemTracker.swift | 14 ++-- 42 files changed, 144 insertions(+), 144 deletions(-) diff --git a/ios-sdk b/ios-sdk index a2efd2df1..8a204151e 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit a2efd2df162ed43d7ed4a7bfc005a0ab1a3ab9f3 +Subproject commit 8a204151e2f08cb990f4b867b08579b8c0e3098e diff --git a/ownCloud File Provider/FileProviderEnumerator.m b/ownCloud File Provider/FileProviderEnumerator.m index 69515e8b1..d2cfebfb3 100644 --- a/ownCloud File Provider/FileProviderEnumerator.m +++ b/ownCloud File Provider/FileProviderEnumerator.m @@ -53,7 +53,7 @@ - (instancetype)initWithBookmark:(OCBookmark *)bookmark enumeratedItemIdentifier - (void)invalidate { - OCLogDebug(@"##### INVALIDATE %@", _query.queryPath); + OCLogDebug(@"##### INVALIDATE %@", _query.queryLocation); [[NSNotificationCenter defaultCenter] removeObserver:self name:DisplaySettingsChanged object:nil]; @@ -197,7 +197,7 @@ - (void)_startQuery else { // Start query - self->_query = [OCQuery queryForPath:queryPath]; + self->_query = [OCQuery queryForLocation:[OCLocation legacyRootPath:queryPath]]; self->_query.includeRootItem = queryPath.isRootPath; // Include the root item only for the root folder. If it's not included, no folder can be created in the root directory. If a non-root folder is included in a query result for its content, the Files Duplicate action will loop infinitely. self->_query.delegate = self; @@ -220,7 +220,7 @@ - (void)_startQuery } } - OCLogDebug(@"##### START QUERY FOR %@", self->_query.queryPath); + OCLogDebug(@"##### START QUERY FOR %@", self->_query.queryLocation); [self->_query attachMeasurement:self->_measurement]; @@ -284,7 +284,7 @@ - (void)provideItemsForEnumerationObserverFromQuery:(OCQuery *)query { NSArray *queryResults = query.queryResults; - OCLogDebug(@"##### PROVIDE ITEMS TO %ld --ENUMERATION-- OBSERVER %@ FOR %@: %@", _enumerationObservers.count, observer.enumerationObserver, query.queryPath, queryResults); + OCLogDebug(@"##### PROVIDE ITEMS TO %ld --ENUMERATION-- OBSERVER %@ FOR %@: %@", _enumerationObservers.count, observer.enumerationObserver, query.queryLocation.path, queryResults); observer.didProvideInitialItems = YES; @@ -329,7 +329,7 @@ - (void)provideItemsForChangeObserverFromQuery:(OCQuery *)query { if (_changeObservers.count > 0) { - OCLogDebug(@"##### PROVIDE ITEMS TO %lu --CHANGE-- OBSERVER FOR %@: %@", _changeObservers.count, query.queryPath, query.queryResults); + OCLogDebug(@"##### PROVIDE ITEMS TO %lu --CHANGE-- OBSERVER FOR %@: %@", _changeObservers.count, query.queryLocation.path, query.queryResults); for (FileProviderEnumeratorObserver *observer in _changeObservers) { @@ -344,7 +344,7 @@ - (void)provideItemsForChangeObserverFromQuery:(OCQuery *)query - (void)queryHasChangesAvailable:(OCQuery *)query { - OCLogDebug(@"##### Query for %@ has changes. Query state: %lu, SinceSyncAnchor: %@, Changes available: %d", query.queryPath, (unsigned long)query.state, query.querySinceSyncAnchor, query.hasChangesAvailable); + OCLogDebug(@"##### Query for %@ has changes. Query state: %lu, SinceSyncAnchor: %@, Changes available: %d", query.queryLocation.path, (unsigned long)query.state, query.querySinceSyncAnchor, query.hasChangesAvailable); if ( (query.state == OCQueryStateContentsFromCache) || ((query.state == OCQueryStateWaitingForServerReply) && (query.queryResults.count > 0)) || diff --git a/ownCloud File Provider/FileProviderExtension.m b/ownCloud File Provider/FileProviderExtension.m index e4187a32d..50a57e55f 100644 --- a/ownCloud File Provider/FileProviderExtension.m +++ b/ownCloud File Provider/FileProviderExtension.m @@ -146,7 +146,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N { OCLogDebug(@"Initial root container scan.."); - OCQuery *query = [OCQuery queryForPath:@"/"]; + OCQuery *query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; __weak OCCore *weakCore = self.core; query.changesAvailableNotificationHandler = ^(OCQuery *query) { @@ -189,7 +189,7 @@ - (NSFileProviderItem)itemForIdentifier:(NSFileProviderItemIdentifier)identifier if ([identifier isEqual:NSFileProviderRootContainerItemIdentifier]) { // Root item - [self.core.vault.database retrieveCacheItemsAtPath:@"/" itemOnly:YES completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { + [self.core.vault.database retrieveCacheItemsAtLocation:OCLocation.legacyRootLocation itemOnly:YES completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { item = items.firstObject; returnError = error; diff --git a/ownCloud Intents/CreateFolderIntentHandler.swift b/ownCloud Intents/CreateFolderIntentHandler.swift index 3d1b00766..658f02664 100644 --- a/ownCloud Intents/CreateFolderIntentHandler.swift +++ b/ownCloud Intents/CreateFolderIntentHandler.swift @@ -84,11 +84,11 @@ public class CreateFolderIntentHandler: NSObject, CreateFolderIntentHandling, OC self.completionHandler = completion - OCItemTracker(for: bookmark, at: path, waitOnlineTimeout: 5) { (error, core, item) in + OCItemTracker(for: bookmark, at: .legacyRootPath(path), waitOnlineTimeout: 5) { (error, core, item) in if error == nil, let targetItem = item { let folderPath = String(format: "%@%@", path, name) // Check, if the folder already exists in the given path - OCItemTracker(for: bookmark, at: folderPath, waitOnlineTimeout: 5) { (error, core, folderPathItem) in + OCItemTracker(for: bookmark, at: .legacyRootPath(folderPath), waitOnlineTimeout: 5) { (error, core, folderPathItem) in if error == nil, folderPathItem == nil, let core = core { let waitForCompletion = intent.waitForCompletion as? Bool ?? false let bookmark = core.bookmark diff --git a/ownCloud Intents/DeletePathItemIntentHandler.swift b/ownCloud Intents/DeletePathItemIntentHandler.swift index 709f0c77a..b73c66104 100644 --- a/ownCloud Intents/DeletePathItemIntentHandler.swift +++ b/ownCloud Intents/DeletePathItemIntentHandler.swift @@ -84,7 +84,7 @@ public class DeletePathItemIntentHandler: NSObject, DeletePathItemIntentHandling self.completionHandler = completion - OCItemTracker(for: bookmark, at: path) { (error, core, item) in + OCItemTracker(for: bookmark, at: .legacyRootPath(path)) { (error, core, item) in if error == nil, let targetItem = item, core != nil { OCCoreManager.shared.requestCore(for: bookmark, setup: { (core, error) in core?.delegate = self diff --git a/ownCloud Intents/GetDirectoryListingIntentHandler.swift b/ownCloud Intents/GetDirectoryListingIntentHandler.swift index 6dc0d3670..d2b0954e1 100644 --- a/ownCloud Intents/GetDirectoryListingIntentHandler.swift +++ b/ownCloud Intents/GetDirectoryListingIntentHandler.swift @@ -113,7 +113,7 @@ public class GetDirectoryListingIntentHandler: NSObject, GetDirectoryListingInte self.core = core if error == nil { - let targetDirectoryQuery = OCQuery(forPath: path) + let targetDirectoryQuery = OCQuery(for: OCLocation.legacyRootPath(path)) targetDirectoryQuery.delegate = self if targetDirectoryQuery.sortComparator == nil { diff --git a/ownCloud Intents/GetFileInfoIntentHandler.swift b/ownCloud Intents/GetFileInfoIntentHandler.swift index e40bf523b..ee1bc4ec7 100644 --- a/ownCloud Intents/GetFileInfoIntentHandler.swift +++ b/ownCloud Intents/GetFileInfoIntentHandler.swift @@ -51,7 +51,7 @@ public class GetFileInfoIntentHandler: NSObject, GetFileInfoIntentHandling { return } - OCItemTracker(for: bookmark, at: path, waitOnlineTimeout: 5) { (error, core, item) in + OCItemTracker(for: bookmark, at: .legacyRootPath(path), waitOnlineTimeout: 5) { (error, core, item) in if error == nil, let targetItem = item { let fileInfo = FileInfo(identifier: targetItem.localID, display: targetItem.name ?? "") diff --git a/ownCloud Intents/GetFileIntentHandler.swift b/ownCloud Intents/GetFileIntentHandler.swift index 00ecc0399..a6ea1db63 100644 --- a/ownCloud Intents/GetFileIntentHandler.swift +++ b/ownCloud Intents/GetFileIntentHandler.swift @@ -84,7 +84,7 @@ public class GetFileIntentHandler: NSObject, GetFileIntentHandling, OCCoreDelega self.completionHandler = completion - OCItemTracker(for: bookmark, at: path, waitOnlineTimeout: 5) { (error, core, item) in + OCItemTracker(for: bookmark, at: .legacyRootPath(path), waitOnlineTimeout: 5) { (error, core, item) in if error == nil, let item = item { if core?.localCopy(of: item) == nil { OCCoreManager.shared.requestCore(for: bookmark, setup: { (core, error) in diff --git a/ownCloud Intents/PathExistsIntentHandler.swift b/ownCloud Intents/PathExistsIntentHandler.swift index cd6fc2286..fd14d9575 100644 --- a/ownCloud Intents/PathExistsIntentHandler.swift +++ b/ownCloud Intents/PathExistsIntentHandler.swift @@ -51,7 +51,7 @@ public class PathExistsIntentHandler: NSObject, PathExistsIntentHandling { return } - OCItemTracker(for: bookmark, at: path, waitOnlineTimeout: 5) { (error, core, item) in + OCItemTracker(for: bookmark, at: .legacyRootPath(path), waitOnlineTimeout: 5) { (error, core, item) in if error == nil, item != nil { completion(PathExistsIntentResponse.success(pathExists: true)) } else if error?.isAuthenticationError == true { diff --git a/ownCloud Intents/SaveFileIntentHandler.swift b/ownCloud Intents/SaveFileIntentHandler.swift index b7cb15103..8dd584f48 100644 --- a/ownCloud Intents/SaveFileIntentHandler.swift +++ b/ownCloud Intents/SaveFileIntentHandler.swift @@ -128,10 +128,10 @@ public class SaveFileIntentHandler: NSObject, SaveFileIntentHandling, OCCoreDele } // Check if given save path exists - OCItemTracker(for: bookmark, at: path, waitOnlineTimeout: 5) { (error, core, item) in + OCItemTracker(for: bookmark, at: .legacyRootPath(path), waitOnlineTimeout: 5) { (error, core, item) in if error == nil, let targetItem = item { // Check if file already exists - OCItemTracker(for: bookmark, at: filePath, waitOnlineTimeout: 5) { (error, core, fileItem) in + OCItemTracker(for: bookmark, at: .legacyRootPath(filePath), waitOnlineTimeout: 5) { (error, core, fileItem) in if let core = core { if error == nil, let fileItem = fileItem { // File already exists diff --git a/ownCloud Share Extension/ShareViewController.swift b/ownCloud Share Extension/ShareViewController.swift index 155450f8f..6dbdf9538 100644 --- a/ownCloud Share Extension/ShareViewController.swift +++ b/ownCloud Share Extension/ShareViewController.swift @@ -196,7 +196,7 @@ class ShareViewController: MoreStaticTableViewController { self.requestCore(for: bookmark, completionHandler: { (core, error) in if let core = core, error == nil { OnMainThread { - let directoryPickerViewController = ClientDirectoryPickerViewController(core: core, path: "/", selectButtonTitle: "Save here".localized, avoidConflictsWith: [], choiceHandler: { [weak core] (selectedDirectory, _) in + let directoryPickerViewController = ClientDirectoryPickerViewController(core: core, location: .legacyRoot, selectButtonTitle: "Save here".localized, avoidConflictsWith: [], choiceHandler: { [weak core] (selectedDirectory, _) in if let targetDirectory = selectedDirectory { if let vault = core?.vault { self.fpServiceSession = OCFileProviderServiceSession(vault: vault) diff --git a/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift b/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift index 2c7d57103..913e944a2 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift @@ -118,7 +118,7 @@ class CopyAction : Action { let items = context.items - let directoryPickerViewController = ClientDirectoryPickerViewController(core: core, path: "/", selectButtonTitle: "Copy here".localized, avoidConflictsWith: items, choiceHandler: { (selectedDirectory, _) in + let directoryPickerViewController = ClientDirectoryPickerViewController(core: core, location: .legacyRoot, selectButtonTitle: "Copy here".localized, avoidConflictsWith: items, choiceHandler: { (selectedDirectory, _) in if let targetDirectory = selectedDirectory { items.forEach({ (item) in diff --git a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift index 8bd343e34..1bce438fd 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift @@ -54,8 +54,8 @@ class DuplicateAction : Action { if let core = self.core { OnBackgroundQueue { [weak core] in for item in duplicateItems { - if let core = core, let itemName = item.name, let parentItem = item.parentItem(from: core), let parentPath = parentItem.path { - core.suggestUnusedNameBased(on: itemName, atPath: parentPath, isDirectory: item.type == .collection, using: item.type == .collection ? .numbered : .bracketed, filteredBy: nil, resultHandler: { (suggestedName, _) in + if let core = core, let itemName = item.name, let parentItem = item.parentItem(from: core), let parentLocation = parentItem.location { + core.suggestUnusedNameBased(on: itemName, at: parentLocation, isDirectory: item.type == .collection, using: item.type == .collection ? .numbered : .bracketed, filteredBy: nil, resultHandler: { (suggestedName, _) in Log.debug("Duplicating \(item.name ?? "(null)") as \(suggestedName ?? "(null)")") if let suggestedName = suggestedName, let progress = core.copy(item, to: parentItem, withName: suggestedName, options: nil, resultHandler: { (error, _, item, _) in diff --git a/ownCloud/Client/Actions/Actions+Extensions/MakeUnavailableOfflineAction.swift b/ownCloud/Client/Actions/Actions+Extensions/MakeUnavailableOfflineAction.swift index 8ffb8246f..093e661ec 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/MakeUnavailableOfflineAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/MakeUnavailableOfflineAction.swift @@ -40,7 +40,7 @@ class MakeUnavailableOfflineAction: Action { for item in context.items { if let itemPolicies = core.retrieveAvailableOfflinePoliciesCovering(item, completionHandler: nil) { if itemPolicies.contains(where: { (itemPolicy) -> Bool in - return (itemPolicy.path == item.path) || (itemPolicy.localID == item.localID) + return (itemPolicy.location == item.location) || (itemPolicy.localID == item.localID) }) { position = .middle } @@ -60,7 +60,7 @@ class MakeUnavailableOfflineAction: Action { for item in context.items { if let itemPolicies = core.retrieveAvailableOfflinePoliciesCovering(item, completionHandler: nil) { for itemPolicy in itemPolicies { - if (itemPolicy.path == item.path) || (itemPolicy.localID == item.localID) { + if (itemPolicy.location == item.location) || (itemPolicy.localID == item.localID) { core.removeAvailableOfflinePolicy(itemPolicy, completionHandler: nil) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift index d5085d66b..7a4554c74 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift @@ -49,7 +49,7 @@ class MoveAction : Action { let items = context.items - let directoryPickerViewController = ClientDirectoryPickerViewController(core: core, path: "/", selectButtonTitle: "Move here".localized, avoidConflictsWith: items, choiceHandler: { (selectedDirectory, _) in + let directoryPickerViewController = ClientDirectoryPickerViewController(core: core, location: OCLocation.legacyRoot, selectButtonTitle: "Move here".localized, avoidConflictsWith: items, choiceHandler: { (selectedDirectory, _) in guard let selectedDirectory = selectedDirectory else { self.completed(with: NSError(ocError: OCError.cancelled)) return diff --git a/ownCloud/Client/Actions/EditDocumentViewController.swift b/ownCloud/Client/Actions/EditDocumentViewController.swift index 77896fc30..51c028383 100644 --- a/ownCloud/Client/Actions/EditDocumentViewController.swift +++ b/ownCloud/Client/Actions/EditDocumentViewController.swift @@ -55,8 +55,8 @@ class EditDocumentViewController: QLPreviewController, Themeable { Theme.shared.register(client: self, applyImmediately: true) - if let core = core, let path = item.path { - itemTracker = core.trackItem(atPath: path, trackingHandler: { [weak self, weak core](error, item, _) in + if let core = core, let location = item.location { + itemTracker = core.trackItem(at: location, trackingHandler: { [weak self, weak core](error, item, _) in if let item = item, let self = self { var refreshPreview = false diff --git a/ownCloud/Client/Actions/Scanner/ScanAction.swift b/ownCloud/Client/Actions/Scanner/ScanAction.swift index ff9995c42..f6963fbb7 100644 --- a/ownCloud/Client/Actions/Scanner/ScanAction.swift +++ b/ownCloud/Client/Actions/Scanner/ScanAction.swift @@ -62,7 +62,7 @@ class ScanAction: Action, VNDocumentCameraViewControllerDelegate { return } - guard let targetFolderItem = context.items.first, let itemPath = targetFolderItem.path else { + guard let targetFolderItem = context.items.first, let itemLocation = targetFolderItem.location else { completed(with: NSError(ocError: .itemNotFound)) return } @@ -85,7 +85,7 @@ class ScanAction: Action, VNDocumentCameraViewControllerDelegate { .replacingOccurrences(of: "/", with: "-") // Remove reserved character ("/" used to delimit paths on macOS, iOS, Linux, …) .replacingOccurrences(of: "\\", with: "-") // Remove reserved character ("\" used to delimit paths on Windows) - core?.suggestUnusedNameBased(on: filename ?? "\("Scan".localized) \(currentDate).pdf", atPath: itemPath, isDirectory: true, using: .bracketed, filteredBy: nil, resultHandler: { (suggestedName, _) in + core?.suggestUnusedNameBased(on: filename ?? "\("Scan".localized) \(currentDate).pdf", at: itemLocation, isDirectory: true, using: .bracketed, filteredBy: nil, resultHandler: { (suggestedName, _) in guard let suggestedName = suggestedName else { return } OnMainThread { @@ -94,7 +94,6 @@ class ScanAction: Action, VNDocumentCameraViewControllerDelegate { } }) } - } } diff --git a/ownCloud/Client/ClientRootViewController.swift b/ownCloud/Client/ClientRootViewController.swift index 4624cbc30..1741ec7a3 100644 --- a/ownCloud/Client/ClientRootViewController.swift +++ b/ownCloud/Client/ClientRootViewController.swift @@ -361,7 +361,7 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa if let localItemId = lastVisibleItemId { self.createFileListStack(for: localItemId) } else { - let query = OCQuery(forPath: "/") + let query = OCQuery(for: .legacyRoot) let queryViewController = ClientQueryViewController(core: core, query: query, rootViewController: self) // Because we have nested UINavigationControllers (first one from ServerListTableViewController and each item UITabBarController needs it own UINavigationController), we have to fake the UINavigationController logic. Here we insert the emptyViewController, because in the UI should appear a "Back" button if the root of the queryViewController is shown. Therefore we put at first the emptyViewController inside and at the same time the queryViewController. Now, the back button is shown and if the users push the "Back" button the ServerListTableViewController is shown. This logic can be found in navigationController(_: UINavigationController, willShow: UIViewController, animated: Bool) below. self.filesNavigationController?.setViewControllers([self.emptyViewController, queryViewController], animated: false) @@ -453,7 +453,7 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa // retrieve the item for the item id core.retrieveItemFromDatabase(forLocalID: itemLocalID, completionHandler: { (error, _, item) in OnMainThread { - let query = OCQuery(forPath: "/") + let query = OCQuery(for: .legacyRoot) let queryViewController = ClientQueryViewController(core: core, query: query, rootViewController: self) if error == nil, let item = item, item.isRoot == false { diff --git a/ownCloud/Client/FileList Extensions/FileListTableViewController+OpenItemTableViewController.swift b/ownCloud/Client/FileList Extensions/FileListTableViewController+OpenItemTableViewController.swift index 82cda00d6..ea82b92fb 100644 --- a/ownCloud/Client/FileList Extensions/FileListTableViewController+OpenItemTableViewController.swift +++ b/ownCloud/Client/FileList Extensions/FileListTableViewController+OpenItemTableViewController.swift @@ -30,8 +30,8 @@ extension FileListTableViewController : OpenItemHandling { switch item.type { case .collection: - if let path = item.path { - let clientQueryViewController = ClientQueryViewController(core: core, query: OCQuery(forPath: path)) + if let location = item.location { + let clientQueryViewController = ClientQueryViewController(core: core, query: OCQuery(for: location)) if pushViewController { self.navigationController?.pushViewController(clientQueryViewController, animated: animated) } diff --git a/ownCloud/Client/Library/Item Policies/ItemPolicyCell.swift b/ownCloud/Client/Library/Item Policies/ItemPolicyCell.swift index dc2bba19a..fe678ecff 100644 --- a/ownCloud/Client/Library/Item Policies/ItemPolicyCell.swift +++ b/ownCloud/Client/Library/Item Policies/ItemPolicyCell.swift @@ -33,21 +33,21 @@ class ItemPolicyCell: ClientItemResolvingCell { // MARK: - Item policy item override func titleLabelString(for item: OCItem?) -> NSAttributedString { - if (itemResolutionPath as NSString?)?.isRootPath == true { + if itemResolutionLocation?.isRoot == true { return NSAttributedString(string: "Root folder".localized) } if let item = item { return super.titleLabelString(for: item) } else { - return NSAttributedString(string: "\((itemResolutionPath as NSString?)?.lastPathComponent ?? "") \("(no match)".localized)") + return NSAttributedString(string: "\((itemResolutionLocation?.path as NSString?)?.lastPathComponent ?? "") \("(no match)".localized)") } } override func detailLabelString(for item: OCItem?) -> String { if itemPolicy?.localID != nil, let itemPath = item?.path { return "\("at".localized) \(itemPath)" - } else if let itemPolicyPath = itemPolicy?.path as NSString?, itemPolicyPath.length > 0 { + } else if let itemPolicyPath = itemPolicy?.location?.path as NSString?, itemPolicyPath.length > 0 { return "\("at".localized) \(itemPolicyPath)" } else { return super.detailLabelString(for: item) @@ -57,18 +57,18 @@ class ItemPolicyCell: ClientItemResolvingCell { var itemPolicy : OCItemPolicy? { didSet { if let itemPolicy = itemPolicy { - if let itemPath = itemPolicy.path { + if let itemLocation = itemPolicy.location, let itemPath = itemLocation.path { if itemPath.hasSuffix("/") { self.iconView.activeViewProvider = ResourceItemIcon.folder - self.itemResolutionPath = itemPath + self.itemResolutionLocation = itemLocation } else { self.iconView.activeViewProvider = ResourceItemIcon.file if let itemLocalID = itemPolicy.localID { self.itemResolutionLocalID = itemLocalID } else { - self.itemResolutionPath = itemPath + self.itemResolutionLocation = itemLocation } } diff --git a/ownCloud/Client/Library/Item Policies/ItemPolicyTableViewController.swift b/ownCloud/Client/Library/Item Policies/ItemPolicyTableViewController.swift index 1cce402eb..61df1d4a2 100644 --- a/ownCloud/Client/Library/Item Policies/ItemPolicyTableViewController.swift +++ b/ownCloud/Client/Library/Item Policies/ItemPolicyTableViewController.swift @@ -69,7 +69,7 @@ class ItemPolicyTableViewController : FileListTableViewController { @objc func loadItemPolicies() { self.core?.retrievePolicies(ofKind: policyKind, affectingItem: nil, includeInternal: false, completionHandler: { (_, policies) in self.itemPolicies = policies?.sorted(by: { (policy1, policy2) -> Bool in - if let path1 = policy1.path, let path2 = policy2.path { + if let path1 = policy1.location?.path, let path2 = policy2.location?.path { return path1.compare(path2) == .orderedAscending } @@ -155,7 +155,7 @@ class ItemPolicyTableViewController : FileListTableViewController { let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell", for: indexPath) as? ItemPolicyCell let itemPolicy = itemPolicyAt(indexPath) - cell?.accessibilityIdentifier = itemPolicy.path ?? itemPolicy.localID + cell?.accessibilityIdentifier = itemPolicy.location?.path ?? itemPolicy.localID cell?.core = self.core cell?.itemPolicy = itemPolicy cell?.isMoreButtonPermanentlyHidden = true diff --git a/ownCloud/Client/Library/LibraryTableViewController.swift b/ownCloud/Client/Library/LibraryTableViewController.swift index d1ab8e4ea..a1ac0c41a 100644 --- a/ownCloud/Client/Library/LibraryTableViewController.swift +++ b/ownCloud/Client/Library/LibraryTableViewController.swift @@ -305,7 +305,7 @@ class LibraryTableViewController: StaticTableViewController { shareResults.append(contentsOf: queryResults) } - let uniqueShares = shareResults.unique { $0.itemPath } + let uniqueShares = shareResults.unique { $0.itemLocation } let sharedWithUserAccepted = uniqueShares.filter({ (share) -> Bool in return ((share.type == .remote) && (share.accepted == true)) || @@ -352,8 +352,8 @@ class LibraryTableViewController: StaticTableViewController { }) OnMainThread { - self.updateView(identifier: .sharedWithOthers, with: sharedByUser.unique { $0.itemPath }) - self.updateView(identifier: .publicLinks, with: sharedByUserLinks.unique { $0.itemPath }) + self.updateView(identifier: .sharedWithOthers, with: sharedByUser.unique { $0.itemLocation }) + self.updateView(identifier: .publicLinks, with: sharedByUserLinks.unique { $0.itemLocation }) } } diff --git a/ownCloud/Client/Sharing/PendingSharesTableViewController.swift b/ownCloud/Client/Sharing/PendingSharesTableViewController.swift index ec23230b6..9de7fb02f 100644 --- a/ownCloud/Client/Sharing/PendingSharesTableViewController.swift +++ b/ownCloud/Client/Sharing/PendingSharesTableViewController.swift @@ -110,8 +110,8 @@ class PendingSharesTableViewController: StaticTableViewController { } var itemName = share.name - if share.itemPath.count > 0 { - itemName = (share.itemPath as NSString).lastPathComponent + if let itemPath = share.itemLocation.path, itemPath.count > 0 { + itemName = (itemPath as NSString).lastPathComponent } let row = StaticTableViewRow(rowWithAction: { [weak self] (_, _) in @@ -143,10 +143,10 @@ class PendingSharesTableViewController: StaticTableViewController { section.add(row: row) - if share.itemPath.count > 0 { + if let itemPath = share.itemLocation.path, itemPath.count > 0 { if (share.state == .accepted) || (share.accepted == true) { // Item should exist -> track it - if let itemTracker = core?.trackItem(atPath: share.itemPath, trackingHandler: { (error, item, isInitial) in + if let itemTracker = core?.trackItem(at: share.itemLocation, trackingHandler: { (error, item, isInitial) in if error == nil, isInitial { OnMainThread { row.cell?.imageView?.image = item?.icon(fitInSize: CGSize(width: PendingSharesTableViewController.imageWidth, height: PendingSharesTableViewController.imageHeight)) @@ -234,8 +234,8 @@ class PendingSharesTableViewController: StaticTableViewController { } else { if share.type == .remote { var itemName = share.name - if share.itemPath.count > 0 { - itemName = (share.itemPath as NSString).lastPathComponent + if let itemPath = share.itemLocation.path, itemPath.count > 0 { + itemName = (itemPath as NSString).lastPathComponent } let alertController = ThemedAlertController(title: String(format: "Decline Invite %@".localized, itemName ?? ""), message: "Decline cannot be undone.", preferredStyle: .alert) diff --git a/ownCloud/Import/ImportFilesController.swift b/ownCloud/Import/ImportFilesController.swift index d59d5ba9f..251310264 100644 --- a/ownCloud/Import/ImportFilesController.swift +++ b/ownCloud/Import/ImportFilesController.swift @@ -213,7 +213,7 @@ extension ImportFilesController { if let core = core, error == nil { OnMainThread { [weak core] in let waitGroup = DispatchGroup() - let directoryPickerViewController = ClientDirectoryPickerViewController(core: core!, path: "/", selectButtonTitle: "Save here".localized, avoidConflictsWith: [], choiceHandler: { (selectedDirectory, _) in + let directoryPickerViewController = ClientDirectoryPickerViewController(core: core!, location: OCLocation.legacyRoot, selectButtonTitle: "Save here".localized, avoidConflictsWith: [], choiceHandler: { (selectedDirectory, _) in if let targetDirectory = selectedDirectory { for importFile in self.importFiles { let name = importFile.url.lastPathComponent diff --git a/ownCloud/Media Uploads/MediaUploadOperation.swift b/ownCloud/Media Uploads/MediaUploadOperation.swift index 5a752905e..166794ffb 100644 --- a/ownCloud/Media Uploads/MediaUploadOperation.swift +++ b/ownCloud/Media Uploads/MediaUploadOperation.swift @@ -89,7 +89,7 @@ class MediaUploadOperation : Operation { // Track the target path importGroup.enter() - self.itemTracking = core.trackItem(atPath: path, trackingHandler: { (_, item, isInitial) in + self.itemTracking = core.trackItem(at: OCLocation.legacyRootPath(path), trackingHandler: { (_, item, isInitial) in let importGroup = importGroupLeaveOnce importGroupLeaveOnce = nil diff --git a/ownCloud/Migration/Migration.swift b/ownCloud/Migration/Migration.swift index f80b2f888..fad6e662a 100644 --- a/ownCloud/Migration/Migration.swift +++ b/ownCloud/Migration/Migration.swift @@ -453,7 +453,7 @@ class Migration { // Track root folder trackItemGroup.enter() - OCItemTracker(for: bookmark, at: "/") { (error, core, rootItem) in + OCItemTracker(for: bookmark, at: .legacyRootPath("/")) { (error, core, rootItem) in defer { trackItemGroup.leave() } @@ -461,7 +461,7 @@ class Migration { // Track InstantUpload subfolder trackItemGroup.enter() - OCItemTracker(for: bookmark, at: Migration.legacyInstantUploadFolder) { (error, core, item) in + OCItemTracker(for: bookmark, at: .legacyRootPath(Migration.legacyInstantUploadFolder)) { (error, core, item) in defer { trackItemGroup.leave() } diff --git a/ownCloud/Settings/AutoUploadSettingsSection.swift b/ownCloud/Settings/AutoUploadSettingsSection.swift index e49ea69af..fb6d0ff6e 100644 --- a/ownCloud/Settings/AutoUploadSettingsSection.swift +++ b/ownCloud/Settings/AutoUploadSettingsSection.swift @@ -256,7 +256,7 @@ class AutoUploadSettingsSection: SettingsSection { self.remove(rowWithIdentifier: AutoUploadSettingsSection.videoUploadBookmarkAndPathSelectionRowIdentifier) if let bookmark = getSelectedBookmark(for: .photo), let path = userDefaults.instantPhotoUploadPath, userDefaults.instantUploadPhotos == true { - OCItemTracker(for: bookmark, at: path) { (error, _, pathItem) in + OCItemTracker(for: bookmark, at: .legacyRootPath(path)) { (error, _, pathItem) in guard error == nil else { return } OnMainThread { @@ -280,7 +280,7 @@ class AutoUploadSettingsSection: SettingsSection { } if let bookmark = getSelectedBookmark(for: .video), let path = userDefaults.instantVideoUploadPath, userDefaults.instantUploadVideos == true { - OCItemTracker(for: bookmark, at: path) { (error, _, pathItem) in + OCItemTracker(for: bookmark, at: .legacyRootPath(path)) { (error, _, pathItem) in guard error == nil else { return } OnMainThread { @@ -398,7 +398,7 @@ class AutoUploadSettingsSection: SettingsSection { guard let core = core, error == nil else { return } OnMainThread { - let directoryPickerViewController = ClientDirectoryPickerViewController(core: core, path: "/", selectButtonTitle: "Select Upload Path".localized, avoidConflictsWith: [], choiceHandler: { (selectedDirectory, _) in + let directoryPickerViewController = ClientDirectoryPickerViewController(core: core, location: .legacyRoot, selectButtonTitle: "Select Upload Path".localized, avoidConflictsWith: [], choiceHandler: { (selectedDirectory, _) in OCCoreManager.shared.returnCore(for: bookmark, completionHandler: nil) completion(selectedDirectory) }) diff --git a/ownCloudAppShared/Client/Actions/ClientItemResolvingCell.swift b/ownCloudAppShared/Client/Actions/ClientItemResolvingCell.swift index 1e57b6e2a..9a22f4063 100644 --- a/ownCloudAppShared/Client/Actions/ClientItemResolvingCell.swift +++ b/ownCloudAppShared/Client/Actions/ClientItemResolvingCell.swift @@ -23,13 +23,13 @@ open class ClientItemResolvingCell: ClientItemCell { var itemTracker : OCCoreItemTracking? // MARK: - Resolve item from path - public var itemResolutionPath : String? { + public var itemResolutionLocation : OCLocation? { didSet { self.item = nil - if let atPath = itemResolutionPath { + if let itemLocation = itemResolutionLocation { self.itemResolutionLocalID = nil - self.itemTracker = core?.trackItem(atPath: atPath, trackingHandler: { (error, item, isInitial) in + self.itemTracker = core?.trackItem(at: itemLocation, trackingHandler: { (error, item, isInitial) in if error == nil, let item = item, isInitial { OnMainThread { self.item = item @@ -51,7 +51,7 @@ open class ClientItemResolvingCell: ClientItemCell { didSet { if let itemResolutionLocalID = itemResolutionLocalID { self.item = nil - self.itemResolutionPath = nil + self.itemResolutionLocation = nil core?.retrieveItemFromDatabase(forLocalID: itemResolutionLocalID, completionHandler: { (error, _, item) in if let item = item, item.localID == self.itemResolutionLocalID { diff --git a/ownCloudAppShared/Client/Actions/CreateFolderAction.swift b/ownCloudAppShared/Client/Actions/CreateFolderAction.swift index 2911098e0..60e4c6e83 100644 --- a/ownCloudAppShared/Client/Actions/CreateFolderAction.swift +++ b/ownCloudAppShared/Client/Actions/CreateFolderAction.swift @@ -52,7 +52,7 @@ open class CreateFolderAction : Action { let item = context.items.first - guard item != nil, let itemPath = item?.path else { + guard item != nil, let itemLocation = item?.location else { completed(with: NSError(ocError: .itemNotFound)) return } @@ -61,7 +61,7 @@ open class CreateFolderAction : Action { return } - core?.suggestUnusedNameBased(on: "New Folder".localized, atPath: itemPath, isDirectory: true, using: .numbered, filteredBy: nil, resultHandler: { (suggestedName, _) in + core?.suggestUnusedNameBased(on: "New Folder".localized, at: itemLocation, isDirectory: true, using: .numbered, filteredBy: nil, resultHandler: { (suggestedName, _) in guard let suggestedName = suggestedName else { return } OnMainThread { diff --git a/ownCloudAppShared/Client/File Lists/ClientDirectoryPickerViewController.swift b/ownCloudAppShared/Client/File Lists/ClientDirectoryPickerViewController.swift index b97f32780..d85ec2334 100644 --- a/ownCloudAppShared/Client/File Lists/ClientDirectoryPickerViewController.swift +++ b/ownCloudAppShared/Client/File Lists/ClientDirectoryPickerViewController.swift @@ -21,7 +21,7 @@ import ownCloudSDK import ownCloudApp import CoreServices -public typealias ClientDirectoryPickerPathFilter = (_ path: String) -> Bool +public typealias ClientDirectoryPickerLocationFilter = (_ location: OCLocation) -> Bool public typealias ClientDirectoryPickerChoiceHandler = (_ chosenItem: OCItem?, _ needsToDismissViewController: Bool) -> Void extension NSErrorDomain { @@ -36,14 +36,14 @@ open class ClientDirectoryPickerViewController: ClientQueryViewController { open var selectButton: UIBarButtonItem? private var selectButtonTitle: String? private var cancelBarButton: UIBarButtonItem? - open var directoryPath : String? + open var directoryLocation : OCLocation? open var choiceHandler: ClientDirectoryPickerChoiceHandler? - open var allowedPathFilter : ClientDirectoryPickerPathFilter? - open var navigationPathFilter : ClientDirectoryPickerPathFilter? + open var allowedLocationFilter : ClientDirectoryPickerLocationFilter? + open var navigationLocationFilter : ClientDirectoryPickerLocationFilter? private var hasFavorites: Bool = false private var showFavorites: Bool { - if directoryPath == "/", hasFavorites == true { + if let directoryLocationPath = directoryLocation?.path, directoryLocationPath == "/", hasFavorites == true { return true } return false @@ -55,38 +55,38 @@ open class ClientDirectoryPickerViewController: ClientQueryViewController { ]), inputFilter:nil) // MARK: - Init & deinit - convenience public init(core inCore: OCCore, path: String, selectButtonTitle: String, avoidConflictsWith items: [OCItem], choiceHandler: @escaping ClientDirectoryPickerChoiceHandler) { - let folderItemPaths = items.filter({ (item) -> Bool in + convenience public init(core inCore: OCCore, location: OCLocation, selectButtonTitle: String, avoidConflictsWith items: [OCItem], choiceHandler: @escaping ClientDirectoryPickerChoiceHandler) { + let folderItemLocations = items.filter({ (item) -> Bool in return item.type == .collection && item.path != nil && !item.isRoot - }).map { (item) -> String in - return item.path! + }).map { (item) -> OCLocation in + return item.location! } - let itemParentPaths = items.filter({ (item) -> Bool in - return item.path?.parentPath != nil - }).map { (item) -> String in - return item.path!.parentPath + let itemParentLocations = items.filter({ (item) -> Bool in + return item.location?.parent != nil + }).map { (item) -> OCLocation in + return item.location!.parent } - var navigationPathFilter : ClientDirectoryPickerPathFilter? + var navigationPathFilter : ClientDirectoryPickerLocationFilter? - if folderItemPaths.count > 0 { - navigationPathFilter = { (targetPath) in - return !folderItemPaths.contains(targetPath) + if folderItemLocations.count > 0 { + navigationPathFilter = { (targetLocation) in + return !folderItemLocations.contains(targetLocation) } } - self.init(core: inCore, path: path, selectButtonTitle: selectButtonTitle, allowedPathFilter: { (targetPath) in + self.init(core: inCore, location: location, selectButtonTitle: selectButtonTitle, allowedLocationFilter: { (targetLocation) in // Disallow all paths as target that are parent of any of the items - return !itemParentPaths.contains(targetPath) - }, navigationPathFilter: navigationPathFilter, choiceHandler: choiceHandler) + return !itemParentLocations.contains(targetLocation) + }, navigationLocationFilter: navigationPathFilter, choiceHandler: choiceHandler) } - public init(core inCore: OCCore, path: String, selectButtonTitle: String, allowedPathFilter: ClientDirectoryPickerPathFilter? = nil, navigationPathFilter: ClientDirectoryPickerPathFilter? = nil, choiceHandler: @escaping ClientDirectoryPickerChoiceHandler) { - let targetDirectoryQuery = OCQuery(forPath: path) + public init(core inCore: OCCore, location: OCLocation, selectButtonTitle: String, allowedLocationFilter: ClientDirectoryPickerLocationFilter? = nil, navigationLocationFilter: ClientDirectoryPickerLocationFilter? = nil, choiceHandler: @escaping ClientDirectoryPickerChoiceHandler) { + let targetDirectoryQuery = OCQuery(for: location) // Sort folders first - targetDirectoryQuery.sortComparator = { (left, right) in - guard let leftItem = left as? OCItem, let rightItem = right as? OCItem else { + targetDirectoryQuery.sortComparator = { (leftVal, rightVal) in + guard let leftItem = leftVal as? OCItem, let rightItem = rightVal as? OCItem else { return .orderedSame } if leftItem.type == OCItemType.collection && rightItem.type != OCItemType.collection { @@ -101,13 +101,13 @@ open class ClientDirectoryPickerViewController: ClientQueryViewController { super.init(core: inCore, query: targetDirectoryQuery, rootViewController: nil) - self.directoryPath = path + self.directoryLocation = location self.choiceHandler = choiceHandler self.selectButtonTitle = selectButtonTitle - self.allowedPathFilter = allowedPathFilter - self.navigationPathFilter = navigationPathFilter + self.allowedLocationFilter = allowedLocationFilter + self.navigationLocationFilter = navigationLocationFilter // Force disable sorting options self.shallShowSortBar = true @@ -136,8 +136,8 @@ open class ClientDirectoryPickerViewController: ClientQueryViewController { selectButton = UIBarButtonItem(title: selectButtonTitle, style: .plain, target: self, action: #selector(selectButtonPressed)) selectButton?.title = selectButtonTitle - if let allowedPathFilter = allowedPathFilter, let directoryPath = directoryPath { - selectButton?.isEnabled = allowedPathFilter(directoryPath) + if let allowedLocationFilter = allowedLocationFilter, let directoryLocation = directoryLocation { + selectButton?.isEnabled = allowedLocationFilter(directoryLocation) } // Cancel button creation @@ -175,8 +175,8 @@ open class ClientDirectoryPickerViewController: ClientQueryViewController { var allowNavigation = item.type == .collection - if allowNavigation, let navigationPathFilter = navigationPathFilter, let itemPath = item.path { - allowNavigation = navigationPathFilter(itemPath) + if allowNavigation, let navigationLocationFilter = navigationLocationFilter, let itemLocation = item.location { + allowNavigation = navigationLocationFilter(itemLocation) } return allowNavigation @@ -252,11 +252,11 @@ open class ClientDirectoryPickerViewController: ClientQueryViewController { if showFavorites, indexPath.section == 0 { selectFavoriteItem() } else { - guard let item : OCItem = itemAt(indexPath: indexPath), item.type == OCItemType.collection, let core = self.core, let path = item.path, let selectButtonTitle = selectButtonTitle, let choiceHandler = choiceHandler else { + guard let item : OCItem = itemAt(indexPath: indexPath), item.type == OCItemType.collection, let core = self.core, let location = item.location, let selectButtonTitle = selectButtonTitle, let choiceHandler = choiceHandler else { return } - let pickerController = ClientDirectoryPickerViewController(core: core, path: path, selectButtonTitle: selectButtonTitle, allowedPathFilter: allowedPathFilter, navigationPathFilter: navigationPathFilter, choiceHandler: choiceHandler) + let pickerController = ClientDirectoryPickerViewController(core: core, location: location, selectButtonTitle: selectButtonTitle, allowedLocationFilter: allowedLocationFilter, navigationLocationFilter: navigationLocationFilter, choiceHandler: choiceHandler) pickerController.cancelAction = cancelAction pickerController.breadCrumbsPush = self.breadCrumbsPush @@ -345,11 +345,11 @@ open class ClientDirectoryPickerViewController: ClientQueryViewController { } customFileListController.didSelectCellAction = { [weak self, customFileListController] (completion) in - guard let favoriteIndexPath = customFileListController.tableView?.indexPathForSelectedRow, let item : OCItem = customFileListController.itemAt(indexPath: favoriteIndexPath), item.type == OCItemType.collection, let core = self?.core, let path = item.path, let selectButtonTitle = self?.selectButtonTitle, let choiceHandler = self?.choiceHandler else { + guard let favoriteIndexPath = customFileListController.tableView?.indexPathForSelectedRow, let item : OCItem = customFileListController.itemAt(indexPath: favoriteIndexPath), item.type == OCItemType.collection, let core = self?.core, let location = item.location, let selectButtonTitle = self?.selectButtonTitle, let choiceHandler = self?.choiceHandler else { return } - let pickerController = ClientDirectoryPickerViewController(core: core, path: path, selectButtonTitle: selectButtonTitle, allowedPathFilter: self?.allowedPathFilter, navigationPathFilter: self?.navigationPathFilter, choiceHandler: choiceHandler) + let pickerController = ClientDirectoryPickerViewController(core: core, location: location, selectButtonTitle: selectButtonTitle, allowedLocationFilter: self?.allowedLocationFilter, navigationLocationFilter: self?.navigationLocationFilter, choiceHandler: choiceHandler) pickerController.cancelAction = self?.cancelAction self?.navigationController?.pushViewController(pickerController, animated: true) @@ -366,12 +366,12 @@ open class ClientDirectoryPickerViewController: ClientQueryViewController { } } - public override func revealViewController(core: OCCore, path: String, item: OCItem, rootViewController: UIViewController?) -> UIViewController? { + public override func revealViewController(core: OCCore, location: OCLocation, item: OCItem, rootViewController: UIViewController?) -> UIViewController? { guard let selectButtonTitle = selectButtonTitle, let choiceHandler = choiceHandler else { return nil } - let pickerController = ClientDirectoryPickerViewController(core: core, path: path, selectButtonTitle: selectButtonTitle, allowedPathFilter: allowedPathFilter, navigationPathFilter: navigationPathFilter, choiceHandler: choiceHandler) + let pickerController = ClientDirectoryPickerViewController(core: core, location: location, selectButtonTitle: selectButtonTitle, allowedLocationFilter: allowedLocationFilter, navigationLocationFilter: navigationLocationFilter, choiceHandler: choiceHandler) pickerController.revealItemLocalID = item.localID pickerController.cancelAction = cancelAction diff --git a/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift b/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift index 68ec8efbb..ffd843750 100644 --- a/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift +++ b/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift @@ -102,7 +102,7 @@ open class ClientQueryViewController: QueryFileListTableViewController, UIDropIn super.init(core: inCore, query: inQuery) updateTitleView() - let lastPathComponent = (query.queryPath as NSString?)!.lastPathComponent + let lastPathComponent = (query.queryLocation?.path as NSString?)!.lastPathComponent if lastPathComponent.isRootPath { quotaObservation = core?.observe(\OCCore.rootQuotaBytesUsed, options: [.initial], changeHandler: { [weak self, weak core] (_, _) in let quotaUsed = core?.rootQuotaBytesUsed?.int64Value ?? 0 @@ -512,14 +512,14 @@ open class ClientQueryViewController: QueryFileListTableViewController, UIDropIn let tableViewController = BreadCrumbTableViewController() tableViewController.modalPresentationStyle = UIModalPresentationStyle.popover tableViewController.parentNavigationController = self.navigationController - tableViewController.queryPath = (query.queryPath as NSString?)! + tableViewController.queryPath = (query.queryLocation?.path as NSString?)! if let shortName = core?.bookmark.shortName { tableViewController.bookmarkShortName = shortName } if breadCrumbsPush { - tableViewController.navigationHandler = { [weak self] (path) in + tableViewController.navigationHandler = { [weak self] (location) in if let self = self, let core = self.core { - let queryViewController = ClientQueryViewController(core: core, query: OCQuery(forPath: path)) + let queryViewController = ClientQueryViewController(core: core, query: OCQuery(for: location)) queryViewController.breadCrumbsPush = true self.navigationController?.pushViewController(queryViewController, animated: true) @@ -576,7 +576,7 @@ open class ClientQueryViewController: QueryFileListTableViewController, UIDropIn } if let rootItem = self.query.rootItem, searchText == nil { - if query.queryPath != "/" { + if let queryPath = query.queryLocation?.path, queryPath != "/" { var totalSize = String(format: "Total: %@".localized, rootItem.sizeLocalized) if self.items.count == 1 { totalSize = String(format: "%@ item | ".localized, "\(self.items.count)") + totalSize @@ -586,7 +586,7 @@ open class ClientQueryViewController: QueryFileListTableViewController, UIDropIn self.updateFooter(text: totalSize) } - if let bookmarkContainer = self.tabBarController as? BookmarkContainer { + if let bookmarkContainer = self.tabBarController as? BookmarkContainer { // Use parent folder for UI state restoration let activity = OpenItemUserActivity(detailItem: rootItem, detailBookmark: bookmarkContainer.bookmark) view.window?.windowScene?.userActivity = activity.openItemUserActivity @@ -927,14 +927,14 @@ extension ClientQueryViewController { } open func updateTitleView() { - let lastPathComponent = (query.queryPath as NSString?)!.lastPathComponent + let lastPathComponent = (query.queryLocation?.path as NSString?)!.lastPathComponent if lastPathComponent.isRootPath, let shortName = core?.bookmark.shortName { self.navigationItem.title = shortName } else { if #available(iOS 14.0, *) { self.navigationItem.backButtonDisplayMode = .generic - let lastPathComponent = (query.queryPath as NSString?)!.lastPathComponent + let lastPathComponent = (query.queryLocation?.path as NSString?)!.lastPathComponent self.title = lastPathComponent } diff --git a/ownCloudAppShared/Client/File Lists/FileListTableViewController.swift b/ownCloudAppShared/Client/File Lists/FileListTableViewController.swift index 33145c15a..ff313819c 100644 --- a/ownCloudAppShared/Client/File Lists/FileListTableViewController.swift +++ b/ownCloudAppShared/Client/File Lists/FileListTableViewController.swift @@ -248,8 +248,8 @@ open class FileListTableViewController: UITableViewController, ClientItemCellDel // MARK: - Single item query creation open func query(forItem: OCItem) -> OCQuery? { - if let path = forItem.path { - return OCQuery(forPath: path) + if let location = forItem.location { + return OCQuery(for: location) } return nil diff --git a/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift b/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift index 25f0311ac..49c9850a6 100644 --- a/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift +++ b/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift @@ -456,13 +456,13 @@ open class QueryFileListTableViewController: FileListTableViewController, SortBa return cell! } - public func revealViewController(core: OCCore, path: String, item: OCItem, rootViewController: UIViewController?) -> UIViewController? { - return ClientQueryViewController(core: core, query: OCQuery(forPath: path), reveal: item, rootViewController: nil) + public func revealViewController(core: OCCore, location: OCLocation, item: OCItem, rootViewController: UIViewController?) -> UIViewController? { + return ClientQueryViewController(core: core, query: OCQuery(for: location), reveal: item, rootViewController: nil) } public func reveal(item: OCItem, core: OCCore, sender: AnyObject?) -> Bool { - if let parentPath = item.path?.parentPath, - let revealQueryViewController = revealViewController(core: core, path: parentPath, item: item, rootViewController: nil) { + if let parentLocation = item.location?.parent, + let revealQueryViewController = revealViewController(core: core, location: parentLocation, item: item, rootViewController: nil) { self.navigationController?.pushViewController(revealQueryViewController, animated: true) diff --git a/ownCloudAppShared/Client/Sharing/GroupSharingEditTableViewController.swift b/ownCloudAppShared/Client/Sharing/GroupSharingEditTableViewController.swift index e4fcaf04a..862bfcb8a 100644 --- a/ownCloudAppShared/Client/Sharing/GroupSharingEditTableViewController.swift +++ b/ownCloudAppShared/Client/Sharing/GroupSharingEditTableViewController.swift @@ -90,7 +90,7 @@ open class GroupSharingEditTableViewController: StaticTableViewController { guard let share = share else { return } if let recipient = share.recipient, let permissionMask = permissionMask { - let newShare = OCShare(recipient: recipient, path: share.itemPath, permissions: permissionMask, expiration: nil) + let newShare = OCShare(recipient: recipient, location: share.itemLocation, permissions: permissionMask, expiration: nil) self.core?.createShare(newShare, options: nil, completionHandler: { (error, _) in if error == nil { OnMainThread { diff --git a/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift b/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift index f1640bb3b..f1193a8b4 100644 --- a/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift +++ b/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift @@ -396,7 +396,7 @@ open class GroupSharingTableViewController: SharingTableViewController, UISearch for recipient in recipients { if !(self.shares.map { $0.recipient?.identifier == recipient.identifier }).contains(true) { - guard let itemPath = self.item.path else { continue } + guard let itemLocation = self.item.location else { continue } var title = "" var image: UIImage? var leadingAccessoryView : UIView? @@ -426,7 +426,7 @@ open class GroupSharingTableViewController: SharingTableViewController, UISearch rows.append( StaticTableViewRow(rowWithAction: { [weak self] (row, _) in guard let self = self else { return } - let share = OCShare(recipient: recipient, path: itemPath, permissions: self.defaultPermissions, expiration: nil) + let share = OCShare(recipient: recipient, location: itemLocation, permissions: self.defaultPermissions, expiration: nil) OnMainThread { self.searchController?.searchBar.text = "" diff --git a/ownCloudAppShared/Client/Sharing/PublicLinkEditTableViewController.swift b/ownCloudAppShared/Client/Sharing/PublicLinkEditTableViewController.swift index 205856bf8..50481b139 100644 --- a/ownCloudAppShared/Client/Sharing/PublicLinkEditTableViewController.swift +++ b/ownCloudAppShared/Client/Sharing/PublicLinkEditTableViewController.swift @@ -649,7 +649,7 @@ open class PublicLinkEditTableViewController: StaticTableViewController { } if let permissionMask = permissionMask { - let share = OCShare(publicLinkToPath: self.share.itemPath, linkName: shareName, permissions: permissionMask, password: password, expiration: expiration) + let share = OCShare(publicLinkTo: self.share.itemLocation, linkName: shareName, permissions: permissionMask, password: password, expiration: expiration) self.core.createShare(share, options: nil, completionHandler: { (error, createdShare) in if error == nil { if let shareURL = createdShare?.url { diff --git a/ownCloudAppShared/Client/Sharing/PublicLinkTableViewController.swift b/ownCloudAppShared/Client/Sharing/PublicLinkTableViewController.swift index f86cc6545..bad0ba49e 100644 --- a/ownCloudAppShared/Client/Sharing/PublicLinkTableViewController.swift +++ b/ownCloudAppShared/Client/Sharing/PublicLinkTableViewController.swift @@ -253,10 +253,10 @@ open class PublicLinkTableViewController: SharingTableViewController { // MARK: Add New Link Share @objc func addPublicLink() { - if let path = item.path, let core = core { + if let location = item.location, let core = core { - func createLink(for itemPath:String, with permissions:OCSharePermissionsMask) { - let share = OCShare(publicLinkToPath: itemPath, linkName: defaultLinkName(), permissions: permissions, password: nil, expiration: nil) + func createLink(for itemLocation:OCLocation, with permissions:OCSharePermissionsMask) { + let share = OCShare(publicLinkTo: itemLocation, linkName: defaultLinkName(), permissions: permissions, password: nil, expiration: nil) let editPublicLinkViewController = PublicLinkEditTableViewController(share: share, core: core, item: self.item, defaultLinkName: defaultLinkName()) editPublicLinkViewController.createLink = true let navigationController = ThemeNavigationController(rootViewController: editPublicLinkViewController) @@ -298,14 +298,14 @@ open class PublicLinkTableViewController: SharingTableViewController { var deepestShare : OCShare? for share in shares { - if share.itemPath == path { + if share.itemLocation == location { deepestShare = share break } else { - if path.hasPrefix(share.itemPath) { + if location.isLocated(in: share.itemLocation) { if deepestShare == nil { deepestShare = share - } else if let deepestShareItemPath = deepestShare?.itemPath, share.itemPath.count > deepestShareItemPath.count { + } else if let deepestShareItemPath = deepestShare?.itemLocation.path, let sharePath = share.itemLocation.path, sharePath.count > deepestShareItemPath.count { deepestShare = share } } @@ -314,12 +314,12 @@ open class PublicLinkTableViewController: SharingTableViewController { if let share = deepestShare { permissions = share.permissions - createLink(for: path, with: permissions!) + createLink(for: location, with: permissions!) } }) } else { permissions = [.create, .read] - createLink(for: path, with: permissions!) + createLink(for: location, with: permissions!) } } } diff --git a/ownCloudAppShared/Client/Sharing/ShareClientItemCell.swift b/ownCloudAppShared/Client/Sharing/ShareClientItemCell.swift index 20bd85235..c62b87cb5 100644 --- a/ownCloudAppShared/Client/Sharing/ShareClientItemCell.swift +++ b/ownCloudAppShared/Client/Sharing/ShareClientItemCell.swift @@ -24,7 +24,7 @@ open class ShareClientItemCell: ClientItemResolvingCell { // MARK: - Share Item open override func titleLabelString(for item: OCItem?) -> NSAttributedString { - if let shareItemPath = share?.itemPath { + if let shareItemPath = share?.itemLocation.path { return NSMutableAttributedString() .appendBold(shareItemPath) } @@ -41,7 +41,7 @@ open class ShareClientItemCell: ClientItemResolvingCell { self.iconView.activeViewProvider = ResourceItemIcon.file } - self.itemResolutionPath = share.itemPath + self.itemResolutionLocation = share.itemLocation self.updateLabels(with: item) } diff --git a/ownCloudAppShared/Client/User Interface/BreadCrumbTableViewController.swift b/ownCloudAppShared/Client/User Interface/BreadCrumbTableViewController.swift index ad6321a7c..3a1c639db 100644 --- a/ownCloudAppShared/Client/User Interface/BreadCrumbTableViewController.swift +++ b/ownCloudAppShared/Client/User Interface/BreadCrumbTableViewController.swift @@ -17,6 +17,7 @@ */ import UIKit +import ownCloudSDK open class BreadCrumbTableViewController: StaticTableViewController { @@ -30,7 +31,7 @@ open class BreadCrumbTableViewController: StaticTableViewController { open var parentNavigationController : UINavigationController? open var queryPath : NSString = "" open var bookmarkShortName : String? - open var navigationHandler : ((_ path: String) -> Void)? + open var navigationHandler : ((_ location: OCLocation) -> Void)? open override func viewDidLoad() { super.viewDidLoad() @@ -69,7 +70,7 @@ open class BreadCrumbTableViewController: StaticTableViewController { let aRow = StaticTableViewRow(rowWithAction: { [weak self] (_, _) in guard let self = self else { return } if let navigationHandler = self.navigationHandler { - navigationHandler(fullPath) + navigationHandler(OCLocation.legacyRootPath(fullPath)) } else { if stackViewControllers.indices.contains(stackIndex) { self.parentNavigationController?.popToViewController((stackViewControllers[stackIndex]), animated: true) diff --git a/ownCloudAppShared/SDK Extensions/OCCore+Extension.swift b/ownCloudAppShared/SDK Extensions/OCCore+Extension.swift index 324fd3c64..3466c191b 100644 --- a/ownCloudAppShared/SDK Extensions/OCCore+Extension.swift +++ b/ownCloudAppShared/SDK Extensions/OCCore+Extension.swift @@ -41,7 +41,7 @@ extension OCCore { shareQuery.initialPopulationHandler = { [weak self] query in let sharesWithMe = query.queryResults.filter({ (share) -> Bool in - return share.itemPath == item.path + return share.itemLocation == item.location }) combinedShares.addObjects(from: sharesWithMe) @@ -71,8 +71,8 @@ extension OCCore { if let shareQuery = OCShareQuery(scope: scope, item: nil) { shareQuery.initialPopulationHandler = { [weak self] query in let shares = query.queryResults.filter({ (share) -> Bool in - return (share.itemPath == item.path) || - (allowPartialMatch && (item.path?.hasPrefix(share.itemPath) == true)) + return (share.itemLocation == item.location) || + (allowPartialMatch && (item.location?.isLocated(in: share.itemLocation) == true)) }) initialPopulationHandler(shares) diff --git a/ownCloudAppShared/SDK Extensions/OCItem+Extension.swift b/ownCloudAppShared/SDK Extensions/OCItem+Extension.swift index c711663fd..744a1ef73 100644 --- a/ownCloudAppShared/SDK Extensions/OCItem+Extension.swift +++ b/ownCloudAppShared/SDK Extensions/OCItem+Extension.swift @@ -278,7 +278,7 @@ extension OCItem { repeat { lastParentPath = parentPath - database?.retrieveCacheItems(atPath: parentPath, itemOnly: true, completionHandler: { (_, error, _, items) in + database?.retrieveCacheItems(at: OCLocation(driveID: self.driveID, path: parentPath), itemOnly: true, completionHandler: { (_, error, _, items) in if error == nil, let parentItem = items?.first { parentPath = parentItem.path @@ -315,8 +315,8 @@ extension OCItem { } core.retrieveItemFromDatabase(forLocalID: parentItemLocalID) { (error, _, item) in - if parentItem == nil, let parentPath = self.path?.parentPath { - parentItem = try? core.cachedItem(atPath: parentPath) + if parentItem == nil, let parentLocation = self.location?.parent { + parentItem = try? core.cachedItem(at: parentLocation) } if completionHandler == nil { diff --git a/ownCloudAppShared/SDK Extensions/OCItemTracker.swift b/ownCloudAppShared/SDK Extensions/OCItemTracker.swift index 495510700..bc5352784 100644 --- a/ownCloudAppShared/SDK Extensions/OCItemTracker.swift +++ b/ownCloudAppShared/SDK Extensions/OCItemTracker.swift @@ -31,7 +31,7 @@ public class OCItemTracker: NSObject, OCCoreDelegate { public typealias CompletionHandler = (_ error: Error?, _ core: OCCore?, _ item: OCItem?) -> Void @discardableResult - public init(for bookmark: OCBookmark, at path: String, withErrorHandler: Bool = true, waitOnlineTimeout : TimeInterval? = nil, completionHandler: @escaping CompletionHandler) { + public init(for bookmark: OCBookmark, at location: OCLocation, withErrorHandler: Bool = true, waitOnlineTimeout : TimeInterval? = nil, completionHandler: @escaping CompletionHandler) { super.init() self.bookmark = bookmark @@ -48,22 +48,22 @@ public class OCItemTracker: NSObject, OCCoreDelegate { if let timeout = waitOnlineTimeout { if core.connectionStatus == .online { // Core online -> begin tracking immediately - self.beginTracking(at: path) + self.beginTracking(at: location) } else { // Force-start tracking after timeout … OnMainThread(after: timeout) { - self.beginTracking(at: path) + self.beginTracking(at: location) } // … or start tracking when the connection status flips to online self.connectionStatusObservation = core.observe(\OCCore.connectionStatus, changeHandler: { (core, _) in if core.connectionStatus == .online { - self.beginTracking(at: path) + self.beginTracking(at: location) } }) } } else { - self.beginTracking(at: path) + self.beginTracking(at: location) } } else { self.completeWith(error: error) @@ -71,7 +71,7 @@ public class OCItemTracker: NSObject, OCCoreDelegate { }) } - func beginTracking(at path: String) { + func beginTracking(at location: OCLocation) { var startTracking = false OCSynchronized(self) { @@ -82,7 +82,7 @@ public class OCItemTracker: NSObject, OCCoreDelegate { } if startTracking, let core = self.requestedCore { - self.itemTracking = core.trackItem(atPath: path, trackingHandler: { [weak core] (error, item, isInitial) in + self.itemTracking = core.trackItem(at: location, trackingHandler: { [weak core] (error, item, isInitial) in if isInitial { self.itemTracking = nil self.completeWith(error: error, core: core, item: item) From 76c0a3f4da4fd47fba1ecb62c6e7687a61454e0b Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Mon, 7 Mar 2022 16:34:22 +0100 Subject: [PATCH 010/328] - update iOS SDK - add ClientSpacesTableViewController as temporary solution to present and pick spaces for spaces-enabled accounts - adapt QueryFileListTableViewController, ClientDirectoryPickerViewController, ClientQueryViewController, ClientRootViewController to support drives structurally --- ownCloud.xcodeproj/project.pbxproj | 4 + .../Client/ClientRootViewController.swift | 15 +++- .../ClientDirectoryPickerViewController.swift | 3 +- .../ClientQueryViewController.swift | 12 ++- .../ClientSpacesTableViewController.swift | 85 +++++++++++++++++++ .../QueryFileListTableViewController.swift | 3 +- 6 files changed, 113 insertions(+), 9 deletions(-) create mode 100644 ownCloudAppShared/Client/File Lists/ClientSpacesTableViewController.swift diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index c5d73e7f7..154ee3469 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -366,6 +366,7 @@ DC973BBE24A28ED0001DEEC4 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCEC3DE3242F665D0076B43C /* CoreServices.framework */; }; DC98BBCB20FF815C00F4ED3E /* NSNumber+OCSyncAnchorData.m in Sources */ = {isa = PBXBuildFile; fileRef = DC98BBCA20FF815C00F4ED3E /* NSNumber+OCSyncAnchorData.m */; }; DC98BBD420FF824600F4ED3E /* FileProviderEnumeratorObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = DC98BBD320FF824600F4ED3E /* FileProviderEnumeratorObserver.m */; }; + DC9A116B27D0338400D90BA4 /* ClientSpacesTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC9A116A27D0338400D90BA4 /* ClientSpacesTableViewController.swift */; }; DC9BFBB320A19AF4007064B5 /* doc in Resources */ = {isa = PBXBuildFile; fileRef = DC9BFBB220A19AF3007064B5 /* doc */; }; DC9BFBBD20A1C37B007064B5 /* PasswordManagerAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC9BFBBC20A1C37B007064B5 /* PasswordManagerAccess.swift */; }; DC9C1AEC247C76470067895A /* MessageGroupCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC9C1AEB247C76470067895A /* MessageGroupCell.swift */; }; @@ -1352,6 +1353,7 @@ DC98BBCA20FF815C00F4ED3E /* NSNumber+OCSyncAnchorData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSNumber+OCSyncAnchorData.m"; sourceTree = ""; }; DC98BBD220FF824600F4ED3E /* FileProviderEnumeratorObserver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileProviderEnumeratorObserver.h; sourceTree = ""; }; DC98BBD320FF824600F4ED3E /* FileProviderEnumeratorObserver.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FileProviderEnumeratorObserver.m; sourceTree = ""; }; + DC9A116A27D0338400D90BA4 /* ClientSpacesTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientSpacesTableViewController.swift; sourceTree = ""; }; DC9BFBB220A19AF3007064B5 /* doc */ = {isa = PBXFileReference; lastKnownFileType = folder; path = doc; sourceTree = ""; }; DC9BFBB820A1AF2B007064B5 /* icon-locked.tvg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "icon-locked.tvg"; path = "img/filetypes-tvg/icon-locked.tvg"; sourceTree = SOURCE_ROOT; }; DC9BFBBA20A1B3CA007064B5 /* icon-password-manager.tvg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "icon-password-manager.tvg"; path = "img/filetypes-tvg/icon-password-manager.tvg"; sourceTree = SOURCE_ROOT; }; @@ -2929,6 +2931,7 @@ DC29F08F22974AEA00F77349 /* QueryFileListTableViewController.swift */, DC3BE0DC2077CC13002A0AC0 /* ClientQueryViewController.swift */, 2308F93C21467F6200CF0B91 /* ClientDirectoryPickerViewController.swift */, + DC9A116A27D0338400D90BA4 /* ClientSpacesTableViewController.swift */, ); path = "File Lists"; sourceTree = ""; @@ -4267,6 +4270,7 @@ DC0A357A24C0E43700FB58FC /* CardViewController.swift in Sources */, DC0A356B24C0E42200FB58FC /* AppLockManager.swift in Sources */, DCE4E4C724C255E00051722F /* AppExtensionNavigationController.swift in Sources */, + DC9A116B27D0338400D90BA4 /* ClientSpacesTableViewController.swift in Sources */, DC0A358024C0E43C00FB58FC /* NSObject+ThemeApplication.swift in Sources */, DC0A358224C0E44200FB58FC /* TVGImage.swift in Sources */, DCE4E44F24C1DF130051722F /* UIViewController+Extension.swift in Sources */, diff --git a/ownCloud/Client/ClientRootViewController.swift b/ownCloud/Client/ClientRootViewController.swift index 1741ec7a3..036c8db73 100644 --- a/ownCloud/Client/ClientRootViewController.swift +++ b/ownCloud/Client/ClientRootViewController.swift @@ -361,10 +361,17 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa if let localItemId = lastVisibleItemId { self.createFileListStack(for: localItemId) } else { - let query = OCQuery(for: .legacyRoot) - let queryViewController = ClientQueryViewController(core: core, query: query, rootViewController: self) + let topLevelViewController : UIViewController? + + if core.useDrives { + topLevelViewController = ClientSpacesTableViewController(core: core, rootViewController: self) + } else { + let query = OCQuery(for: .legacyRoot) + topLevelViewController = ClientQueryViewController(core: core, drive: nil, query: query, rootViewController: self) + } + // Because we have nested UINavigationControllers (first one from ServerListTableViewController and each item UITabBarController needs it own UINavigationController), we have to fake the UINavigationController logic. Here we insert the emptyViewController, because in the UI should appear a "Back" button if the root of the queryViewController is shown. Therefore we put at first the emptyViewController inside and at the same time the queryViewController. Now, the back button is shown and if the users push the "Back" button the ServerListTableViewController is shown. This logic can be found in navigationController(_: UINavigationController, willShow: UIViewController, animated: Bool) below. - self.filesNavigationController?.setViewControllers([self.emptyViewController, queryViewController], animated: false) + self.filesNavigationController?.setViewControllers([self.emptyViewController, topLevelViewController!], animated: false) } let emptyViewController = self.emptyViewController @@ -454,7 +461,7 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa core.retrieveItemFromDatabase(forLocalID: itemLocalID, completionHandler: { (error, _, item) in OnMainThread { let query = OCQuery(for: .legacyRoot) - let queryViewController = ClientQueryViewController(core: core, query: query, rootViewController: self) + let queryViewController = ClientQueryViewController(core: core, drive: nil, query: query, rootViewController: self) if error == nil, let item = item, item.isRoot == false { // get all parent items for the item and rebuild all underlaying ClientQueryViewController for this items in the navigation stack diff --git a/ownCloudAppShared/Client/File Lists/ClientDirectoryPickerViewController.swift b/ownCloudAppShared/Client/File Lists/ClientDirectoryPickerViewController.swift index d85ec2334..39ca3835c 100644 --- a/ownCloudAppShared/Client/File Lists/ClientDirectoryPickerViewController.swift +++ b/ownCloudAppShared/Client/File Lists/ClientDirectoryPickerViewController.swift @@ -83,6 +83,7 @@ open class ClientDirectoryPickerViewController: ClientQueryViewController { public init(core inCore: OCCore, location: OCLocation, selectButtonTitle: String, allowedLocationFilter: ClientDirectoryPickerLocationFilter? = nil, navigationLocationFilter: ClientDirectoryPickerLocationFilter? = nil, choiceHandler: @escaping ClientDirectoryPickerChoiceHandler) { let targetDirectoryQuery = OCQuery(for: location) + let drive = (location.driveID != nil) ? inCore.drive(withIdentifier: location.driveID!) : nil // Sort folders first targetDirectoryQuery.sortComparator = { (leftVal, rightVal) in @@ -99,7 +100,7 @@ open class ClientDirectoryPickerViewController: ClientQueryViewController { return .orderedSame } - super.init(core: inCore, query: targetDirectoryQuery, rootViewController: nil) + super.init(core: inCore, drive: drive, query: targetDirectoryQuery, rootViewController: nil) self.directoryLocation = location diff --git a/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift b/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift index ffd843750..3ddcad51d 100644 --- a/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift +++ b/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift @@ -48,6 +48,7 @@ open class ClientQueryViewController: QueryFileListTableViewController, UIDropIn private let ItemDataUTI = "com.owncloud.ios-app.item-data" private let moreCellIdentifier = "moreCell" private let moreCellAccessibilityIdentifier = "more-results" + public var drive : OCDrive? open override var activeQuery : OCQuery { if let customSearchQuery = customSearchQuery { @@ -91,13 +92,14 @@ open class ClientQueryViewController: QueryFileListTableViewController, UIDropIn // MARK: - Init & Deinit public override convenience init(core inCore: OCCore, query inQuery: OCQuery) { - self.init(core: inCore, query: inQuery, rootViewController: nil) + self.init(core: inCore, drive: nil, query: inQuery, rootViewController: nil) } - public init(core inCore: OCCore, query inQuery: OCQuery, reveal inItem: OCItem? = nil, rootViewController: UIViewController?) { + public init(core inCore: OCCore, drive inDrive: OCDrive?, query inQuery: OCQuery, reveal inItem: OCItem? = nil, rootViewController: UIViewController?) { clientRootViewController = rootViewController revealItemLocalID = inItem?.localID breadCrumbsPush = revealItemLocalID != nil + drive = inDrive super.init(core: inCore, query: inQuery) updateTitleView() @@ -930,7 +932,11 @@ extension ClientQueryViewController { let lastPathComponent = (query.queryLocation?.path as NSString?)!.lastPathComponent if lastPathComponent.isRootPath, let shortName = core?.bookmark.shortName { - self.navigationItem.title = shortName + if let drive = drive, let driveName = drive.name { + self.navigationItem.title = driveName + } else { + self.navigationItem.title = shortName + } } else { if #available(iOS 14.0, *) { self.navigationItem.backButtonDisplayMode = .generic diff --git a/ownCloudAppShared/Client/File Lists/ClientSpacesTableViewController.swift b/ownCloudAppShared/Client/File Lists/ClientSpacesTableViewController.swift new file mode 100644 index 000000000..727b64148 --- /dev/null +++ b/ownCloudAppShared/Client/File Lists/ClientSpacesTableViewController.swift @@ -0,0 +1,85 @@ +// +// ClientSpacesTableViewController.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 03.03.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK + +public class ClientSpacesTableViewController: StaticTableViewController { + public weak var core : OCCore? + public weak var rootViewController: UIViewController? + + public override func viewDidLoad() { + super.viewDidLoad() + + addSection(driveRowsSection) + + updateFromDrives() + } + + var driveListObserver : NSKeyValueObservation? + var driveRowsSection : StaticTableViewSection + + public init(core inCore: OCCore, rootViewController inRootViewController: UIViewController) { + driveRowsSection = StaticTableViewSection(headerTitle: nil, footerTitle: nil, identifier: "drive-rows", rows: []) + + super.init(style: .plain) + + core = inCore + rootViewController = inRootViewController + + self.navigationItem.title = inCore.bookmark.shortName + + driveListObserver = core?.observe(\OCCore.drives, changeHandler: { [weak self] core, change in + OnMainThread { + self?.updateFromDrives() + } + }) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func updateFromDrives() { + if let drives = core?.drives { + var driveRows : [StaticTableViewRow] = [] + + let sortedDrives = drives.sorted { drive1, drive2 in + let name1 = drive1.name ?? drive1.identifier + let name2 = drive2.name ?? drive2.identifier + + return name1.caseInsensitiveCompare(name2) == .orderedAscending + } + + for drive in sortedDrives { + driveRows.append(StaticTableViewRow(rowWithAction: { [weak self] (staticRow, sender) in + if let core = self?.core, let rootViewController = self?.rootViewController { + let query = OCQuery(for: drive.rootLocation) + let rootFolderViewController = ClientQueryViewController(core: core, drive: drive, query: query, rootViewController: rootViewController) + + self?.navigationController?.pushViewController(rootFolderViewController, animated: true) + } + }, title: drive.name ?? drive.identifier, subtitle: drive.type, accessoryType: .disclosureIndicator)) + } + + removeSection(driveRowsSection) + driveRowsSection.rows = driveRows + addSection(driveRowsSection) + } + } +} diff --git a/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift b/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift index 49c9850a6..53daf68ec 100644 --- a/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift +++ b/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift @@ -457,7 +457,8 @@ open class QueryFileListTableViewController: FileListTableViewController, SortBa } public func revealViewController(core: OCCore, location: OCLocation, item: OCItem, rootViewController: UIViewController?) -> UIViewController? { - return ClientQueryViewController(core: core, query: OCQuery(for: location), reveal: item, rootViewController: nil) + let drive = (location.driveID != nil) ? core.drive(withIdentifier: location.driveID!) : nil + return ClientQueryViewController(core: core, drive: drive, query: OCQuery(for: location), reveal: item, rootViewController: nil) } public func reveal(item: OCItem, core: OCCore, sender: AnyObject?) -> Bool { From b6b9bf959cf89f856e04e89156985a4360b27a5b Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Mon, 7 Mar 2022 21:08:07 +0100 Subject: [PATCH 011/328] Via SDK update: - fix duplicate drive entries - working eTag polling via drive list --- ios-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios-sdk b/ios-sdk index 8a204151e..c1e8c92b7 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit 8a204151e2f08cb990f4b867b08579b8c0e3098e +Subproject commit c1e8c92b72cf5d417e26f1077875c81b538b2bf6 From 883d1363dba82a366ad174043a5ee2d806cd725b Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Wed, 23 Mar 2022 20:46:11 +0100 Subject: [PATCH 012/328] - update SDK to include latest Drive and Data Source changes - update schemes to Xcode 13.3 --- external/libzip/libzip.xcodeproj/project.pbxproj | 2 +- ios-sdk | 2 +- ownCloud.xcodeproj/project.pbxproj | 4 +--- ownCloud.xcodeproj/xcshareddata/xcschemes/MakeTVG.xcscheme | 2 +- .../xcshareddata/xcschemes/ownCloud File Provider.xcscheme | 2 +- .../xcshareddata/xcschemes/ownCloud File ProviderUI.xcscheme | 2 +- .../xcshareddata/xcschemes/ownCloud Intents.xcscheme | 2 +- .../xcshareddata/xcschemes/ownCloud Share Extension.xcscheme | 2 +- ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme | 2 +- .../xcshareddata/xcschemes/ownCloudApp.xcscheme | 2 +- .../xcshareddata/xcschemes/ownCloudScreenshotsTests.xcscheme | 2 +- .../xcshareddata/xcschemes/ownCloudTests.xcscheme | 2 +- 12 files changed, 12 insertions(+), 14 deletions(-) diff --git a/external/libzip/libzip.xcodeproj/project.pbxproj b/external/libzip/libzip.xcodeproj/project.pbxproj index c200dd2a0..1e0930089 100644 --- a/external/libzip/libzip.xcodeproj/project.pbxproj +++ b/external/libzip/libzip.xcodeproj/project.pbxproj @@ -453,7 +453,7 @@ DCE93FDA21FCA42B000E14F2 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1240; + LastUpgradeCheck = 1330; ORGANIZATIONNAME = "ownCloud GmbH"; TargetAttributes = { DCE93FE221FCA42C000E14F2 = { diff --git a/ios-sdk b/ios-sdk index c1e8c92b7..bfeaea3e4 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit c1e8c92b72cf5d417e26f1077875c81b538b2bf6 +Subproject commit bfeaea3e437938c03a4f842c40a9e1e725bca664 diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 154ee3469..fc51cb8f9 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -105,7 +105,6 @@ 39BF67B225E804DF0039663F /* ownCloudUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2393697C2076110900BCE21A /* ownCloudUI.framework */; }; 39BF67B325E804EF0039663F /* ownCloudSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 239369782076110900BCE21A /* ownCloudSDK.framework */; }; 39BF67B425E804FB0039663F /* openssl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2347445E2076138000859C93 /* openssl.framework */; }; - 39BF684D25E8E2DD0039663F /* ownCloudAppShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 394A0AF922EEFC2C00603813 /* ownCloudAppShared.framework */; }; 39CC8AE6228C12100020253B /* Array+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CC8AE5228C12100020253B /* Array+Extension.swift */; }; 39CD755423D8392D00193950 /* EditDocumentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CD755323D8392D00193950 /* EditDocumentViewController.swift */; }; 39D06BEC229BE8D8000D7FC9 /* SettingsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39D06BEB229BE8D8000D7FC9 /* SettingsSection.swift */; }; @@ -1587,7 +1586,6 @@ 39DC7CE725C305E40001E08C /* ownCloudApp.framework in Frameworks */, 39BF67B225E804DF0039663F /* ownCloudUI.framework in Frameworks */, 39BF67B325E804EF0039663F /* ownCloudSDK.framework in Frameworks */, - 39BF684D25E8E2DD0039663F /* ownCloudAppShared.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3568,7 +3566,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1150; - LastUpgradeCheck = 1240; + LastUpgradeCheck = 1330; ORGANIZATIONNAME = "ownCloud GmbH"; TargetAttributes = { 233BDE9B204FEFE500C06732 = { diff --git a/ownCloud.xcodeproj/xcshareddata/xcschemes/MakeTVG.xcscheme b/ownCloud.xcodeproj/xcshareddata/xcschemes/MakeTVG.xcscheme index dc35afe39..619f2d312 100644 --- a/ownCloud.xcodeproj/xcshareddata/xcschemes/MakeTVG.xcscheme +++ b/ownCloud.xcodeproj/xcshareddata/xcschemes/MakeTVG.xcscheme @@ -1,6 +1,6 @@ Date: Wed, 6 Apr 2022 16:32:10 +0200 Subject: [PATCH 013/328] set up a new version 11.10.0 --- ownCloud.xcodeproj/project.pbxproj | 8 ++++---- ownCloudAppShared/Tools/VendorServices.swift | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 3d4d004b6..1e480a20c 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -4730,8 +4730,8 @@ APP_BUILD_FLAGS = "$(inherited)"; APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; - APP_SHORT_VERSION = 11.9.1; - APP_VERSION = 213; + APP_SHORT_VERSION = 11.10.0; + APP_VERSION = 214; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -4799,8 +4799,8 @@ APP_BUILD_FLAGS = "$(inherited)"; APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; - APP_SHORT_VERSION = 11.9.1; - APP_VERSION = 213; + APP_SHORT_VERSION = 11.10.0; + APP_VERSION = 214; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; diff --git a/ownCloudAppShared/Tools/VendorServices.swift b/ownCloudAppShared/Tools/VendorServices.swift index e44f5b15f..511a27fd0 100644 --- a/ownCloudAppShared/Tools/VendorServices.swift +++ b/ownCloudAppShared/Tools/VendorServices.swift @@ -167,8 +167,8 @@ extension VendorServices : OCClassSettingsSupport { public static func defaultSettings(forIdentifier identifier: OCClassSettingsIdentifier) -> [OCClassSettingsKey : Any]? { if identifier == .app { return [ - .isBetaBuild : false, - .showBetaWarning : false, + .isBetaBuild : true, + .showBetaWarning : true, .enableUIAnimations: true, .enableReviewPrompt: VendorServices.shared.enableReviewPrompt, From ace22e8344f1c45c8363654adbaf93a79c72cba5 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Thu, 7 Apr 2022 00:04:07 +0200 Subject: [PATCH 014/328] - progress snapshot, before refactoring DataSourceCollectionViewController --- ownCloud.xcodeproj/project.pbxproj | 19 +- .../Client/ClientRootViewController.swift | 3 +- .../DataSourceCollectionViewController.swift | 200 ++++++++++++++++++ .../_DataSourceCollectionViewController.swift | 189 +++++++++++++++++ ...llectionViewDiffableDataSource+Tools.swift | 17 ++ 5 files changed, 425 insertions(+), 3 deletions(-) create mode 100644 ownCloudAppShared/Client/File Lists/DataSourceCollectionViewController.swift create mode 100644 ownCloudAppShared/Client/File Lists/_DataSourceCollectionViewController.swift create mode 100644 ownCloudAppShared/UIKit Extension/UICollectionViewDiffableDataSource+Tools.swift diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index fc51cb8f9..6093b5980 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -204,6 +204,7 @@ DC049157258C00C400DEDC27 /* OCFileProviderServiceStandby.m in Sources */ = {isa = PBXBuildFile; fileRef = DC049155258C00C400DEDC27 /* OCFileProviderServiceStandby.m */; }; DC04920B258CB06A00DEDC27 /* PocketSVG in Frameworks */ = {isa = PBXBuildFile; productRef = DC04920A258CB06A00DEDC27 /* PocketSVG */; }; DC0492B1258CC4EE00DEDC27 /* PocketSVG.LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = DC0492B0258CC4EE00DEDC27 /* PocketSVG.LICENSE */; }; + DC04FFC827F5B79000F22569 /* DataSourceCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC04FFC727F5B79000F22569 /* DataSourceCollectionViewController.swift */; }; DC080CE5238AE3F40044C5D2 /* OCLicenseAppStoreProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DC080CE3238AE3ED0044C5D2 /* OCLicenseAppStoreProvider.m */; }; DC080CE6238AE3F40044C5D2 /* OCLicenseAppStoreProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = DC080CE2238AE3ED0044C5D2 /* OCLicenseAppStoreProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC080CF1238C8D850044C5D2 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC080CF0238C8D850044C5D2 /* StoreKit.framework */; }; @@ -438,6 +439,8 @@ DCE0275E21F1DF7E00F2544E /* ownCloudUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2393697C2076110900BCE21A /* ownCloudUI.framework */; settings = {ATTRIBUTES = (Required, ); }; }; DCE20272249AB50E0015A22A /* OCMessage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE20271249AB50E0015A22A /* OCMessage+Extension.swift */; }; DCE28F602433683700879DEC /* ClientSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE28F5F2433683700879DEC /* ClientSessionManager.swift */; }; + DCE2F03E27FADF2600E9E136 /* UICollectionViewDiffableDataSource+Tools.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE2F03D27FADF2600E9E136 /* UICollectionViewDiffableDataSource+Tools.swift */; }; + DCE2F04027FB857E00E9E136 /* _DataSourceCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE2F03F27FB857E00E9E136 /* _DataSourceCollectionViewController.swift */; }; DCE442CE2387452000940A6D /* LicensingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC0856B2293F1FD008CC05C /* LicensingTests.m */; }; DCE4E43124C197450051722F /* OpenItemUserActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 394E200B233E477F009D2897 /* OpenItemUserActivity.swift */; }; DCE4E43524C1999A0051722F /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E37F48A2188B27D00CF16CA /* Action.swift */; }; @@ -1228,6 +1231,7 @@ DC049154258C00C400DEDC27 /* OCFileProviderServiceStandby.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCFileProviderServiceStandby.h; sourceTree = ""; }; DC049155258C00C400DEDC27 /* OCFileProviderServiceStandby.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCFileProviderServiceStandby.m; sourceTree = ""; }; DC0492B0258CC4EE00DEDC27 /* PocketSVG.LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = PocketSVG.LICENSE; sourceTree = ""; }; + DC04FFC727F5B79000F22569 /* DataSourceCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataSourceCollectionViewController.swift; sourceTree = ""; }; DC080CE2238AE3ED0044C5D2 /* OCLicenseAppStoreProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCLicenseAppStoreProvider.h; sourceTree = ""; }; DC080CE3238AE3ED0044C5D2 /* OCLicenseAppStoreProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCLicenseAppStoreProvider.m; sourceTree = ""; }; DC080CE7238BD71F0044C5D2 /* OCLicenseAppStoreItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCLicenseAppStoreItem.h; sourceTree = ""; }; @@ -1442,6 +1446,8 @@ DCE0FC4823E42ACB0037B4AD /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; DCE20271249AB50E0015A22A /* OCMessage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCMessage+Extension.swift"; sourceTree = ""; }; DCE28F5F2433683700879DEC /* ClientSessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientSessionManager.swift; sourceTree = ""; }; + DCE2F03D27FADF2600E9E136 /* UICollectionViewDiffableDataSource+Tools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionViewDiffableDataSource+Tools.swift"; sourceTree = ""; }; + DCE2F03F27FB857E00E9E136 /* _DataSourceCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _DataSourceCollectionViewController.swift; sourceTree = ""; }; DCE4E43D24C19C3E0051722F /* Action+UserInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Action+UserInterface.swift"; sourceTree = ""; }; DCE4E44324C1A3E30051722F /* FileListTableViewController+OpenItemTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileListTableViewController+OpenItemTableViewController.swift"; sourceTree = ""; }; DCE4E44C24C1D48B0051722F /* QueryFileListTableViewController+Multiselect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueryFileListTableViewController+Multiselect.swift"; sourceTree = ""; }; @@ -1923,6 +1929,7 @@ 399725DF233DF37300FC3B94 /* UIKit Extension */ = { isa = PBXGroup; children = ( + DCE2F03D27FADF2600E9E136 /* UICollectionViewDiffableDataSource+Tools.swift */, 392CFEB62705831700631D2B /* LAContext+Extension.swift */, 399EA73925E656A900B6FF11 /* UITableView+Extension.swift */, DC248C66213E7DB00067FE94 /* NSLayoutConstraint+Extension.swift */, @@ -2930,6 +2937,8 @@ DC3BE0DC2077CC13002A0AC0 /* ClientQueryViewController.swift */, 2308F93C21467F6200CF0B91 /* ClientDirectoryPickerViewController.swift */, DC9A116A27D0338400D90BA4 /* ClientSpacesTableViewController.swift */, + DC04FFC727F5B79000F22569 /* DataSourceCollectionViewController.swift */, + DCE2F03F27FB857E00E9E136 /* _DataSourceCollectionViewController.swift */, ); path = "File Lists"; sourceTree = ""; @@ -3596,6 +3605,7 @@ }; 394A0AF822EEFC2C00603813 = { CreatedOnToolsVersion = 11.0; + LastSwiftMigration = 1330; ProvisioningStyle = Automatic; }; 39A7137F22E79C6700089423 = { @@ -4241,11 +4251,13 @@ files = ( 399EA6F625E6544100B6FF11 /* ShareClientItemCell.swift in Sources */, DC0A357E24C0E43C00FB58FC /* ThemeStyle+Extensions.swift in Sources */, + DC04FFC827F5B79000F22569 /* DataSourceCollectionViewController.swift in Sources */, DCE4E44524C1A4260051722F /* FileListTableViewController.swift in Sources */, 399EA75A25E66DB000B6FF11 /* ClientItemResolvingCell.swift in Sources */, DCE4E43524C1999A0051722F /* Action.swift in Sources */, DC0A356C24C0E42200FB58FC /* AppLockWindow.swift in Sources */, DCE4E43B24C19B4F0051722F /* NSLayoutConstraint+Extension.swift in Sources */, + DCE2F03E27FADF2600E9E136 /* UICollectionViewDiffableDataSource+Tools.swift in Sources */, DC0A355724C0E35B00FB58FC /* UIDevice+UIUserInterfaceIdiom.swift in Sources */, DC0A359224C0E55800FB58FC /* UIColor+Extension.swift in Sources */, 399EA6F825E6544100B6FF11 /* PublicLinkTableViewController.swift in Sources */, @@ -4270,6 +4282,7 @@ DCE4E4C724C255E00051722F /* AppExtensionNavigationController.swift in Sources */, DC9A116B27D0338400D90BA4 /* ClientSpacesTableViewController.swift in Sources */, DC0A358024C0E43C00FB58FC /* NSObject+ThemeApplication.swift in Sources */, + DCE2F04027FB857E00E9E136 /* _DataSourceCollectionViewController.swift in Sources */, DC0A358224C0E44200FB58FC /* TVGImage.swift in Sources */, DCE4E44F24C1DF130051722F /* UIViewController+Extension.swift in Sources */, DC0A357F24C0E43C00FB58FC /* ThemeStyle+DefaultStyles.swift in Sources */, @@ -4866,7 +4879,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_PREPROCESSOR_DEFINITIONS = "$(APP_BUILD_FLAGS)"; - IPHONEOS_DEPLOYMENT_TARGET = 13.6; + IPHONEOS_DEPLOYMENT_TARGET = 15.4; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -4932,7 +4945,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_PREPROCESSOR_DEFINITIONS = "$(APP_BUILD_FLAGS)"; - IPHONEOS_DEPLOYMENT_TARGET = 13.6; + IPHONEOS_DEPLOYMENT_TARGET = 15.4; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(APP_BUILD_FLAGS_SWIFT)"; @@ -5057,6 +5070,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -5096,6 +5110,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; diff --git a/ownCloud/Client/ClientRootViewController.swift b/ownCloud/Client/ClientRootViewController.swift index 036c8db73..8529ea1b1 100644 --- a/ownCloud/Client/ClientRootViewController.swift +++ b/ownCloud/Client/ClientRootViewController.swift @@ -364,7 +364,8 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa let topLevelViewController : UIViewController? if core.useDrives { - topLevelViewController = ClientSpacesTableViewController(core: core, rootViewController: self) +// topLevelViewController = ClientSpacesTableViewController(core: core, rootViewController: self) + topLevelViewController = DataSourceCollectionViewController(core: core, dataSource: core.hierarchicDrivesDataSource, rootViewController: self) } else { let query = OCQuery(for: .legacyRoot) topLevelViewController = ClientQueryViewController(core: core, drive: nil, query: query, rootViewController: self) diff --git a/ownCloudAppShared/Client/File Lists/DataSourceCollectionViewController.swift b/ownCloudAppShared/Client/File Lists/DataSourceCollectionViewController.swift new file mode 100644 index 000000000..cece28bcd --- /dev/null +++ b/ownCloudAppShared/Client/File Lists/DataSourceCollectionViewController.swift @@ -0,0 +1,200 @@ +// +// DataSourceCollectionViewController.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 31.03.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +import UIKit +import ownCloudSDK + +private let reuseIdentifier = "Cell" + +public class CollectionViewCellProvider: NSObject { + public typealias CellProvider = (_ collectionView: UICollectionView, _ itemRecord: OCDataItemRecord, _ itemRef: OCDataItemReference, _ indexPath: IndexPath) -> UICollectionViewCell + + var provider : CellProvider + var dataItemType : OCDataItemType + + public func provideCell(for collectionView: UICollectionView, itemRecord: OCDataItemRecord, itemRef: OCDataItemReference, indexPath: IndexPath) -> UICollectionViewCell { + return provider(collectionView, itemRecord, itemRef, indexPath) + } + + public init(for type : OCDataItemType, with cellProvider: @escaping CellProvider) { + provider = cellProvider + dataItemType = type + + super.init() + } +} + +public class DataSourceCollectionViewController: UIViewController, UICollectionViewDelegate { + static var cellProviders : [OCDataItemType:CollectionViewCellProvider] = [:] + + public static func register(cellProvider: CollectionViewCellProvider) { + cellProviders[cellProvider.dataItemType] = cellProvider + } + + public static func cellProvider(for itemRecord: OCDataItemRecord) -> CollectionViewCellProvider? { + return cellProviders[itemRecord.type] + } + + public static func cellProvider(for itemType: OCDataItemType) -> CollectionViewCellProvider? { + return cellProviders[itemType] + } + + public weak var core : OCCore? + public weak var rootViewController: UIViewController? + + public var dataSource: OCDataSource? + public var dataSourceSubscription : OCDataSourceSubscription? + + public init(core inCore: OCCore, dataSource inDataSource: OCDataSource?, rootViewController inRootViewController: UIViewController) { + super.init(nibName: nil, bundle: nil) + + core = inCore + rootViewController = inRootViewController + dataSource = inDataSource + + self.navigationItem.title = inCore.bookmark.shortName + + // Register cell providers for .drive and .presentable + let cellRegistration = UICollectionView.CellRegistration { [weak self] (cell, indexPath, itemRef) in + var content = cell.defaultContentConfiguration() + + if let itemRecord = try? self?.dataSource?.record(forItemRef: itemRef) { + if let item = itemRecord?.item { + if let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil, withOptions: nil) as? OCDataItemPresentable { + content.text = presentable.title + content.secondaryText = presentable.subtitle + } + } else { + // Request reconfiguration of cell + itemRecord?.retrieveItem(completionHandler: { error, itemRecord in + self?.collectionViewDataSource.requestReconfigurationOfItems([itemRef]) + }) + } + } + + cell.contentConfiguration = content + cell.accessories = [ .disclosureIndicator() ] + } + + DataSourceCollectionViewController.register(cellProvider: CollectionViewCellProvider(for: .drive, with: { collectionView, itemRecord, itemRef, indexPath in + return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemRef) + })) + + DataSourceCollectionViewController.register(cellProvider: CollectionViewCellProvider(for: .presentable, with: { collectionView, itemRecord, itemRef, indexPath in + return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemRef) + })) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + dataSourceSubscription?.terminate() + } + + func handleListUpdates(from subscription: OCDataSourceSubscription) { + updateFromSubscription(animatingDifferences: true) + } + + /// Collection View implementation + enum Section: CaseIterable { + case spaces + } + + var collectionView : UICollectionView! = nil + var collectionViewDataSource: UICollectionViewDiffableDataSource! = nil + + public override func viewDidLoad() { + super.viewDidLoad() + configureHierarchy() + configureDataSource() + + dataSourceSubscription = dataSource?.subscribe(updateHandler: { [weak self] (subscription) in + self?.handleListUpdates(from: subscription) + }, on: .main, trackDifferences: true, performIntialUpdate: true) + } + + func createLayout() -> UICollectionViewLayout { + let config = UICollectionLayoutListConfiguration(appearance: .plain) + return UICollectionViewCompositionalLayout.list(using: config) + } + + func configureHierarchy() { + collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createLayout()) + collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + view.addSubview(collectionView) + collectionView.delegate = self + } + + func configureDataSource() { + collectionViewDataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { [weak self] (collectionView: UICollectionView, indexPath: IndexPath, itemRef: OCDataItemReference) -> UICollectionViewCell? in + return self?.provideReusableCell(for: collectionView, itemRef: itemRef, indexPath: indexPath) ?? UICollectionViewCell() + } + + // initial data + updateFromSubscription(animatingDifferences: false) + } + + func provideReusableCell(for collectionView: UICollectionView, itemRef: OCDataItemReference, indexPath: IndexPath) -> UICollectionViewCell { + var cell: UICollectionViewCell? + + if let itemRecord = try? dataSource?.record(forItemRef: itemRef), let itemRecord = itemRecord { + var cellProvider = DataSourceCollectionViewController.cellProvider(for: itemRecord) + + if cellProvider == nil { + cellProvider = DataSourceCollectionViewController.cellProvider(for: .presentable) + } + + if let cellProvider = cellProvider { + cell = cellProvider.provideCell(for: collectionView, itemRecord: itemRecord, itemRef: itemRef, indexPath: indexPath) + } + } + + return cell ?? UICollectionViewCell() + } + + func updateFromSubscription(animatingDifferences: Bool = true) { + if let datasourceSnapshot = dataSourceSubscription?.snapshotResettingChangeTracking(true) { + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.spaces]) + + let items = datasourceSnapshot.items + + if items.count > 0 { + snapshot.appendItems(datasourceSnapshot.items) + } + + if let updatedItems = datasourceSnapshot.updatedItems, updatedItems.count > 0 { + snapshot.reloadItems(Array(updatedItems)) + } + + collectionViewDataSource.apply(snapshot, animatingDifferences: animatingDifferences) + } + } + + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + guard let itemRef = self.collectionViewDataSource.itemIdentifier(for: indexPath) else { + collectionView.deselectItem(at: indexPath, animated: true) + return + } + + dataSource?.retrieveItem(forRef: itemRef, reusing: nil, completionHandler: { [weak self] (error, record) in + if let drive = record?.item as? OCDrive { + if let core = self?.core, let rootViewController = self?.rootViewController { + let query = OCQuery(for: drive.rootLocation) + let rootFolderViewController = ClientQueryViewController(core: core, drive: drive, query: query, rootViewController: rootViewController) + + collectionView.deselectItem(at: indexPath, animated: true) + + self?.navigationController?.pushViewController(rootFolderViewController, animated: true) + } + } + }) + } +} diff --git a/ownCloudAppShared/Client/File Lists/_DataSourceCollectionViewController.swift b/ownCloudAppShared/Client/File Lists/_DataSourceCollectionViewController.swift new file mode 100644 index 000000000..263c898c9 --- /dev/null +++ b/ownCloudAppShared/Client/File Lists/_DataSourceCollectionViewController.swift @@ -0,0 +1,189 @@ +// +// DataSourceCollectionViewController.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 04.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +import UIKit +import ownCloudSDK + +//private let reuseIdentifier = "Cell" +// +//extension OCDataItem { +// +//} + +//public class DataSourceSectionSnapshotProvider: NSObject { +// public var dataSource : OCDataSource +// public var subscription : OCDataSourceSubscription? +// +// private var changeCountByItemRef : NSCountedSet +// +// required init(datasource: OCDataSource) { +// self.dataSource = datasource +// +// changeCountByItemRef = NSCountedSet() +// +// super.init() +// +// subscription = dataSource.subscribe(updateHandler: { [weak self] (subscription) in +// self?.handleUpdates() +// }, on: .main, trackDifferences: true, performIntialUpdate: true) +// } +// +// deinit { +// subscription?.terminate() +// } +// +// func handleUpdates() { +// } +// +// func generateSectionSnapshot() -> NSDiffableDataSourceSectionSnapshot { +// if let subscription = subscription { +// let dataSnapshot = subscription.snapshotResettingChangeTracking(true) +// +// var items = dataSnapshot.items +// +// if let updatedItems = dataSnapshot.updatedItems { +// changeCountByItemRef.addingObjects(from: updatedItems) +// } +// } +// +// var sectionSnapshot = NSDiffableDataSourceSectionSnapshot() +// +// sectionSnapshot.append(dataSnapshot.items) +// sectionSnapshot. +// +// reloadItems(Array(dataSnapshot.updatedItems)) +// } +//} +// +//public class DataSourceCollectionViewController: UIViewController, UICollectionViewDelegate { +// public weak var core : OCCore? +// public weak var rootViewController: UIViewController? +// +// public var driveListSubscription : OCDataSourceSubscription? +// +// public init(core inCore: OCCore, rootViewController inRootViewController: UIViewController) { +// super.init(nibName: nil, bundle: nil) +// +// core = inCore +// rootViewController = inRootViewController +// +// self.navigationItem.title = inCore.bookmark.shortName +// +// driveListSubscription = core?.drivesDataSource.subscribe(updateHandler: { [weak self] (subscription) in +// self?.handleListUpdates(from: subscription) +// }, on: .main, trackDifferences: true, performIntialUpdate: true) +// } +// +// required init?(coder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +// +// deinit { +// driveListSubscription?.terminate() +// } +// +// func handleListUpdates(from subscription: OCDataSourceSubscription) { +// updateFromSubscription(animatingDifferences: true) +// } +// +// /// Collection View implementation +// enum Section: CaseIterable { +// case spaces +// } +// +// var collectionView : UICollectionView! = nil +// var dataSource: UICollectionViewDiffableDataSource! = nil +// +// public override func viewDidLoad() { +// super.viewDidLoad() +// configureHierarchy() +// configureDataSource() +// } +// +// func createLayout() -> UICollectionViewLayout { +// let config = UICollectionLayoutListConfiguration(appearance: .plain) +// return UICollectionViewCompositionalLayout.list(using: config) +// } +// +// func configureHierarchy() { +// collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createLayout()) +// collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] +// view.addSubview(collectionView) +// collectionView.delegate = self +// } +// +// func configureDataSource() { +//// let cellRegistration = UICollectionView.CellRegistration { (cell, indexPath, item) in +//// cell.updateWithItem(item) +//// cell.accessories = [.disclosureIndicator()] +//// } +// +// let cellRegistration = UICollectionView.CellRegistration { [weak self] (cell, indexPath, itemRef) in +// var content = cell.defaultContentConfiguration() +// +// if let itemRecord = try? self?.driveListSubscription?.source?.record(forItemRef: itemRef) { +// if let item = itemRecord?.item { +// if let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil, withOptions: nil) as? OCDataItemPresentable { +// content.text = presentable.title +// content.secondaryText = presentable.subtitle +// } +// } else { +// // Request reconfiguration of cell +// itemRecord?.retrieveItem(completionHandler: { error, itemRecord in +// self?.dataSource.requestReconfigurationOfItems([itemRef]) +// }) +// } +// } +// +// cell.contentConfiguration = content +// cell.accessories = [ .disclosureIndicator() ] +// } +// +// dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { (collectionView: UICollectionView, indexPath: IndexPath, itemRef: NSObject) -> UICollectionViewCell? in +// return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemRef) +// } +// +// // initial data +// updateFromSubscription(animatingDifferences: false) +// } +// +// func updateFromSubscription(animatingDifferences: Bool = true) { +// if let datasourceSnapshot = driveListSubscription?.snapshotResettingChangeTracking(true) { +// var snapshot = NSDiffableDataSourceSnapshot() +// snapshot.appendSections([.spaces]) +// +// snapshot.appendItems(datasourceSnapshot.items) +// +// if let updatedItems = datasourceSnapshot.updatedItems, updatedItems.count > 0 { +// snapshot.reloadItems(Array(updatedItems)) +// } +// +// dataSource.apply(snapshot, animatingDifferences: animatingDifferences) +// } +// } +// +// public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { +// guard let itemRef = self.dataSource.itemIdentifier(for: indexPath) else { +// collectionView.deselectItem(at: indexPath, animated: true) +// return +// } +// +// driveListSubscription?.source?.retrieveItem(forRef: itemRef, reusing: nil, completionHandler: { [weak self] (error, record) in +// if let drive = record?.item as? OCDrive { +// if let core = self?.core, let rootViewController = self?.rootViewController { +// let query = OCQuery(for: drive.rootLocation) +// let rootFolderViewController = ClientQueryViewController(core: core, drive: drive, query: query, rootViewController: rootViewController) +// +// collectionView.deselectItem(at: indexPath, animated: true) +// +// self?.navigationController?.pushViewController(rootFolderViewController, animated: true) +// } +// } +// }) +// } +//} diff --git a/ownCloudAppShared/UIKit Extension/UICollectionViewDiffableDataSource+Tools.swift b/ownCloudAppShared/UIKit Extension/UICollectionViewDiffableDataSource+Tools.swift new file mode 100644 index 000000000..f02e8361d --- /dev/null +++ b/ownCloudAppShared/UIKit Extension/UICollectionViewDiffableDataSource+Tools.swift @@ -0,0 +1,17 @@ +// +// UICollectionViewDiffableDataSource+Tools.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 04.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +import UIKit + +public extension UICollectionViewDiffableDataSource { + func requestReconfigurationOfItems(_ items: [ItemIdentifierType], animated: Bool = true) { + var snapshot = snapshot() + snapshot.reconfigureItems(items) + apply(snapshot, animatingDifferences: true) + } +} From 71af450c763f8cffd1f766abc420cc53a02dd409 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Thu, 7 Apr 2022 14:37:49 +0200 Subject: [PATCH 015/328] - CollectionViewController: generalized collection view controller based on one data source / section (WIP) --- ios-sdk | 2 +- ownCloud.xcodeproj/project.pbxproj | 8 +- .../Client/ClientRootViewController.swift | 2 +- .../File Lists/CollectionViewController.swift | 310 ++++++++++++++++++ .../DataSourceCollectionViewController.swift | 200 ----------- 5 files changed, 316 insertions(+), 206 deletions(-) create mode 100644 ownCloudAppShared/Client/File Lists/CollectionViewController.swift delete mode 100644 ownCloudAppShared/Client/File Lists/DataSourceCollectionViewController.swift diff --git a/ios-sdk b/ios-sdk index bfeaea3e4..9706be800 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit bfeaea3e437938c03a4f842c40a9e1e725bca664 +Subproject commit 9706be8000336854f3968e7f415e5f63827314ec diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 6093b5980..892c774e8 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -204,7 +204,7 @@ DC049157258C00C400DEDC27 /* OCFileProviderServiceStandby.m in Sources */ = {isa = PBXBuildFile; fileRef = DC049155258C00C400DEDC27 /* OCFileProviderServiceStandby.m */; }; DC04920B258CB06A00DEDC27 /* PocketSVG in Frameworks */ = {isa = PBXBuildFile; productRef = DC04920A258CB06A00DEDC27 /* PocketSVG */; }; DC0492B1258CC4EE00DEDC27 /* PocketSVG.LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = DC0492B0258CC4EE00DEDC27 /* PocketSVG.LICENSE */; }; - DC04FFC827F5B79000F22569 /* DataSourceCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC04FFC727F5B79000F22569 /* DataSourceCollectionViewController.swift */; }; + DC04FFC827F5B79000F22569 /* CollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC04FFC727F5B79000F22569 /* CollectionViewController.swift */; }; DC080CE5238AE3F40044C5D2 /* OCLicenseAppStoreProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DC080CE3238AE3ED0044C5D2 /* OCLicenseAppStoreProvider.m */; }; DC080CE6238AE3F40044C5D2 /* OCLicenseAppStoreProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = DC080CE2238AE3ED0044C5D2 /* OCLicenseAppStoreProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC080CF1238C8D850044C5D2 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC080CF0238C8D850044C5D2 /* StoreKit.framework */; }; @@ -1231,7 +1231,7 @@ DC049154258C00C400DEDC27 /* OCFileProviderServiceStandby.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCFileProviderServiceStandby.h; sourceTree = ""; }; DC049155258C00C400DEDC27 /* OCFileProviderServiceStandby.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCFileProviderServiceStandby.m; sourceTree = ""; }; DC0492B0258CC4EE00DEDC27 /* PocketSVG.LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = PocketSVG.LICENSE; sourceTree = ""; }; - DC04FFC727F5B79000F22569 /* DataSourceCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataSourceCollectionViewController.swift; sourceTree = ""; }; + DC04FFC727F5B79000F22569 /* CollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewController.swift; sourceTree = ""; }; DC080CE2238AE3ED0044C5D2 /* OCLicenseAppStoreProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCLicenseAppStoreProvider.h; sourceTree = ""; }; DC080CE3238AE3ED0044C5D2 /* OCLicenseAppStoreProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCLicenseAppStoreProvider.m; sourceTree = ""; }; DC080CE7238BD71F0044C5D2 /* OCLicenseAppStoreItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCLicenseAppStoreItem.h; sourceTree = ""; }; @@ -2937,7 +2937,7 @@ DC3BE0DC2077CC13002A0AC0 /* ClientQueryViewController.swift */, 2308F93C21467F6200CF0B91 /* ClientDirectoryPickerViewController.swift */, DC9A116A27D0338400D90BA4 /* ClientSpacesTableViewController.swift */, - DC04FFC727F5B79000F22569 /* DataSourceCollectionViewController.swift */, + DC04FFC727F5B79000F22569 /* CollectionViewController.swift */, DCE2F03F27FB857E00E9E136 /* _DataSourceCollectionViewController.swift */, ); path = "File Lists"; @@ -4251,7 +4251,7 @@ files = ( 399EA6F625E6544100B6FF11 /* ShareClientItemCell.swift in Sources */, DC0A357E24C0E43C00FB58FC /* ThemeStyle+Extensions.swift in Sources */, - DC04FFC827F5B79000F22569 /* DataSourceCollectionViewController.swift in Sources */, + DC04FFC827F5B79000F22569 /* CollectionViewController.swift in Sources */, DCE4E44524C1A4260051722F /* FileListTableViewController.swift in Sources */, 399EA75A25E66DB000B6FF11 /* ClientItemResolvingCell.swift in Sources */, DCE4E43524C1999A0051722F /* Action.swift in Sources */, diff --git a/ownCloud/Client/ClientRootViewController.swift b/ownCloud/Client/ClientRootViewController.swift index 8529ea1b1..f77b78c98 100644 --- a/ownCloud/Client/ClientRootViewController.swift +++ b/ownCloud/Client/ClientRootViewController.swift @@ -365,7 +365,7 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa if core.useDrives { // topLevelViewController = ClientSpacesTableViewController(core: core, rootViewController: self) - topLevelViewController = DataSourceCollectionViewController(core: core, dataSource: core.hierarchicDrivesDataSource, rootViewController: self) + topLevelViewController = CollectionViewController(core: core, dataSource: core.hierarchicDrivesDataSource, rootViewController: self) } else { let query = OCQuery(for: .legacyRoot) topLevelViewController = ClientQueryViewController(core: core, drive: nil, query: query, rootViewController: self) diff --git a/ownCloudAppShared/Client/File Lists/CollectionViewController.swift b/ownCloudAppShared/Client/File Lists/CollectionViewController.swift new file mode 100644 index 000000000..f896c08b2 --- /dev/null +++ b/ownCloudAppShared/Client/File Lists/CollectionViewController.swift @@ -0,0 +1,310 @@ +// +// CollectionViewController.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 31.03.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +import UIKit +import ownCloudSDK + +private let reuseIdentifier = "Cell" + +public class CollectionViewCellProvider: NSObject { + public typealias CellProvider = (_ collectionView: UICollectionView, _ cellConfiguration: OCDataItemCellConfiguration?, _ itemRecord: OCDataItemRecord, _ itemRef: OCDataItemReference, _ indexPath: IndexPath) -> UICollectionViewCell + + static var cellProviders : [OCDataItemType:CollectionViewCellProvider] = [:] + + public static func register(_ cellProvider: CollectionViewCellProvider) { + cellProviders[cellProvider.dataItemType] = cellProvider + } + + public static func providerFor(_ itemRecord: OCDataItemRecord) -> CollectionViewCellProvider? { + return cellProviders[itemRecord.type] + } + + public static func providerFor(_ itemType: OCDataItemType) -> CollectionViewCellProvider? { + return cellProviders[itemType] + } + + var provider : CellProvider + var dataItemType : OCDataItemType + + public func provideCell(for collectionView: UICollectionView, cellConfiguration: OCDataItemCellConfiguration?, itemRecord: OCDataItemRecord, itemRef: OCDataItemReference, indexPath: IndexPath) -> UICollectionViewCell { + // Save any existing cell configuration + let previousCellConfiguration = itemRef.ocDataItemCellConfiguration + + // Set cell configuration + itemRef.ocDataItemCellConfiguration = cellConfiguration + + // Ask provider to provide cell + let cell = provider(collectionView, cellConfiguration, itemRecord, itemRef, indexPath) + + // Restore previously existing cell configuration + itemRef.ocDataItemCellConfiguration = previousCellConfiguration + + return cell + } + + public init(for type : OCDataItemType, with cellProvider: @escaping CellProvider) { + provider = cellProvider + dataItemType = type + + super.init() + } +} + +public class CollectionViewSection: NSObject { + public typealias SectionIdentifier = String + + public var identifier: SectionIdentifier + + public var dataSource: OCDataSource? { + willSet { + dataSourceSubscription?.terminate() + dataSourceSubscription = nil + } + + didSet { + updateDatasourceSubscription() + } + } + public var dataSourceSubscription : OCDataSourceSubscription? + + weak public var collectionViewController : CollectionViewController? + + func updateDatasourceSubscription() { + if let dataSource = dataSource { + dataSourceSubscription = dataSource.subscribe(updateHandler: { [weak self] (subscription) in + self?.handleListUpdates(from: subscription) + }, on: .main, trackDifferences: true, performIntialUpdate: true) + } + } + + public init(identifier: SectionIdentifier, dataSource inDataSource: OCDataSource?) { + self.identifier = identifier + super.init() + + self.dataSource = inDataSource + updateDatasourceSubscription() // dataSource.didSet is not called during initialization + } + + deinit { + dataSourceSubscription?.terminate() + } + + func handleListUpdates(from subscription: OCDataSourceSubscription) { + collectionViewController?.updateSource(animatingDifferences: true) + } + + func provideReusableCell(for collectionView: UICollectionView, itemRef: OCDataItemReference, indexPath: IndexPath) -> UICollectionViewCell { + var cell: UICollectionViewCell? + + if let itemRecord = try? dataSource?.record(forItemRef: itemRef), let itemRecord = itemRecord { + var cellProvider = CollectionViewCellProvider.providerFor(itemRecord) + + if cellProvider == nil { + cellProvider = CollectionViewCellProvider.providerFor(.presentable) + } + + if let cellProvider = cellProvider, let dataSource = dataSource { + let cellConfiguration = OCDataItemCellConfiguration(source: dataSource) + + cellConfiguration.reference = itemRef + cellConfiguration.record = itemRecord + + cell = cellProvider.provideCell(for: collectionView, cellConfiguration: cellConfiguration, itemRecord: itemRecord, itemRef: itemRef, indexPath: indexPath) + } + } + + return cell ?? UICollectionViewCell() + } +} + +public class CollectionViewController: UIViewController, UICollectionViewDelegate { + + public weak var core : OCCore? + public weak var rootViewController: UIViewController? + + public init(core inCore: OCCore, dataSource inDataSource: OCDataSource?, rootViewController inRootViewController: UIViewController) { + super.init(nibName: nil, bundle: nil) + + core = inCore + rootViewController = inRootViewController + + self.navigationItem.title = inCore.bookmark.shortName + + // Register cell providers for .drive and .presentable + let cellRegistration = UICollectionView.CellRegistration { [weak self] (cell, indexPath, itemRef) in + var content = cell.defaultContentConfiguration() + + if let cellConfiguration = itemRef.ocDataItemCellConfiguration { + if let itemRecord = try? cellConfiguration.source?.record(forItemRef: itemRef) { + if let item = itemRecord?.item { + if let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil, withOptions: nil) as? OCDataItemPresentable { + content.text = presentable.title + content.secondaryText = presentable.subtitle + } + } else { + // Request reconfiguration of cell + itemRecord?.retrieveItem(completionHandler: { error, itemRecord in + self?.collectionViewDataSource.requestReconfigurationOfItems([itemRef]) + }) + } + } + } + + cell.contentConfiguration = content + cell.accessories = [ .disclosureIndicator() ] + } + + CollectionViewCellProvider.register(CollectionViewCellProvider(for: .drive, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in + return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemRef) + })) + + CollectionViewCellProvider.register(CollectionViewCellProvider(for: .presentable, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in + return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemRef) + })) + + // Add demo section + self.add(section: CollectionViewSection(identifier: "hierarchy", dataSource: inDataSource)) + self.add(section: CollectionViewSection(identifier: "all", dataSource: core!.projectDrivesDataSource)) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Collection View implementation + var collectionView : UICollectionView! = nil + var collectionViewDataSource: UICollectionViewDiffableDataSource! = nil + + public override func viewDidLoad() { + super.viewDidLoad() + configureHierarchy() + configureDataSource() + } + + func createLayout() -> UICollectionViewLayout { + let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped) + return UICollectionViewCompositionalLayout.list(using: config) + } + + func configureHierarchy() { + collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createLayout()) + collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + view.addSubview(collectionView) + collectionView.delegate = self + } + + func configureDataSource() { + collectionViewDataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { [weak self] (collectionView: UICollectionView, indexPath: IndexPath, itemRef: OCDataItemReference) -> UICollectionViewCell? in + // let dataSourceSectionIndex = collectionView.dataSourceSectionIndex(forPresentationSectionIndex: indexPath.section) // not sure if needed + if let sectionIdentifier = self?.collectionViewDataSource.sectionIdentifier(for: indexPath.section), + let section = self?.sectionsByID[sectionIdentifier] { + return section.provideReusableCell(for: collectionView, itemRef: itemRef, indexPath: indexPath) + } + + return UICollectionViewCell() + } + + // initial data + updateSource(animatingDifferences: false) + } + + var sections : [CollectionViewSection] = [] + var sectionsByID : [CollectionViewSection.SectionIdentifier : CollectionViewSection] = [:] + + func add(section: CollectionViewSection) { + section.collectionViewController = self + + sections.append(section) + sectionsByID[section.identifier] = section + + updateSource() + } + + func updateSource(animatingDifferences: Bool = true) { + guard let collectionViewDataSource = collectionViewDataSource else { + return + } + + var snapshot = NSDiffableDataSourceSnapshot() + + for section in sections { + snapshot.appendSections([section.identifier]) + + if let datasourceSnapshot = section.dataSourceSubscription?.snapshotResettingChangeTracking(true) { + snapshot.appendItems(datasourceSnapshot.items, toSection: section.identifier) + + if let updatedItems = datasourceSnapshot.updatedItems, updatedItems.count > 0 { + snapshot.reloadItems(Array(updatedItems)) + } + } + } + + collectionViewDataSource.apply(snapshot, animatingDifferences: animatingDifferences) + } + +// func provideReusableCell(for collectionView: UICollectionView, itemRef: OCDataItemReference, indexPath: IndexPath) -> UICollectionViewCell { +// var cell: UICollectionViewCell? +// +// if let itemRecord = try? dataSource?.record(forItemRef: itemRef), let itemRecord = itemRecord { +// var cellProvider = CollectionViewController.cellProvider(for: itemRecord) +// +// if cellProvider == nil { +// cellProvider = CollectionViewController.cellProvider(for: .presentable) +// } +// +// if let cellProvider = cellProvider { +// cell = cellProvider.provideCell(for: collectionView, itemRecord: itemRecord, itemRef: itemRef, indexPath: indexPath) +// } +// } +// +// return cell ?? UICollectionViewCell() +// } +// +// func updateFromSubscription(animatingDifferences: Bool = true) { +// if let datasourceSnapshot = dataSourceSubscription?.snapshotResettingChangeTracking(true) { +// var snapshot = NSDiffableDataSourceSnapshot() +// snapshot.appendSections([.spaces]) +// +// let items = datasourceSnapshot.items +// +// if items.count > 0 { +// snapshot.appendItems(datasourceSnapshot.items) +// } +// +// if let updatedItems = datasourceSnapshot.updatedItems, updatedItems.count > 0 { +// snapshot.reloadItems(Array(updatedItems)) +// } +// +// collectionViewDataSource.apply(snapshot, animatingDifferences: animatingDifferences) +// } +// } +// + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + guard let itemRef = collectionViewDataSource.itemIdentifier(for: indexPath) else { + collectionView.deselectItem(at: indexPath, animated: true) + return + } + + if let sectionIdentifier = collectionViewDataSource.sectionIdentifier(for: indexPath.section), + let section = sectionsByID[sectionIdentifier], + let dataSource = section.dataSource { + dataSource.retrieveItem(forRef: itemRef, reusing: nil, completionHandler: { [weak self] (error, record) in + if let drive = record?.item as? OCDrive { + if let core = self?.core, let rootViewController = self?.rootViewController { + let query = OCQuery(for: drive.rootLocation) + let rootFolderViewController = ClientQueryViewController(core: core, drive: drive, query: query, rootViewController: rootViewController) + + collectionView.deselectItem(at: indexPath, animated: true) + + self?.navigationController?.pushViewController(rootFolderViewController, animated: true) + } + } + }) + } + } +} diff --git a/ownCloudAppShared/Client/File Lists/DataSourceCollectionViewController.swift b/ownCloudAppShared/Client/File Lists/DataSourceCollectionViewController.swift deleted file mode 100644 index cece28bcd..000000000 --- a/ownCloudAppShared/Client/File Lists/DataSourceCollectionViewController.swift +++ /dev/null @@ -1,200 +0,0 @@ -// -// DataSourceCollectionViewController.swift -// ownCloudAppShared -// -// Created by Felix Schwarz on 31.03.22. -// Copyright © 2022 ownCloud GmbH. All rights reserved. -// - -import UIKit -import ownCloudSDK - -private let reuseIdentifier = "Cell" - -public class CollectionViewCellProvider: NSObject { - public typealias CellProvider = (_ collectionView: UICollectionView, _ itemRecord: OCDataItemRecord, _ itemRef: OCDataItemReference, _ indexPath: IndexPath) -> UICollectionViewCell - - var provider : CellProvider - var dataItemType : OCDataItemType - - public func provideCell(for collectionView: UICollectionView, itemRecord: OCDataItemRecord, itemRef: OCDataItemReference, indexPath: IndexPath) -> UICollectionViewCell { - return provider(collectionView, itemRecord, itemRef, indexPath) - } - - public init(for type : OCDataItemType, with cellProvider: @escaping CellProvider) { - provider = cellProvider - dataItemType = type - - super.init() - } -} - -public class DataSourceCollectionViewController: UIViewController, UICollectionViewDelegate { - static var cellProviders : [OCDataItemType:CollectionViewCellProvider] = [:] - - public static func register(cellProvider: CollectionViewCellProvider) { - cellProviders[cellProvider.dataItemType] = cellProvider - } - - public static func cellProvider(for itemRecord: OCDataItemRecord) -> CollectionViewCellProvider? { - return cellProviders[itemRecord.type] - } - - public static func cellProvider(for itemType: OCDataItemType) -> CollectionViewCellProvider? { - return cellProviders[itemType] - } - - public weak var core : OCCore? - public weak var rootViewController: UIViewController? - - public var dataSource: OCDataSource? - public var dataSourceSubscription : OCDataSourceSubscription? - - public init(core inCore: OCCore, dataSource inDataSource: OCDataSource?, rootViewController inRootViewController: UIViewController) { - super.init(nibName: nil, bundle: nil) - - core = inCore - rootViewController = inRootViewController - dataSource = inDataSource - - self.navigationItem.title = inCore.bookmark.shortName - - // Register cell providers for .drive and .presentable - let cellRegistration = UICollectionView.CellRegistration { [weak self] (cell, indexPath, itemRef) in - var content = cell.defaultContentConfiguration() - - if let itemRecord = try? self?.dataSource?.record(forItemRef: itemRef) { - if let item = itemRecord?.item { - if let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil, withOptions: nil) as? OCDataItemPresentable { - content.text = presentable.title - content.secondaryText = presentable.subtitle - } - } else { - // Request reconfiguration of cell - itemRecord?.retrieveItem(completionHandler: { error, itemRecord in - self?.collectionViewDataSource.requestReconfigurationOfItems([itemRef]) - }) - } - } - - cell.contentConfiguration = content - cell.accessories = [ .disclosureIndicator() ] - } - - DataSourceCollectionViewController.register(cellProvider: CollectionViewCellProvider(for: .drive, with: { collectionView, itemRecord, itemRef, indexPath in - return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemRef) - })) - - DataSourceCollectionViewController.register(cellProvider: CollectionViewCellProvider(for: .presentable, with: { collectionView, itemRecord, itemRef, indexPath in - return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemRef) - })) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - dataSourceSubscription?.terminate() - } - - func handleListUpdates(from subscription: OCDataSourceSubscription) { - updateFromSubscription(animatingDifferences: true) - } - - /// Collection View implementation - enum Section: CaseIterable { - case spaces - } - - var collectionView : UICollectionView! = nil - var collectionViewDataSource: UICollectionViewDiffableDataSource! = nil - - public override func viewDidLoad() { - super.viewDidLoad() - configureHierarchy() - configureDataSource() - - dataSourceSubscription = dataSource?.subscribe(updateHandler: { [weak self] (subscription) in - self?.handleListUpdates(from: subscription) - }, on: .main, trackDifferences: true, performIntialUpdate: true) - } - - func createLayout() -> UICollectionViewLayout { - let config = UICollectionLayoutListConfiguration(appearance: .plain) - return UICollectionViewCompositionalLayout.list(using: config) - } - - func configureHierarchy() { - collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createLayout()) - collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] - view.addSubview(collectionView) - collectionView.delegate = self - } - - func configureDataSource() { - collectionViewDataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { [weak self] (collectionView: UICollectionView, indexPath: IndexPath, itemRef: OCDataItemReference) -> UICollectionViewCell? in - return self?.provideReusableCell(for: collectionView, itemRef: itemRef, indexPath: indexPath) ?? UICollectionViewCell() - } - - // initial data - updateFromSubscription(animatingDifferences: false) - } - - func provideReusableCell(for collectionView: UICollectionView, itemRef: OCDataItemReference, indexPath: IndexPath) -> UICollectionViewCell { - var cell: UICollectionViewCell? - - if let itemRecord = try? dataSource?.record(forItemRef: itemRef), let itemRecord = itemRecord { - var cellProvider = DataSourceCollectionViewController.cellProvider(for: itemRecord) - - if cellProvider == nil { - cellProvider = DataSourceCollectionViewController.cellProvider(for: .presentable) - } - - if let cellProvider = cellProvider { - cell = cellProvider.provideCell(for: collectionView, itemRecord: itemRecord, itemRef: itemRef, indexPath: indexPath) - } - } - - return cell ?? UICollectionViewCell() - } - - func updateFromSubscription(animatingDifferences: Bool = true) { - if let datasourceSnapshot = dataSourceSubscription?.snapshotResettingChangeTracking(true) { - var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections([.spaces]) - - let items = datasourceSnapshot.items - - if items.count > 0 { - snapshot.appendItems(datasourceSnapshot.items) - } - - if let updatedItems = datasourceSnapshot.updatedItems, updatedItems.count > 0 { - snapshot.reloadItems(Array(updatedItems)) - } - - collectionViewDataSource.apply(snapshot, animatingDifferences: animatingDifferences) - } - } - - public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - guard let itemRef = self.collectionViewDataSource.itemIdentifier(for: indexPath) else { - collectionView.deselectItem(at: indexPath, animated: true) - return - } - - dataSource?.retrieveItem(forRef: itemRef, reusing: nil, completionHandler: { [weak self] (error, record) in - if let drive = record?.item as? OCDrive { - if let core = self?.core, let rootViewController = self?.rootViewController { - let query = OCQuery(for: drive.rootLocation) - let rootFolderViewController = ClientQueryViewController(core: core, drive: drive, query: query, rootViewController: rootViewController) - - collectionView.deselectItem(at: indexPath, animated: true) - - self?.navigationController?.pushViewController(rootFolderViewController, animated: true) - } - } - }) - } -} From a265ac479db809592c2d18db48962c36c06997db Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Fri, 8 Apr 2022 10:20:38 +0200 Subject: [PATCH 016/328] - CollectionView: seperate classes in their own source files, clean up structure --- ios-sdk | 2 +- ownCloud.xcodeproj/project.pbxproj | 12 + ownCloud/AppDelegate.swift | 2 + .../Client/ClientRootViewController.swift | 6 +- ...CellProvider+StandardImplementations.swift | 58 +++++ .../CollectionViewCellProvider.swift | 68 +++++ .../File Lists/CollectionViewController.swift | 235 +++--------------- .../File Lists/CollectionViewSection.swift | 88 +++++++ 8 files changed, 274 insertions(+), 197 deletions(-) create mode 100644 ownCloudAppShared/Client/File Lists/CollectionViewCellProvider+StandardImplementations.swift create mode 100644 ownCloudAppShared/Client/File Lists/CollectionViewCellProvider.swift create mode 100644 ownCloudAppShared/Client/File Lists/CollectionViewSection.swift diff --git a/ios-sdk b/ios-sdk index 9706be800..d77c12252 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit 9706be8000336854f3968e7f415e5f63827314ec +Subproject commit d77c12252601bf37f10be376498caa09af4f9f47 diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 892c774e8..ad1854ef8 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -526,6 +526,9 @@ DCFB74C221AD5D10005796AF /* StaticLoginSetupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4434C521AC898700376B16 /* StaticLoginSetupViewController.swift */; }; DCFB74C421AD7E18005796AF /* StaticLoginServerListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFB74C321AD7E18005796AF /* StaticLoginServerListViewController.swift */; }; DCFBAD0C21BE67A100943F76 /* ownCloudUI.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 2393697C2076110900BCE21A /* ownCloudUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + DCFC9ECC28002303005D9144 /* CollectionViewSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFC9ECB28002303005D9144 /* CollectionViewSection.swift */; }; + DCFC9ED128002335005D9144 /* CollectionViewCellProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFC9ED028002335005D9144 /* CollectionViewCellProvider.swift */; }; + DCFC9ED3280023BB005D9144 /* CollectionViewCellProvider+StandardImplementations.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFC9ED2280023BB005D9144 /* CollectionViewCellProvider+StandardImplementations.swift */; }; DCFEF90926EFA45A001DC7A4 /* VendorServices+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFEF90526EFA45A001DC7A4 /* VendorServices+App.swift */; }; DCFEFE2A236876BD009A142F /* OCLicenseManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DCFEFE28236876BD009A142F /* OCLicenseManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCFEFE2B236876BD009A142F /* OCLicenseManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFEFE29236876BD009A142F /* OCLicenseManager.m */; }; @@ -1510,6 +1513,9 @@ DCF575ED2796CE38003BEBBA /* OCViewHost.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCViewHost.h; sourceTree = ""; }; DCF575EE2796CE38003BEBBA /* OCViewHost.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCViewHost.m; sourceTree = ""; }; DCFB74C321AD7E18005796AF /* StaticLoginServerListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLoginServerListViewController.swift; sourceTree = ""; }; + DCFC9ECB28002303005D9144 /* CollectionViewSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewSection.swift; sourceTree = ""; }; + DCFC9ED028002335005D9144 /* CollectionViewCellProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewCellProvider.swift; sourceTree = ""; }; + DCFC9ED2280023BB005D9144 /* CollectionViewCellProvider+StandardImplementations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CollectionViewCellProvider+StandardImplementations.swift"; sourceTree = ""; }; DCFED971208095E200A2D984 /* ClientItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientItemCell.swift; sourceTree = ""; }; DCFED9B920809B8900A2D984 /* ThemeTVGResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeTVGResource.swift; sourceTree = ""; }; DCFEF90526EFA45A001DC7A4 /* VendorServices+App.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "VendorServices+App.swift"; sourceTree = ""; }; @@ -2937,6 +2943,9 @@ DC3BE0DC2077CC13002A0AC0 /* ClientQueryViewController.swift */, 2308F93C21467F6200CF0B91 /* ClientDirectoryPickerViewController.swift */, DC9A116A27D0338400D90BA4 /* ClientSpacesTableViewController.swift */, + DCFC9ED028002335005D9144 /* CollectionViewCellProvider.swift */, + DCFC9ED2280023BB005D9144 /* CollectionViewCellProvider+StandardImplementations.swift */, + DCFC9ECB28002303005D9144 /* CollectionViewSection.swift */, DC04FFC727F5B79000F22569 /* CollectionViewController.swift */, DCE2F03F27FB857E00E9E136 /* _DataSourceCollectionViewController.swift */, ); @@ -4255,7 +4264,9 @@ DCE4E44524C1A4260051722F /* FileListTableViewController.swift in Sources */, 399EA75A25E66DB000B6FF11 /* ClientItemResolvingCell.swift in Sources */, DCE4E43524C1999A0051722F /* Action.swift in Sources */, + DCFC9ECC28002303005D9144 /* CollectionViewSection.swift in Sources */, DC0A356C24C0E42200FB58FC /* AppLockWindow.swift in Sources */, + DCFC9ED128002335005D9144 /* CollectionViewCellProvider.swift in Sources */, DCE4E43B24C19B4F0051722F /* NSLayoutConstraint+Extension.swift in Sources */, DCE2F03E27FADF2600E9E136 /* UICollectionViewDiffableDataSource+Tools.swift in Sources */, DC0A355724C0E35B00FB58FC /* UIDevice+UIUserInterfaceIdiom.swift in Sources */, @@ -4312,6 +4323,7 @@ DC0A358C24C0E44B00FB58FC /* ThemeWindow.swift in Sources */, DC0A357124C0E42700FB58FC /* StaticTableViewRow.swift in Sources */, DCE4E43124C197450051722F /* OpenItemUserActivity.swift in Sources */, + DCFC9ED3280023BB005D9144 /* CollectionViewCellProvider+StandardImplementations.swift in Sources */, DC0A358D24C0E44B00FB58FC /* ThemedAlertController.swift in Sources */, DCE4E43C24C19B660051722F /* FrameViewController.swift in Sources */, DC0A35A124C1091400FB58FC /* UserInterfaceContext.swift in Sources */, diff --git a/ownCloud/AppDelegate.swift b/ownCloud/AppDelegate.swift index 569b22cb2..57e3b649b 100644 --- a/ownCloud/AppDelegate.swift +++ b/ownCloud/AppDelegate.swift @@ -52,6 +52,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { ThemeStyle.registerDefaultStyles() + CollectionViewCellProvider.registerStandardImplementations() + if VendorServices.shared.isBranded { staticLoginViewController = StaticLoginViewController(with: StaticLoginBundle.defaultBundle) navigationController = ThemeNavigationController(rootViewController: staticLoginViewController!) diff --git a/ownCloud/Client/ClientRootViewController.swift b/ownCloud/Client/ClientRootViewController.swift index f77b78c98..677d55b8c 100644 --- a/ownCloud/Client/ClientRootViewController.swift +++ b/ownCloud/Client/ClientRootViewController.swift @@ -364,8 +364,10 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa let topLevelViewController : UIViewController? if core.useDrives { -// topLevelViewController = ClientSpacesTableViewController(core: core, rootViewController: self) - topLevelViewController = CollectionViewController(core: core, dataSource: core.hierarchicDrivesDataSource, rootViewController: self) + topLevelViewController = CollectionViewController(core: core, rootViewController: self, sections: [ + CollectionViewSection(identifier: "top", dataSource: core.hierarchicDrivesDataSource), + CollectionViewSection(identifier: "projects", dataSource: core.projectDrivesDataSource) + ]) } else { let query = OCQuery(for: .legacyRoot) topLevelViewController = ClientQueryViewController(core: core, drive: nil, query: query, rootViewController: self) diff --git a/ownCloudAppShared/Client/File Lists/CollectionViewCellProvider+StandardImplementations.swift b/ownCloudAppShared/Client/File Lists/CollectionViewCellProvider+StandardImplementations.swift new file mode 100644 index 000000000..e1047e000 --- /dev/null +++ b/ownCloudAppShared/Client/File Lists/CollectionViewCellProvider+StandardImplementations.swift @@ -0,0 +1,58 @@ +// +// CollectionViewCellProvider+StandardImplementations.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 08.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK + +public extension CollectionViewCellProvider { + static func registerStandardImplementations() { + // Register cell providers for .drive and .presentable + let cellRegistration = UICollectionView.CellRegistration { (cell, indexPath, itemRef) in + var content = cell.defaultContentConfiguration() + + if let cellConfiguration = itemRef.ocDataItemCellConfiguration { + if let itemRecord = try? cellConfiguration.source?.record(forItemRef: itemRef) { + if let item = itemRecord?.item { + if let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil, withOptions: nil) as? OCDataItemPresentable { + content.text = presentable.title + content.secondaryText = presentable.subtitle + } + } else { + // Request reconfiguration of cell + itemRecord?.retrieveItem(completionHandler: { error, itemRecord in + if let collectionViewController = cellConfiguration.hostViewController as? CollectionViewController { + collectionViewController.collectionViewDataSource.requestReconfigurationOfItems([itemRef]) + } + }) + } + } + } + + cell.contentConfiguration = content + cell.accessories = [ .disclosureIndicator() ] + } + + CollectionViewCellProvider.register(CollectionViewCellProvider(for: .drive, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in + return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemRef) + })) + + CollectionViewCellProvider.register(CollectionViewCellProvider(for: .presentable, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in + return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemRef) + })) + } +} diff --git a/ownCloudAppShared/Client/File Lists/CollectionViewCellProvider.swift b/ownCloudAppShared/Client/File Lists/CollectionViewCellProvider.swift new file mode 100644 index 000000000..6702421d9 --- /dev/null +++ b/ownCloudAppShared/Client/File Lists/CollectionViewCellProvider.swift @@ -0,0 +1,68 @@ +// +// CollectionViewCellProvider.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 08.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK + +public class CollectionViewCellProvider: NSObject { + // MARK: - Types + public typealias CellProvider = (_ collectionView: UICollectionView, _ cellConfiguration: OCDataItemCellConfiguration?, _ itemRecord: OCDataItemRecord, _ itemRef: OCDataItemReference, _ indexPath: IndexPath) -> UICollectionViewCell + + // MARK: - Global registry + static var cellProviders : [OCDataItemType:CollectionViewCellProvider] = [:] + + public static func register(_ cellProvider: CollectionViewCellProvider) { + cellProviders[cellProvider.dataItemType] = cellProvider + } + + public static func providerFor(_ itemRecord: OCDataItemRecord) -> CollectionViewCellProvider? { + return cellProviders[itemRecord.type] + } + + public static func providerFor(_ itemType: OCDataItemType) -> CollectionViewCellProvider? { + return cellProviders[itemType] + } + + // MARK: - Implementation + + var provider : CellProvider + var dataItemType : OCDataItemType + + public func provideCell(for collectionView: UICollectionView, cellConfiguration: OCDataItemCellConfiguration?, itemRecord: OCDataItemRecord, itemRef: OCDataItemReference, indexPath: IndexPath) -> UICollectionViewCell { + // Save any existing cell configuration + let previousCellConfiguration = itemRef.ocDataItemCellConfiguration + + // Set cell configuration + itemRef.ocDataItemCellConfiguration = cellConfiguration + + // Ask provider to provide cell + let cell = provider(collectionView, cellConfiguration, itemRecord, itemRef, indexPath) + + // Restore previously existing cell configuration + itemRef.ocDataItemCellConfiguration = previousCellConfiguration + + return cell + } + + public init(for type : OCDataItemType, with cellProvider: @escaping CellProvider) { + provider = cellProvider + dataItemType = type + + super.init() + } +} diff --git a/ownCloudAppShared/Client/File Lists/CollectionViewController.swift b/ownCloudAppShared/Client/File Lists/CollectionViewController.swift index f896c08b2..f07499705 100644 --- a/ownCloudAppShared/Client/File Lists/CollectionViewController.swift +++ b/ownCloudAppShared/Client/File Lists/CollectionViewController.swift @@ -6,128 +6,25 @@ // Copyright © 2022 ownCloud GmbH. All rights reserved. // +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + import UIKit import ownCloudSDK -private let reuseIdentifier = "Cell" - -public class CollectionViewCellProvider: NSObject { - public typealias CellProvider = (_ collectionView: UICollectionView, _ cellConfiguration: OCDataItemCellConfiguration?, _ itemRecord: OCDataItemRecord, _ itemRef: OCDataItemReference, _ indexPath: IndexPath) -> UICollectionViewCell - - static var cellProviders : [OCDataItemType:CollectionViewCellProvider] = [:] - - public static func register(_ cellProvider: CollectionViewCellProvider) { - cellProviders[cellProvider.dataItemType] = cellProvider - } - - public static func providerFor(_ itemRecord: OCDataItemRecord) -> CollectionViewCellProvider? { - return cellProviders[itemRecord.type] - } - - public static func providerFor(_ itemType: OCDataItemType) -> CollectionViewCellProvider? { - return cellProviders[itemType] - } - - var provider : CellProvider - var dataItemType : OCDataItemType - - public func provideCell(for collectionView: UICollectionView, cellConfiguration: OCDataItemCellConfiguration?, itemRecord: OCDataItemRecord, itemRef: OCDataItemReference, indexPath: IndexPath) -> UICollectionViewCell { - // Save any existing cell configuration - let previousCellConfiguration = itemRef.ocDataItemCellConfiguration - - // Set cell configuration - itemRef.ocDataItemCellConfiguration = cellConfiguration - - // Ask provider to provide cell - let cell = provider(collectionView, cellConfiguration, itemRecord, itemRef, indexPath) - - // Restore previously existing cell configuration - itemRef.ocDataItemCellConfiguration = previousCellConfiguration - - return cell - } - - public init(for type : OCDataItemType, with cellProvider: @escaping CellProvider) { - provider = cellProvider - dataItemType = type - - super.init() - } -} - -public class CollectionViewSection: NSObject { - public typealias SectionIdentifier = String - - public var identifier: SectionIdentifier - - public var dataSource: OCDataSource? { - willSet { - dataSourceSubscription?.terminate() - dataSourceSubscription = nil - } - - didSet { - updateDatasourceSubscription() - } - } - public var dataSourceSubscription : OCDataSourceSubscription? - - weak public var collectionViewController : CollectionViewController? - - func updateDatasourceSubscription() { - if let dataSource = dataSource { - dataSourceSubscription = dataSource.subscribe(updateHandler: { [weak self] (subscription) in - self?.handleListUpdates(from: subscription) - }, on: .main, trackDifferences: true, performIntialUpdate: true) - } - } - - public init(identifier: SectionIdentifier, dataSource inDataSource: OCDataSource?) { - self.identifier = identifier - super.init() - - self.dataSource = inDataSource - updateDatasourceSubscription() // dataSource.didSet is not called during initialization - } - - deinit { - dataSourceSubscription?.terminate() - } - - func handleListUpdates(from subscription: OCDataSourceSubscription) { - collectionViewController?.updateSource(animatingDifferences: true) - } - - func provideReusableCell(for collectionView: UICollectionView, itemRef: OCDataItemReference, indexPath: IndexPath) -> UICollectionViewCell { - var cell: UICollectionViewCell? - - if let itemRecord = try? dataSource?.record(forItemRef: itemRef), let itemRecord = itemRecord { - var cellProvider = CollectionViewCellProvider.providerFor(itemRecord) - - if cellProvider == nil { - cellProvider = CollectionViewCellProvider.providerFor(.presentable) - } - - if let cellProvider = cellProvider, let dataSource = dataSource { - let cellConfiguration = OCDataItemCellConfiguration(source: dataSource) - - cellConfiguration.reference = itemRef - cellConfiguration.record = itemRecord - - cell = cellProvider.provideCell(for: collectionView, cellConfiguration: cellConfiguration, itemRecord: itemRecord, itemRef: itemRef, indexPath: indexPath) - } - } - - return cell ?? UICollectionViewCell() - } -} - public class CollectionViewController: UIViewController, UICollectionViewDelegate { public weak var core : OCCore? public weak var rootViewController: UIViewController? - public init(core inCore: OCCore, dataSource inDataSource: OCDataSource?, rootViewController inRootViewController: UIViewController) { + public init(core inCore: OCCore, rootViewController inRootViewController: UIViewController, sections inSections: [CollectionViewSection]?) { super.init(nibName: nil, bundle: nil) core = inCore @@ -135,69 +32,39 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat self.navigationItem.title = inCore.bookmark.shortName - // Register cell providers for .drive and .presentable - let cellRegistration = UICollectionView.CellRegistration { [weak self] (cell, indexPath, itemRef) in - var content = cell.defaultContentConfiguration() - - if let cellConfiguration = itemRef.ocDataItemCellConfiguration { - if let itemRecord = try? cellConfiguration.source?.record(forItemRef: itemRef) { - if let item = itemRecord?.item { - if let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil, withOptions: nil) as? OCDataItemPresentable { - content.text = presentable.title - content.secondaryText = presentable.subtitle - } - } else { - // Request reconfiguration of cell - itemRecord?.retrieveItem(completionHandler: { error, itemRecord in - self?.collectionViewDataSource.requestReconfigurationOfItems([itemRef]) - }) - } - } - } - - cell.contentConfiguration = content - cell.accessories = [ .disclosureIndicator() ] + // Add datasources + if let addSections = inSections { + add(sections: addSections) } - - CollectionViewCellProvider.register(CollectionViewCellProvider(for: .drive, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in - return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemRef) - })) - - CollectionViewCellProvider.register(CollectionViewCellProvider(for: .presentable, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in - return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemRef) - })) - - // Add demo section - self.add(section: CollectionViewSection(identifier: "hierarchy", dataSource: inDataSource)) - self.add(section: CollectionViewSection(identifier: "all", dataSource: core!.projectDrivesDataSource)) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - /// Collection View implementation + // MARK: - Collection View var collectionView : UICollectionView! = nil var collectionViewDataSource: UICollectionViewDiffableDataSource! = nil public override func viewDidLoad() { super.viewDidLoad() - configureHierarchy() + configureViews() configureDataSource() } - func createLayout() -> UICollectionViewLayout { + func createCollectionViewLayout() -> UICollectionViewLayout { let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped) return UICollectionViewCompositionalLayout.list(using: config) } - func configureHierarchy() { - collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createLayout()) + func configureViews() { + collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createCollectionViewLayout()) collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] view.addSubview(collectionView) collectionView.delegate = self } + // MARK: - Collection View Datasource func configureDataSource() { collectionViewDataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { [weak self] (collectionView: UICollectionView, indexPath: IndexPath, itemRef: OCDataItemReference) -> UICollectionViewCell? in // let dataSourceSectionIndex = collectionView.dataSourceSectionIndex(forPresentationSectionIndex: indexPath.section) // not sure if needed @@ -216,11 +83,27 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat var sections : [CollectionViewSection] = [] var sectionsByID : [CollectionViewSection.SectionIdentifier : CollectionViewSection] = [:] - func add(section: CollectionViewSection) { - section.collectionViewController = self + // MARK: - Sections + func add(sections sectionsToAdd: [CollectionViewSection]) { + for section in sectionsToAdd { + section.collectionViewController = self + + sections.append(section) + sectionsByID[section.identifier] = section + } + + updateSource() + } + + func remove(sections sectionsToRemove: [CollectionViewSection]) { + for section in sectionsToRemove { + section.collectionViewController = nil - sections.append(section) - sectionsByID[section.identifier] = section + if let sectionIdx = sections.firstIndex(of: section) { + sections.remove(at: sectionIdx) + sectionsByID[section.identifier] = nil + } + } updateSource() } @@ -247,43 +130,7 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat collectionViewDataSource.apply(snapshot, animatingDifferences: animatingDifferences) } -// func provideReusableCell(for collectionView: UICollectionView, itemRef: OCDataItemReference, indexPath: IndexPath) -> UICollectionViewCell { -// var cell: UICollectionViewCell? -// -// if let itemRecord = try? dataSource?.record(forItemRef: itemRef), let itemRecord = itemRecord { -// var cellProvider = CollectionViewController.cellProvider(for: itemRecord) -// -// if cellProvider == nil { -// cellProvider = CollectionViewController.cellProvider(for: .presentable) -// } -// -// if let cellProvider = cellProvider { -// cell = cellProvider.provideCell(for: collectionView, itemRecord: itemRecord, itemRef: itemRef, indexPath: indexPath) -// } -// } -// -// return cell ?? UICollectionViewCell() -// } -// -// func updateFromSubscription(animatingDifferences: Bool = true) { -// if let datasourceSnapshot = dataSourceSubscription?.snapshotResettingChangeTracking(true) { -// var snapshot = NSDiffableDataSourceSnapshot() -// snapshot.appendSections([.spaces]) -// -// let items = datasourceSnapshot.items -// -// if items.count > 0 { -// snapshot.appendItems(datasourceSnapshot.items) -// } -// -// if let updatedItems = datasourceSnapshot.updatedItems, updatedItems.count > 0 { -// snapshot.reloadItems(Array(updatedItems)) -// } -// -// collectionViewDataSource.apply(snapshot, animatingDifferences: animatingDifferences) -// } -// } -// + // MARK: - Collection View Delegate public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { guard let itemRef = collectionViewDataSource.itemIdentifier(for: indexPath) else { collectionView.deselectItem(at: indexPath, animated: true) diff --git a/ownCloudAppShared/Client/File Lists/CollectionViewSection.swift b/ownCloudAppShared/Client/File Lists/CollectionViewSection.swift new file mode 100644 index 000000000..de126e4bc --- /dev/null +++ b/ownCloudAppShared/Client/File Lists/CollectionViewSection.swift @@ -0,0 +1,88 @@ +// +// CollectionViewSection.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 08.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK + +public class CollectionViewSection: NSObject { + public typealias SectionIdentifier = String + + public var identifier: SectionIdentifier + + public var dataSource: OCDataSource? { + willSet { + dataSourceSubscription?.terminate() + dataSourceSubscription = nil + } + + didSet { + updateDatasourceSubscription() + } + } + public var dataSourceSubscription : OCDataSourceSubscription? + + weak public var collectionViewController : CollectionViewController? + + func updateDatasourceSubscription() { + if let dataSource = dataSource { + dataSourceSubscription = dataSource.subscribe(updateHandler: { [weak self] (subscription) in + self?.handleListUpdates(from: subscription) + }, on: .main, trackDifferences: true, performIntialUpdate: true) + } + } + + public init(identifier: SectionIdentifier, dataSource inDataSource: OCDataSource?) { + self.identifier = identifier + super.init() + + self.dataSource = inDataSource + updateDatasourceSubscription() // dataSource.didSet is not called during initialization + } + + deinit { + dataSourceSubscription?.terminate() + } + + func handleListUpdates(from subscription: OCDataSourceSubscription) { + collectionViewController?.updateSource(animatingDifferences: true) + } + + func provideReusableCell(for collectionView: UICollectionView, itemRef: OCDataItemReference, indexPath: IndexPath) -> UICollectionViewCell { + var cell: UICollectionViewCell? + + if let itemRecord = try? dataSource?.record(forItemRef: itemRef), let itemRecord = itemRecord { + var cellProvider = CollectionViewCellProvider.providerFor(itemRecord) + + if cellProvider == nil { + cellProvider = CollectionViewCellProvider.providerFor(.presentable) + } + + if let cellProvider = cellProvider, let dataSource = dataSource { + let cellConfiguration = OCDataItemCellConfiguration(source: dataSource) + + cellConfiguration.reference = itemRef + cellConfiguration.record = itemRecord + cellConfiguration.hostViewController = self.collectionViewController + + cell = cellProvider.provideCell(for: collectionView, cellConfiguration: cellConfiguration, itemRecord: itemRecord, itemRef: itemRef, indexPath: indexPath) + } + } + + return cell ?? UICollectionViewCell() + } +} From 867c787a6811efbf4fd7b4a26cfc4e0dbd1dc53b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=BChne?= Date: Fri, 8 Apr 2022 16:07:13 +0200 Subject: [PATCH 017/328] [feature/openssl-1.1.1] Migration to OpenSSL 1.1.0 (#1116) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * - migrated code to new OpenSSL 1.1.1 API - using OpenSSL Package * - update SDK * - update ios-sdk Co-authored-by: Matthias Hühne <> Co-authored-by: Felix Schwarz --- ios-sdk | 2 +- ownCloud.xcodeproj/project.pbxproj | 81 ++++--------------- .../xcshareddata/swiftpm/Package.resolved | 51 +++++++----- 3 files changed, 47 insertions(+), 87 deletions(-) diff --git a/ios-sdk b/ios-sdk index d2fa23526..95b7c5fc2 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit d2fa23526c6981d0ba53cc2e801424f6f532db8d +Subproject commit 95b7c5fc2b54c1077f08637590f19964179efb19 diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 1e480a20c..9d2182492 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -49,6 +49,7 @@ 391C79AE24E187C400CB6333 /* LegacyCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F2890F24BFAF0100E3D35C /* LegacyCredentials.swift */; }; 392378FF24EBD1A1006E86DE /* branding-splashscreen.png in Resources */ = {isa = PBXBuildFile; fileRef = 39F48A6624D848550000E3F9 /* branding-splashscreen.png */; }; 3923790524EBD1A5006E86DE /* branding-splashscreen-background.png in Resources */ = {isa = PBXBuildFile; fileRef = 39F48A6724D848550000E3F9 /* branding-splashscreen-background.png */; }; + 3924AB5827FC6A270055DC8A /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 3924AB5727FC6A270055DC8A /* OpenSSL */; }; 39265BB223D9987500B0C4CA /* MediaEditingAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39265BB123D9987500B0C4CA /* MediaEditingAction.swift */; }; 392CFEB72705831700631D2B /* LAContext+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 392CFEB62705831700631D2B /* LAContext+Extension.swift */; }; 392DDAE624C8923B009E5406 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 233BDEA6204FEFE500C06732 /* Assets.xcassets */; }; @@ -106,7 +107,6 @@ 39BF674C25E7FE020039663F /* CancelLabelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39BF674B25E7FE020039663F /* CancelLabelViewController.swift */; }; 39BF67B225E804DF0039663F /* ownCloudUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2393697C2076110900BCE21A /* ownCloudUI.framework */; }; 39BF67B325E804EF0039663F /* ownCloudSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 239369782076110900BCE21A /* ownCloudSDK.framework */; }; - 39BF67B425E804FB0039663F /* openssl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2347445E2076138000859C93 /* openssl.framework */; }; 39BF684D25E8E2DD0039663F /* ownCloudAppShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 394A0AF922EEFC2C00603813 /* ownCloudAppShared.framework */; }; 39CC8AE6228C12100020253B /* Array+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CC8AE5228C12100020253B /* Array+Extension.swift */; }; 39CD755423D8392D00193950 /* EditDocumentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CD755323D8392D00193950 /* EditDocumentViewController.swift */; }; @@ -306,12 +306,9 @@ DC36885824DC98BF00333600 /* OCFileProviderServiceSession.h in Headers */ = {isa = PBXBuildFile; fileRef = DC36885624DC98BF00333600 /* OCFileProviderServiceSession.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC36885924DC98BF00333600 /* OCFileProviderServiceSession.m in Sources */ = {isa = PBXBuildFile; fileRef = DC36885724DC98BF00333600 /* OCFileProviderServiceSession.m */; }; DC36885D24DD916800333600 /* ownCloudApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCC0855C2293F1FD008CC05C /* ownCloudApp.framework */; }; - DC36885E24DD917400333600 /* openssl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2347445E2076138000859C93 /* openssl.framework */; }; DC36885F24DD917900333600 /* ownCloudUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2393697C2076110900BCE21A /* ownCloudUI.framework */; }; DC36886224DDA9AB00333600 /* ProgressIndicatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC36886024DDA3C300333600 /* ProgressIndicatorViewController.swift */; }; - DC3BE0D72077BC5D002A0AC0 /* openssl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2347445E2076138000859C93 /* openssl.framework */; }; DC3BE0D82077BC5D002A0AC0 /* ownCloudSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 239369782076110900BCE21A /* ownCloudSDK.framework */; }; - DC3BE0D92077BC6B002A0AC0 /* openssl.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 2347445E2076138000859C93 /* openssl.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DC3BE0DA2077BC6B002A0AC0 /* ownCloudSDK.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 239369782076110900BCE21A /* ownCloudSDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DC3BE0DF2077CC14002A0AC0 /* ClientRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3BE0DD2077CC13002A0AC0 /* ClientRootViewController.swift */; }; DC3DEC7B22AFA1F000F3352D /* DownloadItemsHUDViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3DEC7A22AFA1F000F3352D /* DownloadItemsHUDViewController.swift */; }; @@ -361,7 +358,6 @@ DC85572C20513B8C00189B9A /* ServerListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC85572A20513B8C00189B9A /* ServerListTableViewController.swift */; }; DC85572D20513B8C00189B9A /* ServerListTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = DC85572B20513B8C00189B9A /* ServerListTableViewController.xib */; }; DC869A592153B1F60088977E /* OCMockingManager+SwiftTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC869A582153B1F60088977E /* OCMockingManager+SwiftTools.swift */; }; - DC8EB26C23927FDD009148F9 /* openssl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2347445E2076138000859C93 /* openssl.framework */; }; DC8EB271239308E5009148F9 /* LicenseOffersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC8EB270239308E5009148F9 /* LicenseOffersViewController.swift */; }; DC973BBE24A28ED0001DEEC4 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCEC3DE3242F665D0076B43C /* CoreServices.framework */; }; DC98BBCB20FF815C00F4ED3E /* NSNumber+OCSyncAnchorData.m in Sources */ = {isa = PBXBuildFile; fileRef = DC98BBCA20FF815C00F4ED3E /* NSNumber+OCSyncAnchorData.m */; }; @@ -540,13 +536,6 @@ remoteGlobalIDString = 233BDE9B204FEFE500C06732; remoteInfo = ownCloud; }; - 2347445D2076138000859C93 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = DCD344AF205BD0FA00189B9A /* openssl.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = DC30949D205749EA00189B9A; - remoteInfo = openssl; - }; 239369772076110900BCE21A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 233BDEBF204FEFF300C06732 /* ownCloudSDK.xcodeproj */; @@ -687,13 +676,6 @@ remoteGlobalIDString = DCC8F9AA202852A200EB6701; remoteInfo = ownCloudSDK; }; - DC3BE0CC2077BC52002A0AC0 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = DCD344AF205BD0FA00189B9A /* openssl.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = DC30949C205749EA00189B9A; - remoteInfo = openssl; - }; DC3BE0CE2077BC52002A0AC0 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 233BDEBF204FEFF300C06732 /* ownCloudSDK.xcodeproj */; @@ -722,13 +704,6 @@ remoteGlobalIDString = DCE93FE221FCA42C000E14F2; remoteInfo = libzip; }; - DC8EB26D23927FE7009148F9 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = DCD344AF205BD0FA00189B9A /* openssl.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = DC30949C205749EA00189B9A; - remoteInfo = openssl; - }; DCB2C058250C1C3F001083CA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 233BDE94204FEFE500C06732 /* Project object */; @@ -876,7 +851,6 @@ 394A0B0122EEFC2C00603813 /* ownCloudAppShared.framework in Copy Frameworks */, DCC085722293F1FD008CC05C /* ownCloudApp.framework in Copy Frameworks */, DCFBAD0C21BE67A100943F76 /* ownCloudUI.framework in Copy Frameworks */, - DC3BE0D92077BC6B002A0AC0 /* openssl.framework in Copy Frameworks */, DC3BE0DA2077BC6B002A0AC0 /* ownCloudSDK.framework in Copy Frameworks */, ); name = "Copy Frameworks"; @@ -1401,7 +1375,6 @@ DCD1301023A23F4E00255779 /* OCLicenseManager+AppStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCLicenseManager+AppStore.swift"; sourceTree = ""; }; DCD2D40522F06ECA0071FB8F /* DataSettingsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataSettingsSection.swift; sourceTree = ""; }; DCD344A5205BD0C000189B9A /* openssl.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = openssl.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - DCD344AF205BD0FA00189B9A /* openssl.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = openssl.xcodeproj; path = "ios-sdk/ownCloudUI/openssl/framework/openssl.xcodeproj"; sourceTree = ""; }; DCD71E7C2742745D001592C6 /* BuildOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BuildOptions.h; sourceTree = ""; }; DCD71E7D2742745D001592C6 /* BuildOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BuildOptions.m; sourceTree = ""; }; DCD810922398492C003B0053 /* OCLicenseDuration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCLicenseDuration.h; sourceTree = ""; }; @@ -1505,8 +1478,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3924AB5827FC6A270055DC8A /* OpenSSL in Frameworks */, 024F3A2124A3AB410083E11E /* CrashReporter in Frameworks */, - DC3BE0D72077BC5D002A0AC0 /* openssl.framework in Frameworks */, 394A0B0022EEFC2C00603813 /* ownCloudAppShared.framework in Frameworks */, DC3BE0D82077BC5D002A0AC0 /* ownCloudSDK.framework in Frameworks */, DC080CF1238C8D850044C5D2 /* StoreKit.framework in Frameworks */, @@ -1551,7 +1524,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 39BF67B425E804FB0039663F /* openssl.framework in Frameworks */, 39DC7CF425C305E80001E08C /* ownCloudAppShared.framework in Frameworks */, 39DC7CE725C305E40001E08C /* ownCloudApp.framework in Frameworks */, 39BF67B225E804DF0039663F /* ownCloudUI.framework in Frameworks */, @@ -1583,7 +1555,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DC8EB26C23927FDD009148F9 /* openssl.framework in Frameworks */, DC774E6822F44F6A000B11A1 /* libzip.framework in Frameworks */, DCC0857B2293F29F008CC05C /* ownCloudSDK.framework in Frameworks */, ); @@ -1613,7 +1584,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DC36885E24DD917400333600 /* openssl.framework in Frameworks */, DC36885D24DD916800333600 /* ownCloudApp.framework in Frameworks */, DC36885F24DD917900333600 /* ownCloudUI.framework in Frameworks */, DCE4E48024C1F58D0051722F /* ownCloudSDK.framework in Frameworks */, @@ -1648,7 +1618,6 @@ isa = PBXGroup; children = ( DC9BFBB220A19AF3007064B5 /* doc */, - DCD344AF205BD0FA00189B9A /* openssl.xcodeproj */, 233BDEBF204FEFF300C06732 /* ownCloudSDK.xcodeproj */, 233BDE9E204FEFE500C06732 /* ownCloud */, DC7DBA10207F59C500E7337D /* External */, @@ -1734,14 +1703,6 @@ path = ownCloudTests; sourceTree = ""; }; - 2347445A2076138000859C93 /* Products */ = { - isa = PBXGroup; - children = ( - 2347445E2076138000859C93 /* openssl.framework */, - ); - name = Products; - sourceTree = ""; - }; 2366821521144DCD0045EF72 /* Card Presentation Controller */ = { isa = PBXGroup; children = ( @@ -3256,7 +3217,6 @@ ); dependencies = ( DC63207D21FCA71B007EC0A8 /* PBXTargetDependency */, - DC3BE0CD2077BC52002A0AC0 /* PBXTargetDependency */, DC3BE0CF2077BC52002A0AC0 /* PBXTargetDependency */, DC3BE0D12077BC52002A0AC0 /* PBXTargetDependency */, DCC6566420C9B7E400110A97 /* PBXTargetDependency */, @@ -3270,6 +3230,7 @@ name = ownCloud; packageProductDependencies = ( 024F3A2024A3AB410083E11E /* CrashReporter */, + 3924AB5727FC6A270055DC8A /* OpenSSL */, ); productName = ownCloud; productReference = 233BDE9C204FEFE500C06732 /* ownCloud.app */; @@ -3415,7 +3376,6 @@ buildRules = ( ); dependencies = ( - DC8EB26E23927FE7009148F9 /* PBXTargetDependency */, DC774E6722F44F65000B11A1 /* PBXTargetDependency */, DCC0857A2293F296008CC05C /* PBXTargetDependency */, ); @@ -3593,6 +3553,7 @@ packageReferences = ( 024F3A1F24A3AB410083E11E /* XCRemoteSwiftPackageReference "plcrashreporter" */, DC049197258CAF8200DEDC27 /* XCRemoteSwiftPackageReference "PocketSVG" */, + 3924AB5627FC6A270055DC8A /* XCRemoteSwiftPackageReference "OpenSSL" */, ); productRefGroup = 233BDE9D204FEFE500C06732 /* Products */; projectDirPath = ""; @@ -3601,10 +3562,6 @@ ProductGroup = DCE93FEF21FCA434000E14F2 /* Products */; ProjectRef = DCE93FEE21FCA434000E14F2 /* libzip.xcodeproj */; }, - { - ProductGroup = 2347445A2076138000859C93 /* Products */; - ProjectRef = DCD344AF205BD0FA00189B9A /* openssl.xcodeproj */; - }, { ProductGroup = 239369712076110900BCE21A /* Products */; ProjectRef = 233BDEBF204FEFF300C06732 /* ownCloudSDK.xcodeproj */; @@ -3628,13 +3585,6 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ - 2347445E2076138000859C93 /* openssl.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = openssl.framework; - remoteRef = 2347445D2076138000859C93 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; 239369782076110900BCE21A /* ownCloudSDK.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; @@ -4484,11 +4434,6 @@ name = ownCloudSDK; targetProxy = DC27A19220CAA0C6008ACB6C /* PBXContainerItemProxy */; }; - DC3BE0CD2077BC52002A0AC0 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = openssl; - targetProxy = DC3BE0CC2077BC52002A0AC0 /* PBXContainerItemProxy */; - }; DC3BE0CF2077BC52002A0AC0 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = ownCloudSDK; @@ -4509,11 +4454,6 @@ name = libzip; targetProxy = DC774E6622F44F65000B11A1 /* PBXContainerItemProxy */; }; - DC8EB26E23927FE7009148F9 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = openssl; - targetProxy = DC8EB26D23927FE7009148F9 /* PBXContainerItemProxy */; - }; DCB2C059250C1C3F001083CA /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = DCC0855B2293F1FD008CC05C /* ownCloudApp */; @@ -5606,6 +5546,14 @@ minimumVersion = 1.7.0; }; }; + 3924AB5627FC6A270055DC8A /* XCRemoteSwiftPackageReference "OpenSSL" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/krzyzanowskim/OpenSSL.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.1.180; + }; + }; DC049197258CAF8200DEDC27 /* XCRemoteSwiftPackageReference "PocketSVG" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/pocketsvg/PocketSVG.git"; @@ -5627,6 +5575,11 @@ package = 024F3A1F24A3AB410083E11E /* XCRemoteSwiftPackageReference "plcrashreporter" */; productName = CrashReporter; }; + 3924AB5727FC6A270055DC8A /* OpenSSL */ = { + isa = XCSwiftPackageProductDependency; + package = 3924AB5627FC6A270055DC8A /* XCRemoteSwiftPackageReference "OpenSSL" */; + productName = OpenSSL; + }; DC0491A9258CAF9800DEDC27 /* PocketSVG */ = { isa = XCSwiftPackageProductDependency; package = DC049197258CAF8200DEDC27 /* XCRemoteSwiftPackageReference "PocketSVG" */; diff --git a/ownCloud.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ownCloud.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6b4b251f8..b14d7da78 100644 --- a/ownCloud.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ownCloud.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,25 +1,32 @@ { - "object": { - "pins": [ - { - "package": "PLCrashReporter", - "repositoryURL": "https://github.com/microsoft/plcrashreporter.git", - "state": { - "branch": null, - "revision": "4637a7854de2cc5c354d46fb931d74bdbc2c043e", - "version": "1.7.0" - } - }, - { - "package": "PocketSVG", - "repositoryURL": "https://github.com/pocketsvg/PocketSVG.git", - "state": { - "branch": null, - "revision": "51d4fd9ae48e79207034afdd53f365fc2b9d6068", - "version": "2.7.0" - } + "pins" : [ + { + "identity" : "openssl", + "kind" : "remoteSourceControl", + "location" : "https://github.com/krzyzanowskim/OpenSSL.git", + "state" : { + "revision" : "8697a05bcddbbeb5ac2d29cd60442cf93ee0d3ae", + "version" : "1.1.1400" } - ] - }, - "version": 1 + }, + { + "identity" : "plcrashreporter", + "kind" : "remoteSourceControl", + "location" : "https://github.com/microsoft/plcrashreporter.git", + "state" : { + "revision" : "4637a7854de2cc5c354d46fb931d74bdbc2c043e", + "version" : "1.7.0" + } + }, + { + "identity" : "pocketsvg", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pocketsvg/PocketSVG.git", + "state" : { + "revision" : "51d4fd9ae48e79207034afdd53f365fc2b9d6068", + "version" : "2.7.0" + } + } + ], + "version" : 2 } From d6ff1d74abbb64fb504fdd6cbdcc6ab35d0fc210 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Wed, 13 Apr 2022 21:38:26 +0200 Subject: [PATCH 018/328] - update SDK for improved error handling - add environment variable switch to enable/disable action-timeout-simulator host simulator --- ios-sdk | 2 +- ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ios-sdk b/ios-sdk index 95b7c5fc2..a96bf760c 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit 95b7c5fc2b54c1077f08637590f19964179efb19 +Subproject commit a96bf760cb3ff9d825d74f944648f2961e33d5ac diff --git a/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme b/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme index b6809e148..e0dd390a7 100644 --- a/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme +++ b/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme @@ -310,6 +310,11 @@ value = "[auth-race-condition]" isEnabled = "NO"> + + Date: Wed, 13 Apr 2022 22:28:38 +0200 Subject: [PATCH 019/328] - CollectionView: make progress on modular structure and implementation, adding CollectionViewCellConfiguration to pass additional parameters alongside simple objects - update SDK - update project to use latest OpenSSL version, following merge of SDK with ios-sdk/develop --- ios-sdk | 2 +- ownCloud.xcodeproj/project.pbxproj | 101 +++------- .../Client/ClientRootViewController.swift | 67 ++++++- .../CollectionViewCellConfiguration.swift | 44 ++++ ...CellProvider+StandardImplementations.swift | 29 ++- .../CollectionViewCellProvider.swift | 12 +- .../File Lists/CollectionViewController.swift | 87 ++++++-- .../File Lists/CollectionViewSection.swift | 37 ++-- .../_DataSourceCollectionViewController.swift | 189 ------------------ 9 files changed, 263 insertions(+), 305 deletions(-) create mode 100644 ownCloudAppShared/Client/File Lists/CollectionViewCellConfiguration.swift delete mode 100644 ownCloudAppShared/Client/File Lists/_DataSourceCollectionViewController.swift diff --git a/ios-sdk b/ios-sdk index d77c12252..43dd85c87 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit d77c12252601bf37f10be376498caa09af4f9f47 +Subproject commit 43dd85c87873b0044daa0833e4979377671450ba diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index ad1854ef8..553e103c9 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -104,7 +104,6 @@ 39BF674C25E7FE020039663F /* CancelLabelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39BF674B25E7FE020039663F /* CancelLabelViewController.swift */; }; 39BF67B225E804DF0039663F /* ownCloudUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2393697C2076110900BCE21A /* ownCloudUI.framework */; }; 39BF67B325E804EF0039663F /* ownCloudSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 239369782076110900BCE21A /* ownCloudSDK.framework */; }; - 39BF67B425E804FB0039663F /* openssl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2347445E2076138000859C93 /* openssl.framework */; }; 39CC8AE6228C12100020253B /* Array+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CC8AE5228C12100020253B /* Array+Extension.swift */; }; 39CD755423D8392D00193950 /* EditDocumentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CD755323D8392D00193950 /* EditDocumentViewController.swift */; }; 39D06BEC229BE8D8000D7FC9 /* SettingsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39D06BEB229BE8D8000D7FC9 /* SettingsSection.swift */; }; @@ -303,12 +302,9 @@ DC36885824DC98BF00333600 /* OCFileProviderServiceSession.h in Headers */ = {isa = PBXBuildFile; fileRef = DC36885624DC98BF00333600 /* OCFileProviderServiceSession.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC36885924DC98BF00333600 /* OCFileProviderServiceSession.m in Sources */ = {isa = PBXBuildFile; fileRef = DC36885724DC98BF00333600 /* OCFileProviderServiceSession.m */; }; DC36885D24DD916800333600 /* ownCloudApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCC0855C2293F1FD008CC05C /* ownCloudApp.framework */; }; - DC36885E24DD917400333600 /* openssl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2347445E2076138000859C93 /* openssl.framework */; }; DC36885F24DD917900333600 /* ownCloudUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2393697C2076110900BCE21A /* ownCloudUI.framework */; }; DC36886224DDA9AB00333600 /* ProgressIndicatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC36886024DDA3C300333600 /* ProgressIndicatorViewController.swift */; }; - DC3BE0D72077BC5D002A0AC0 /* openssl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2347445E2076138000859C93 /* openssl.framework */; }; DC3BE0D82077BC5D002A0AC0 /* ownCloudSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 239369782076110900BCE21A /* ownCloudSDK.framework */; }; - DC3BE0D92077BC6B002A0AC0 /* openssl.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 2347445E2076138000859C93 /* openssl.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DC3BE0DA2077BC6B002A0AC0 /* ownCloudSDK.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 239369782076110900BCE21A /* ownCloudSDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DC3BE0DF2077CC14002A0AC0 /* ClientRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3BE0DD2077CC13002A0AC0 /* ClientRootViewController.swift */; }; DC3DEC7B22AFA1F000F3352D /* DownloadItemsHUDViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3DEC7A22AFA1F000F3352D /* DownloadItemsHUDViewController.swift */; }; @@ -361,7 +357,6 @@ DC85572C20513B8C00189B9A /* ServerListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC85572A20513B8C00189B9A /* ServerListTableViewController.swift */; }; DC85572D20513B8C00189B9A /* ServerListTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = DC85572B20513B8C00189B9A /* ServerListTableViewController.xib */; }; DC869A592153B1F60088977E /* OCMockingManager+SwiftTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC869A582153B1F60088977E /* OCMockingManager+SwiftTools.swift */; }; - DC8EB26C23927FDD009148F9 /* openssl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2347445E2076138000859C93 /* openssl.framework */; }; DC8EB271239308E5009148F9 /* LicenseOffersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC8EB270239308E5009148F9 /* LicenseOffersViewController.swift */; }; DC973BBE24A28ED0001DEEC4 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCEC3DE3242F665D0076B43C /* CoreServices.framework */; }; DC98BBCB20FF815C00F4ED3E /* NSNumber+OCSyncAnchorData.m in Sources */ = {isa = PBXBuildFile; fileRef = DC98BBCA20FF815C00F4ED3E /* NSNumber+OCSyncAnchorData.m */; }; @@ -440,7 +435,6 @@ DCE20272249AB50E0015A22A /* OCMessage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE20271249AB50E0015A22A /* OCMessage+Extension.swift */; }; DCE28F602433683700879DEC /* ClientSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE28F5F2433683700879DEC /* ClientSessionManager.swift */; }; DCE2F03E27FADF2600E9E136 /* UICollectionViewDiffableDataSource+Tools.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE2F03D27FADF2600E9E136 /* UICollectionViewDiffableDataSource+Tools.swift */; }; - DCE2F04027FB857E00E9E136 /* _DataSourceCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE2F03F27FB857E00E9E136 /* _DataSourceCollectionViewController.swift */; }; DCE442CE2387452000940A6D /* LicensingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC0856B2293F1FD008CC05C /* LicensingTests.m */; }; DCE4E43124C197450051722F /* OpenItemUserActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 394E200B233E477F009D2897 /* OpenItemUserActivity.swift */; }; DCE4E43524C1999A0051722F /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E37F48A2188B27D00CF16CA /* Action.swift */; }; @@ -500,6 +494,8 @@ DCE4E4A324C1FB750051722F /* icon-search.tvg in Resources */ = {isa = PBXBuildFile; fileRef = 239F437D20D0EE6300B1276D /* icon-search.tvg */; }; DCE4E4C724C255E00051722F /* AppExtensionNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE4E4C624C255E00051722F /* AppExtensionNavigationController.swift */; }; DCE684F6241BD4E800799C30 /* Branding.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3931206A2326451900E8DFBA /* Branding.plist */; }; + DCEAF068280767BC00980B6D /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = DCEAF067280767BC00980B6D /* OpenSSL */; }; + DCEAF06D280767CF00980B6D /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = DCEAF06C280767CF00980B6D /* OpenSSL */; }; DCEE1C9C23A0EADD00FE8D98 /* LicenseOfferView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCEE1C9B23A0EADD00FE8D98 /* LicenseOfferView.swift */; }; DCF072D72798558B00E0B01D /* OCCircularImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF072D5279850CC00E0B01D /* OCCircularImageView.m */; }; DCF072D82798559900E0B01D /* OCCircularImageView.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF072D4279850CC00E0B01D /* OCCircularImageView.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -529,6 +525,7 @@ DCFC9ECC28002303005D9144 /* CollectionViewSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFC9ECB28002303005D9144 /* CollectionViewSection.swift */; }; DCFC9ED128002335005D9144 /* CollectionViewCellProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFC9ED028002335005D9144 /* CollectionViewCellProvider.swift */; }; DCFC9ED3280023BB005D9144 /* CollectionViewCellProvider+StandardImplementations.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFC9ED2280023BB005D9144 /* CollectionViewCellProvider+StandardImplementations.swift */; }; + DCFC9ED528002F33005D9144 /* CollectionViewCellConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFC9ED428002F33005D9144 /* CollectionViewCellConfiguration.swift */; }; DCFEF90926EFA45A001DC7A4 /* VendorServices+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFEF90526EFA45A001DC7A4 /* VendorServices+App.swift */; }; DCFEFE2A236876BD009A142F /* OCLicenseManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DCFEFE28236876BD009A142F /* OCLicenseManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCFEFE2B236876BD009A142F /* OCLicenseManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFEFE29236876BD009A142F /* OCLicenseManager.m */; }; @@ -560,13 +557,6 @@ remoteGlobalIDString = 233BDE9B204FEFE500C06732; remoteInfo = ownCloud; }; - 2347445D2076138000859C93 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = DCD344AF205BD0FA00189B9A /* openssl.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = DC30949D205749EA00189B9A; - remoteInfo = openssl; - }; 239369772076110900BCE21A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 233BDEBF204FEFF300C06732 /* ownCloudSDK.xcodeproj */; @@ -707,13 +697,6 @@ remoteGlobalIDString = DCC8F9AA202852A200EB6701; remoteInfo = ownCloudSDK; }; - DC3BE0CC2077BC52002A0AC0 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = DCD344AF205BD0FA00189B9A /* openssl.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = DC30949C205749EA00189B9A; - remoteInfo = openssl; - }; DC3BE0CE2077BC52002A0AC0 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 233BDEBF204FEFF300C06732 /* ownCloudSDK.xcodeproj */; @@ -742,13 +725,6 @@ remoteGlobalIDString = DCE93FE221FCA42C000E14F2; remoteInfo = libzip; }; - DC8EB26D23927FE7009148F9 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = DCD344AF205BD0FA00189B9A /* openssl.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = DC30949C205749EA00189B9A; - remoteInfo = openssl; - }; DCB2C058250C1C3F001083CA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 233BDE94204FEFE500C06732 /* Project object */; @@ -896,7 +872,6 @@ 394A0B0122EEFC2C00603813 /* ownCloudAppShared.framework in Copy Frameworks */, DCC085722293F1FD008CC05C /* ownCloudApp.framework in Copy Frameworks */, DCFBAD0C21BE67A100943F76 /* ownCloudUI.framework in Copy Frameworks */, - DC3BE0D92077BC6B002A0AC0 /* openssl.framework in Copy Frameworks */, DC3BE0DA2077BC6B002A0AC0 /* ownCloudSDK.framework in Copy Frameworks */, ); name = "Copy Frameworks"; @@ -1425,8 +1400,6 @@ DCD1300923A191C000255779 /* LicenseOfferButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicenseOfferButton.swift; sourceTree = ""; }; DCD1301023A23F4E00255779 /* OCLicenseManager+AppStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCLicenseManager+AppStore.swift"; sourceTree = ""; }; DCD2D40522F06ECA0071FB8F /* DataSettingsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataSettingsSection.swift; sourceTree = ""; }; - DCD344A5205BD0C000189B9A /* openssl.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = openssl.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - DCD344AF205BD0FA00189B9A /* openssl.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = openssl.xcodeproj; path = "ios-sdk/ownCloudUI/openssl/framework/openssl.xcodeproj"; sourceTree = ""; }; DCD71E7C2742745D001592C6 /* BuildOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BuildOptions.h; sourceTree = ""; }; DCD71E7D2742745D001592C6 /* BuildOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BuildOptions.m; sourceTree = ""; }; DCD810922398492C003B0053 /* OCLicenseDuration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCLicenseDuration.h; sourceTree = ""; }; @@ -1450,7 +1423,6 @@ DCE20271249AB50E0015A22A /* OCMessage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCMessage+Extension.swift"; sourceTree = ""; }; DCE28F5F2433683700879DEC /* ClientSessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientSessionManager.swift; sourceTree = ""; }; DCE2F03D27FADF2600E9E136 /* UICollectionViewDiffableDataSource+Tools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionViewDiffableDataSource+Tools.swift"; sourceTree = ""; }; - DCE2F03F27FB857E00E9E136 /* _DataSourceCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _DataSourceCollectionViewController.swift; sourceTree = ""; }; DCE4E43D24C19C3E0051722F /* Action+UserInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Action+UserInterface.swift"; sourceTree = ""; }; DCE4E44324C1A3E30051722F /* FileListTableViewController+OpenItemTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileListTableViewController+OpenItemTableViewController.swift"; sourceTree = ""; }; DCE4E44C24C1D48B0051722F /* QueryFileListTableViewController+Multiselect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueryFileListTableViewController+Multiselect.swift"; sourceTree = ""; }; @@ -1516,6 +1488,7 @@ DCFC9ECB28002303005D9144 /* CollectionViewSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewSection.swift; sourceTree = ""; }; DCFC9ED028002335005D9144 /* CollectionViewCellProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewCellProvider.swift; sourceTree = ""; }; DCFC9ED2280023BB005D9144 /* CollectionViewCellProvider+StandardImplementations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CollectionViewCellProvider+StandardImplementations.swift"; sourceTree = ""; }; + DCFC9ED428002F33005D9144 /* CollectionViewCellConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewCellConfiguration.swift; sourceTree = ""; }; DCFED971208095E200A2D984 /* ClientItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientItemCell.swift; sourceTree = ""; }; DCFED9B920809B8900A2D984 /* ThemeTVGResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeTVGResource.swift; sourceTree = ""; }; DCFEF90526EFA45A001DC7A4 /* VendorServices+App.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "VendorServices+App.swift"; sourceTree = ""; }; @@ -1547,8 +1520,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + DCEAF068280767BC00980B6D /* OpenSSL in Frameworks */, 024F3A2124A3AB410083E11E /* CrashReporter in Frameworks */, - DC3BE0D72077BC5D002A0AC0 /* openssl.framework in Frameworks */, 394A0B0022EEFC2C00603813 /* ownCloudAppShared.framework in Frameworks */, DC3BE0D82077BC5D002A0AC0 /* ownCloudSDK.framework in Frameworks */, DC080CF1238C8D850044C5D2 /* StoreKit.framework in Frameworks */, @@ -1593,7 +1566,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 39BF67B425E804FB0039663F /* openssl.framework in Frameworks */, 39DC7CF425C305E80001E08C /* ownCloudAppShared.framework in Frameworks */, 39DC7CE725C305E40001E08C /* ownCloudApp.framework in Frameworks */, 39BF67B225E804DF0039663F /* ownCloudUI.framework in Frameworks */, @@ -1624,7 +1596,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DC8EB26C23927FDD009148F9 /* openssl.framework in Frameworks */, + DCEAF06D280767CF00980B6D /* OpenSSL in Frameworks */, DC774E6822F44F6A000B11A1 /* libzip.framework in Frameworks */, DCC0857B2293F29F008CC05C /* ownCloudSDK.framework in Frameworks */, ); @@ -1654,7 +1626,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DC36885E24DD917400333600 /* openssl.framework in Frameworks */, DC36885D24DD916800333600 /* ownCloudApp.framework in Frameworks */, DC36885F24DD917900333600 /* ownCloudUI.framework in Frameworks */, DCE4E48024C1F58D0051722F /* ownCloudSDK.framework in Frameworks */, @@ -1689,7 +1660,6 @@ isa = PBXGroup; children = ( DC9BFBB220A19AF3007064B5 /* doc */, - DCD344AF205BD0FA00189B9A /* openssl.xcodeproj */, 233BDEBF204FEFF300C06732 /* ownCloudSDK.xcodeproj */, 233BDE9E204FEFE500C06732 /* ownCloud */, DC7DBA10207F59C500E7337D /* External */, @@ -1775,14 +1745,6 @@ path = ownCloudTests; sourceTree = ""; }; - 2347445A2076138000859C93 /* Products */ = { - isa = PBXGroup; - children = ( - 2347445E2076138000859C93 /* openssl.framework */, - ); - name = Products; - sourceTree = ""; - }; 2366821521144DCD0045EF72 /* Card Presentation Controller */ = { isa = PBXGroup; children = ( @@ -2616,7 +2578,6 @@ DC080CF0238C8D850044C5D2 /* StoreKit.framework */, DC2565E8225F5A1900828AA5 /* UserNotifications.framework */, DC27A19020CAA0BA008ACB6C /* MobileCoreServices.framework */, - DCD344A5205BD0C000189B9A /* openssl.framework */, D0D9C062DD1E85A838608B0F /* EarlGrey.framework */, A56EA84D8AD331FFA604138B /* Pods_ownCloudTests.framework */, 42866B2892DC9EDC65D844E7 /* Pods_ownCloud_Screenshots_Tests.framework */, @@ -2943,11 +2904,11 @@ DC3BE0DC2077CC13002A0AC0 /* ClientQueryViewController.swift */, 2308F93C21467F6200CF0B91 /* ClientDirectoryPickerViewController.swift */, DC9A116A27D0338400D90BA4 /* ClientSpacesTableViewController.swift */, + DCFC9ED428002F33005D9144 /* CollectionViewCellConfiguration.swift */, DCFC9ED028002335005D9144 /* CollectionViewCellProvider.swift */, DCFC9ED2280023BB005D9144 /* CollectionViewCellProvider+StandardImplementations.swift */, DCFC9ECB28002303005D9144 /* CollectionViewSection.swift */, DC04FFC727F5B79000F22569 /* CollectionViewController.swift */, - DCE2F03F27FB857E00E9E136 /* _DataSourceCollectionViewController.swift */, ); path = "File Lists"; sourceTree = ""; @@ -3349,7 +3310,6 @@ ); dependencies = ( DC63207D21FCA71B007EC0A8 /* PBXTargetDependency */, - DC3BE0CD2077BC52002A0AC0 /* PBXTargetDependency */, DC3BE0CF2077BC52002A0AC0 /* PBXTargetDependency */, DC3BE0D12077BC52002A0AC0 /* PBXTargetDependency */, DCC6566420C9B7E400110A97 /* PBXTargetDependency */, @@ -3363,6 +3323,7 @@ name = ownCloud; packageProductDependencies = ( 024F3A2024A3AB410083E11E /* CrashReporter */, + DCEAF067280767BC00980B6D /* OpenSSL */, ); productName = ownCloud; productReference = 233BDE9C204FEFE500C06732 /* ownCloud.app */; @@ -3508,11 +3469,13 @@ buildRules = ( ); dependencies = ( - DC8EB26E23927FE7009148F9 /* PBXTargetDependency */, DC774E6722F44F65000B11A1 /* PBXTargetDependency */, DCC0857A2293F296008CC05C /* PBXTargetDependency */, ); name = ownCloudApp; + packageProductDependencies = ( + DCEAF06C280767CF00980B6D /* OpenSSL */, + ); productName = ownCloudApp; productReference = DCC0855C2293F1FD008CC05C /* ownCloudApp.framework */; productType = "com.apple.product-type.framework"; @@ -3687,6 +3650,7 @@ packageReferences = ( 024F3A1F24A3AB410083E11E /* XCRemoteSwiftPackageReference "plcrashreporter" */, DC049197258CAF8200DEDC27 /* XCRemoteSwiftPackageReference "PocketSVG" */, + DCEAF066280767BC00980B6D /* XCRemoteSwiftPackageReference "OpenSSL" */, ); productRefGroup = 233BDE9D204FEFE500C06732 /* Products */; projectDirPath = ""; @@ -3695,10 +3659,6 @@ ProductGroup = DCE93FEF21FCA434000E14F2 /* Products */; ProjectRef = DCE93FEE21FCA434000E14F2 /* libzip.xcodeproj */; }, - { - ProductGroup = 2347445A2076138000859C93 /* Products */; - ProjectRef = DCD344AF205BD0FA00189B9A /* openssl.xcodeproj */; - }, { ProductGroup = 239369712076110900BCE21A /* Products */; ProjectRef = 233BDEBF204FEFF300C06732 /* ownCloudSDK.xcodeproj */; @@ -3722,13 +3682,6 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ - 2347445E2076138000859C93 /* openssl.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = openssl.framework; - remoteRef = 2347445D2076138000859C93 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; 239369782076110900BCE21A /* ownCloudSDK.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; @@ -4293,7 +4246,6 @@ DCE4E4C724C255E00051722F /* AppExtensionNavigationController.swift in Sources */, DC9A116B27D0338400D90BA4 /* ClientSpacesTableViewController.swift in Sources */, DC0A358024C0E43C00FB58FC /* NSObject+ThemeApplication.swift in Sources */, - DCE2F04027FB857E00E9E136 /* _DataSourceCollectionViewController.swift in Sources */, DC0A358224C0E44200FB58FC /* TVGImage.swift in Sources */, DCE4E44F24C1DF130051722F /* UIViewController+Extension.swift in Sources */, DC0A357F24C0E43C00FB58FC /* ThemeStyle+DefaultStyles.swift in Sources */, @@ -4317,6 +4269,7 @@ 39E6DE86233CDF1E008DAE04 /* OCItemTracker.swift in Sources */, DC0A358624C0E44600FB58FC /* ThemeTVGResource.swift in Sources */, DC0A358524C0E44600FB58FC /* ThemeResource.swift in Sources */, + DCFC9ED528002F33005D9144 /* CollectionViewCellConfiguration.swift in Sources */, DC0A355624C0E33A00FB58FC /* Log.swift in Sources */, DC0A359624C0E61500FB58FC /* UIView+Extension.swift in Sources */, DCE4E43924C19AB20051722F /* MoreStaticTableViewController.swift in Sources */, @@ -4593,11 +4546,6 @@ name = ownCloudSDK; targetProxy = DC27A19220CAA0C6008ACB6C /* PBXContainerItemProxy */; }; - DC3BE0CD2077BC52002A0AC0 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = openssl; - targetProxy = DC3BE0CC2077BC52002A0AC0 /* PBXContainerItemProxy */; - }; DC3BE0CF2077BC52002A0AC0 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = ownCloudSDK; @@ -4618,11 +4566,6 @@ name = libzip; targetProxy = DC774E6622F44F65000B11A1 /* PBXContainerItemProxy */; }; - DC8EB26E23927FE7009148F9 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = openssl; - targetProxy = DC8EB26D23927FE7009148F9 /* PBXContainerItemProxy */; - }; DCB2C059250C1C3F001083CA /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = DCC0855B2293F1FD008CC05C /* ownCloudApp */; @@ -5715,6 +5658,14 @@ minimumVersion = 2.7.0; }; }; + DCEAF066280767BC00980B6D /* XCRemoteSwiftPackageReference "OpenSSL" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/krzyzanowskim/OpenSSL.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -5743,6 +5694,16 @@ package = DC049197258CAF8200DEDC27 /* XCRemoteSwiftPackageReference "PocketSVG" */; productName = PocketSVG; }; + DCEAF067280767BC00980B6D /* OpenSSL */ = { + isa = XCSwiftPackageProductDependency; + package = DCEAF066280767BC00980B6D /* XCRemoteSwiftPackageReference "OpenSSL" */; + productName = OpenSSL; + }; + DCEAF06C280767CF00980B6D /* OpenSSL */ = { + isa = XCSwiftPackageProductDependency; + package = DCEAF066280767BC00980B6D /* XCRemoteSwiftPackageReference "OpenSSL" */; + productName = OpenSSL; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 233BDE94204FEFE500C06732 /* Project object */; diff --git a/ownCloud/Client/ClientRootViewController.swift b/ownCloud/Client/ClientRootViewController.swift index 677d55b8c..ed38a9bf3 100644 --- a/ownCloud/Client/ClientRootViewController.swift +++ b/ownCloud/Client/ClientRootViewController.swift @@ -364,9 +364,72 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa let topLevelViewController : UIViewController? if core.useDrives { + let composedDataSource = OCDataSourceComposition(sources: [ + core.hierarchicDrivesDataSource, + core.projectDrivesDataSource + ], applyCustomizations: { (composedDataSource) in +// composedDataSource.sortComparator = OCDataSourceComposition.itemComparator(withItemRetrieval: false, fromRecordComparator: { record1, record2 in +// var presentable1 : OCDataItemPresentable? +// var presentable2 : OCDataItemPresentable? +// +// if let item = record1?.item { +// presentable1 = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable +// } +// +// if let item = record2?.item { +// presentable2 = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable +// } +// +// let title1 = presentable1?.title ?? "" +// let title2 = presentable2?.title ?? "" +// +// return title1.localizedCompare(title2) +// }) +// +// composedDataSource.filter = OCDataSourceComposition.itemFilter(recordFilter: { record in +// if let item = record?.item, +// let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable, +// let startsWithA = presentable.title?.starts(with: "A") { +// return startsWithA +// } +// +// return false +// }) + + composedDataSource.setSortComparator(OCDataSourceComposition.itemComparator(withItemRetrieval: false, fromRecordComparator: { record1, record2 in + var presentable1 : OCDataItemPresentable? + var presentable2 : OCDataItemPresentable? + + if let item = record1?.item { + presentable1 = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable + } + + if let item = record2?.item { + presentable2 = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable + } + + let title1 = presentable1?.title?.last?.lowercased() ?? "" + let title2 = presentable2?.title?.last?.lowercased() ?? "" + + return title1.localizedCompare(title2) + }), for: core.projectDrivesDataSource) + +// composedDataSource.setFilter(OCDataSourceComposition.itemFilter(withItemRetrieval: false, fromRecordFilter: { (record) in +// if let item = record?.item, +// let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable, +// let startsWithA = presentable.title?.starts(with: "A") { +// return startsWithA +// } +// +// return false +// }), for: core.projectDrivesDataSource) + }) + topLevelViewController = CollectionViewController(core: core, rootViewController: self, sections: [ - CollectionViewSection(identifier: "top", dataSource: core.hierarchicDrivesDataSource), - CollectionViewSection(identifier: "projects", dataSource: core.projectDrivesDataSource) + CollectionViewSection(identifier: "composed", dataSource: composedDataSource) + +// CollectionViewSection(identifier: "top", dataSource: core.hierarchicDrivesDataSource), +// CollectionViewSection(identifier: "projects", dataSource: core.projectDrivesDataSource) ]) } else { let query = OCQuery(for: .legacyRoot) diff --git a/ownCloudAppShared/Client/File Lists/CollectionViewCellConfiguration.swift b/ownCloudAppShared/Client/File Lists/CollectionViewCellConfiguration.swift new file mode 100644 index 000000000..b65860a18 --- /dev/null +++ b/ownCloudAppShared/Client/File Lists/CollectionViewCellConfiguration.swift @@ -0,0 +1,44 @@ +// +// CollectionViewCellConfiguration.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 08.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +import UIKit +import ownCloudSDK + +public class CollectionViewCellConfiguration: NSObject { + public weak var source: OCDataSource? + + public var collectionItemRef: CollectionViewController.ItemRef? + public var record: OCDataItemRecord? + + public weak var hostViewController: CollectionViewController? + + public init(source: OCDataSource? = nil, collectionItemRef: CollectionViewController.ItemRef? = nil, record: OCDataItemRecord? = nil, hostViewController: CollectionViewController?) { + super.init() + + self.source = source + self.collectionItemRef = collectionItemRef + self.record = record + self.hostViewController = hostViewController + } +} + +public extension NSObject { + private struct AssociatedKeys { + static var cellConfiguration = "cellConfiguration" + } + + var ocCellConfiguration : CollectionViewCellConfiguration? { + set { + objc_setAssociatedObject(self, &AssociatedKeys.cellConfiguration, newValue, .OBJC_ASSOCIATION_RETAIN) + } + + get { + return objc_getAssociatedObject(self, &AssociatedKeys.cellConfiguration) as? CollectionViewCellConfiguration + } + } +} diff --git a/ownCloudAppShared/Client/File Lists/CollectionViewCellProvider+StandardImplementations.swift b/ownCloudAppShared/Client/File Lists/CollectionViewCellProvider+StandardImplementations.swift index e1047e000..ec47107ff 100644 --- a/ownCloudAppShared/Client/File Lists/CollectionViewCellProvider+StandardImplementations.swift +++ b/ownCloudAppShared/Client/File Lists/CollectionViewCellProvider+StandardImplementations.swift @@ -22,21 +22,36 @@ import ownCloudSDK public extension CollectionViewCellProvider { static func registerStandardImplementations() { // Register cell providers for .drive and .presentable - let cellRegistration = UICollectionView.CellRegistration { (cell, indexPath, itemRef) in + let cellRegistration = UICollectionView.CellRegistration { (cell, indexPath, collectionItemRef) in var content = cell.defaultContentConfiguration() - if let cellConfiguration = itemRef.ocDataItemCellConfiguration { - if let itemRecord = try? cellConfiguration.source?.record(forItemRef: itemRef) { - if let item = itemRecord?.item { + if let cellConfiguration = collectionItemRef.ocCellConfiguration { + var itemRecord = cellConfiguration.record + + if itemRecord == nil { + if let collectionViewController = cellConfiguration.hostViewController { + let (itemRef, _) = collectionViewController.unwrap(collectionItemRef) + + if let retrievedItemRecord = try? cellConfiguration.source?.record(forItemRef: itemRef) { + itemRecord = retrievedItemRecord + } + } + } + + if let itemRecord = itemRecord { + if let item = itemRecord.item { if let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil, withOptions: nil) as? OCDataItemPresentable { content.text = presentable.title content.secondaryText = presentable.subtitle + +// presentable.requestResource(.coverImage, withOptions: [.core : core], completionHandler: { error, resource in +// }) } } else { // Request reconfiguration of cell - itemRecord?.retrieveItem(completionHandler: { error, itemRecord in - if let collectionViewController = cellConfiguration.hostViewController as? CollectionViewController { - collectionViewController.collectionViewDataSource.requestReconfigurationOfItems([itemRef]) + itemRecord.retrieveItem(completionHandler: { error, itemRecord in + if let collectionViewController = cellConfiguration.hostViewController { + collectionViewController.collectionViewDataSource.requestReconfigurationOfItems([collectionItemRef]) } }) } diff --git a/ownCloudAppShared/Client/File Lists/CollectionViewCellProvider.swift b/ownCloudAppShared/Client/File Lists/CollectionViewCellProvider.swift index 6702421d9..8388864ef 100644 --- a/ownCloudAppShared/Client/File Lists/CollectionViewCellProvider.swift +++ b/ownCloudAppShared/Client/File Lists/CollectionViewCellProvider.swift @@ -21,7 +21,7 @@ import ownCloudSDK public class CollectionViewCellProvider: NSObject { // MARK: - Types - public typealias CellProvider = (_ collectionView: UICollectionView, _ cellConfiguration: OCDataItemCellConfiguration?, _ itemRecord: OCDataItemRecord, _ itemRef: OCDataItemReference, _ indexPath: IndexPath) -> UICollectionViewCell + public typealias CellProvider = (_ collectionView: UICollectionView, _ cellConfiguration: CollectionViewCellConfiguration?, _ itemRecord: OCDataItemRecord, _ collectionItemRef: CollectionViewController.ItemRef, _ indexPath: IndexPath) -> UICollectionViewCell // MARK: - Global registry static var cellProviders : [OCDataItemType:CollectionViewCellProvider] = [:] @@ -43,18 +43,18 @@ public class CollectionViewCellProvider: NSObject { var provider : CellProvider var dataItemType : OCDataItemType - public func provideCell(for collectionView: UICollectionView, cellConfiguration: OCDataItemCellConfiguration?, itemRecord: OCDataItemRecord, itemRef: OCDataItemReference, indexPath: IndexPath) -> UICollectionViewCell { + public func provideCell(for collectionView: UICollectionView, cellConfiguration: CollectionViewCellConfiguration?, itemRecord: OCDataItemRecord, collectionItemRef: CollectionViewController.ItemRef, indexPath: IndexPath) -> UICollectionViewCell { // Save any existing cell configuration - let previousCellConfiguration = itemRef.ocDataItemCellConfiguration + let previousCellConfiguration = collectionItemRef.ocCellConfiguration // Set cell configuration - itemRef.ocDataItemCellConfiguration = cellConfiguration + collectionItemRef.ocCellConfiguration = cellConfiguration // Ask provider to provide cell - let cell = provider(collectionView, cellConfiguration, itemRecord, itemRef, indexPath) + let cell = provider(collectionView, cellConfiguration, itemRecord, collectionItemRef, indexPath) // Restore previously existing cell configuration - itemRef.ocDataItemCellConfiguration = previousCellConfiguration + collectionItemRef.ocCellConfiguration = previousCellConfiguration return cell } diff --git a/ownCloudAppShared/Client/File Lists/CollectionViewController.swift b/ownCloudAppShared/Client/File Lists/CollectionViewController.swift index f07499705..40ef13444 100644 --- a/ownCloudAppShared/Client/File Lists/CollectionViewController.swift +++ b/ownCloudAppShared/Client/File Lists/CollectionViewController.swift @@ -21,16 +21,22 @@ import ownCloudSDK public class CollectionViewController: UIViewController, UICollectionViewDelegate { - public weak var core : OCCore? + public weak var core: OCCore? public weak var rootViewController: UIViewController? - public init(core inCore: OCCore, rootViewController inRootViewController: UIViewController, sections inSections: [CollectionViewSection]?) { + public var supportsHierarchicContent: Bool + + public init(core inCore: OCCore?, rootViewController inRootViewController: UIViewController?, sections inSections: [CollectionViewSection]?, hierarchic: Bool = false) { + supportsHierarchicContent = hierarchic + super.init(nibName: nil, bundle: nil) core = inCore rootViewController = inRootViewController - self.navigationItem.title = inCore.bookmark.shortName + if let core = inCore { + self.navigationItem.title = core.bookmark.shortName + } // Add datasources if let addSections = inSections { @@ -44,7 +50,7 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat // MARK: - Collection View var collectionView : UICollectionView! = nil - var collectionViewDataSource: UICollectionViewDiffableDataSource! = nil + var collectionViewDataSource: UICollectionViewDiffableDataSource! = nil public override func viewDidLoad() { super.viewDidLoad() @@ -66,11 +72,10 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat // MARK: - Collection View Datasource func configureDataSource() { - collectionViewDataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { [weak self] (collectionView: UICollectionView, indexPath: IndexPath, itemRef: OCDataItemReference) -> UICollectionViewCell? in - // let dataSourceSectionIndex = collectionView.dataSourceSectionIndex(forPresentationSectionIndex: indexPath.section) // not sure if needed + collectionViewDataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { [weak self] (collectionView: UICollectionView, indexPath: IndexPath, collectionItemRef: CollectionViewController.ItemRef) -> UICollectionViewCell? in if let sectionIdentifier = self?.collectionViewDataSource.sectionIdentifier(for: indexPath.section), let section = self?.sectionsByID[sectionIdentifier] { - return section.provideReusableCell(for: collectionView, itemRef: itemRef, indexPath: indexPath) + return section.provideReusableCell(for: collectionView, collectionItemRef: collectionItemRef, indexPath: indexPath) } return UICollectionViewCell() @@ -84,7 +89,7 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat var sectionsByID : [CollectionViewSection.SectionIdentifier : CollectionViewSection] = [:] // MARK: - Sections - func add(sections sectionsToAdd: [CollectionViewSection]) { + public func add(sections sectionsToAdd: [CollectionViewSection]) { for section in sectionsToAdd { section.collectionViewController = self @@ -95,7 +100,7 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat updateSource() } - func remove(sections sectionsToRemove: [CollectionViewSection]) { + public func remove(sections sectionsToRemove: [CollectionViewSection]) { for section in sectionsToRemove { section.collectionViewController = nil @@ -113,26 +118,72 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat return } - var snapshot = NSDiffableDataSourceSnapshot() + var snapshot = NSDiffableDataSourceSnapshot() for section in sections { snapshot.appendSections([section.identifier]) - if let datasourceSnapshot = section.dataSourceSubscription?.snapshotResettingChangeTracking(true) { - snapshot.appendItems(datasourceSnapshot.items, toSection: section.identifier) + section.populate(snapshot: &snapshot) + } - if let updatedItems = datasourceSnapshot.updatedItems, updatedItems.count > 0 { - snapshot.reloadItems(Array(updatedItems)) - } + collectionViewDataSource.apply(snapshot, animatingDifferences: animatingDifferences) + } + + // MARK: - Item references + public typealias ItemRef = NSObject + public class WrappedItem : NSObject { + var dataItemReference: OCDataItemReference + var sectionIdentifier: CollectionViewSection.SectionIdentifier + + init(reference: OCDataItemReference, forSection: CollectionViewSection.SectionIdentifier) { + dataItemReference = reference + sectionIdentifier = forSection + super.init() + } + + public override func isEqual(_ object: Any?) -> Bool { + if let otherObj = object as? WrappedItem, + dataItemReference.isEqual(otherObj.dataItemReference), + sectionIdentifier == otherObj.sectionIdentifier { + return true } + + return false } - collectionViewDataSource.apply(snapshot, animatingDifferences: animatingDifferences) + public override var hash: Int { + return dataItemReference.hash ^ sectionIdentifier.hash + } + } + + public func wrap(references: [OCDataItemReference], forSection: CollectionViewSection.SectionIdentifier) -> [ItemRef] { + if supportsHierarchicContent { + // wrap references and section ID together into a single object + var itemRefs : [ItemRef] = [] + + for reference in references { + itemRefs.append(WrappedItem(reference: reference, forSection: forSection)) + } + + return itemRefs + } + + // no hierarchic content, so can just use data source references as-is + return references + } + + public func unwrap(_ collectionItemRef: ItemRef) -> (OCDataItemReference, CollectionViewSection.SectionIdentifier?) { + if supportsHierarchicContent, let wrappedItem = collectionItemRef as? WrappedItem { + // unwrap bundled item references + section ID + return (wrappedItem.dataItemReference, wrappedItem.sectionIdentifier) + } + + return (collectionItemRef, nil) } // MARK: - Collection View Delegate public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - guard let itemRef = collectionViewDataSource.itemIdentifier(for: indexPath) else { + guard let collectionItemRef = collectionViewDataSource.itemIdentifier(for: indexPath) else { collectionView.deselectItem(at: indexPath, animated: true) return } @@ -140,6 +191,8 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat if let sectionIdentifier = collectionViewDataSource.sectionIdentifier(for: indexPath.section), let section = sectionsByID[sectionIdentifier], let dataSource = section.dataSource { + let (itemRef, _) = unwrap(collectionItemRef) + dataSource.retrieveItem(forRef: itemRef, reusing: nil, completionHandler: { [weak self] (error, record) in if let drive = record?.item as? OCDrive { if let core = self?.core, let rootViewController = self?.rootViewController { diff --git a/ownCloudAppShared/Client/File Lists/CollectionViewSection.swift b/ownCloudAppShared/Client/File Lists/CollectionViewSection.swift index de126e4bc..8ffdd889c 100644 --- a/ownCloudAppShared/Client/File Lists/CollectionViewSection.swift +++ b/ownCloudAppShared/Client/File Lists/CollectionViewSection.swift @@ -62,24 +62,35 @@ public class CollectionViewSection: NSObject { collectionViewController?.updateSource(animatingDifferences: true) } - func provideReusableCell(for collectionView: UICollectionView, itemRef: OCDataItemReference, indexPath: IndexPath) -> UICollectionViewCell { - var cell: UICollectionViewCell? - - if let itemRecord = try? dataSource?.record(forItemRef: itemRef), let itemRecord = itemRecord { - var cellProvider = CollectionViewCellProvider.providerFor(itemRecord) + func populate(snapshot: inout NSDiffableDataSourceSnapshot) { + if let datasourceSnapshot = dataSourceSubscription?.snapshotResettingChangeTracking(true) { + if let wrappedItems = collectionViewController?.wrap(references: datasourceSnapshot.items, forSection: identifier) { + snapshot.appendItems(wrappedItems, toSection: identifier) + } - if cellProvider == nil { - cellProvider = CollectionViewCellProvider.providerFor(.presentable) + if let updatedItems = datasourceSnapshot.updatedItems, updatedItems.count > 0, + let wrappedUpdatedItems = collectionViewController?.wrap(references: Array(updatedItems), forSection: identifier) { + snapshot.reloadItems(wrappedUpdatedItems) } + } + } + + func provideReusableCell(for collectionView: UICollectionView, collectionItemRef: CollectionViewController.ItemRef, indexPath: IndexPath) -> UICollectionViewCell { + var cell: UICollectionViewCell? + + if let (dataItemRef, _) = collectionViewController?.unwrap(collectionItemRef) { + if let itemRecord = try? dataSource?.record(forItemRef: dataItemRef), let itemRecord = itemRecord { + var cellProvider = CollectionViewCellProvider.providerFor(itemRecord) - if let cellProvider = cellProvider, let dataSource = dataSource { - let cellConfiguration = OCDataItemCellConfiguration(source: dataSource) + if cellProvider == nil { + cellProvider = CollectionViewCellProvider.providerFor(.presentable) + } - cellConfiguration.reference = itemRef - cellConfiguration.record = itemRecord - cellConfiguration.hostViewController = self.collectionViewController + if let cellProvider = cellProvider, let dataSource = dataSource { + let cellConfiguration = CollectionViewCellConfiguration(source: dataSource, collectionItemRef: collectionItemRef, record: itemRecord, hostViewController: collectionViewController) - cell = cellProvider.provideCell(for: collectionView, cellConfiguration: cellConfiguration, itemRecord: itemRecord, itemRef: itemRef, indexPath: indexPath) + cell = cellProvider.provideCell(for: collectionView, cellConfiguration: cellConfiguration, itemRecord: itemRecord, collectionItemRef: collectionItemRef, indexPath: indexPath) + } } } diff --git a/ownCloudAppShared/Client/File Lists/_DataSourceCollectionViewController.swift b/ownCloudAppShared/Client/File Lists/_DataSourceCollectionViewController.swift deleted file mode 100644 index 263c898c9..000000000 --- a/ownCloudAppShared/Client/File Lists/_DataSourceCollectionViewController.swift +++ /dev/null @@ -1,189 +0,0 @@ -// -// DataSourceCollectionViewController.swift -// ownCloudAppShared -// -// Created by Felix Schwarz on 04.04.22. -// Copyright © 2022 ownCloud GmbH. All rights reserved. -// - -import UIKit -import ownCloudSDK - -//private let reuseIdentifier = "Cell" -// -//extension OCDataItem { -// -//} - -//public class DataSourceSectionSnapshotProvider: NSObject { -// public var dataSource : OCDataSource -// public var subscription : OCDataSourceSubscription? -// -// private var changeCountByItemRef : NSCountedSet -// -// required init(datasource: OCDataSource) { -// self.dataSource = datasource -// -// changeCountByItemRef = NSCountedSet() -// -// super.init() -// -// subscription = dataSource.subscribe(updateHandler: { [weak self] (subscription) in -// self?.handleUpdates() -// }, on: .main, trackDifferences: true, performIntialUpdate: true) -// } -// -// deinit { -// subscription?.terminate() -// } -// -// func handleUpdates() { -// } -// -// func generateSectionSnapshot() -> NSDiffableDataSourceSectionSnapshot { -// if let subscription = subscription { -// let dataSnapshot = subscription.snapshotResettingChangeTracking(true) -// -// var items = dataSnapshot.items -// -// if let updatedItems = dataSnapshot.updatedItems { -// changeCountByItemRef.addingObjects(from: updatedItems) -// } -// } -// -// var sectionSnapshot = NSDiffableDataSourceSectionSnapshot() -// -// sectionSnapshot.append(dataSnapshot.items) -// sectionSnapshot. -// -// reloadItems(Array(dataSnapshot.updatedItems)) -// } -//} -// -//public class DataSourceCollectionViewController: UIViewController, UICollectionViewDelegate { -// public weak var core : OCCore? -// public weak var rootViewController: UIViewController? -// -// public var driveListSubscription : OCDataSourceSubscription? -// -// public init(core inCore: OCCore, rootViewController inRootViewController: UIViewController) { -// super.init(nibName: nil, bundle: nil) -// -// core = inCore -// rootViewController = inRootViewController -// -// self.navigationItem.title = inCore.bookmark.shortName -// -// driveListSubscription = core?.drivesDataSource.subscribe(updateHandler: { [weak self] (subscription) in -// self?.handleListUpdates(from: subscription) -// }, on: .main, trackDifferences: true, performIntialUpdate: true) -// } -// -// required init?(coder: NSCoder) { -// fatalError("init(coder:) has not been implemented") -// } -// -// deinit { -// driveListSubscription?.terminate() -// } -// -// func handleListUpdates(from subscription: OCDataSourceSubscription) { -// updateFromSubscription(animatingDifferences: true) -// } -// -// /// Collection View implementation -// enum Section: CaseIterable { -// case spaces -// } -// -// var collectionView : UICollectionView! = nil -// var dataSource: UICollectionViewDiffableDataSource! = nil -// -// public override func viewDidLoad() { -// super.viewDidLoad() -// configureHierarchy() -// configureDataSource() -// } -// -// func createLayout() -> UICollectionViewLayout { -// let config = UICollectionLayoutListConfiguration(appearance: .plain) -// return UICollectionViewCompositionalLayout.list(using: config) -// } -// -// func configureHierarchy() { -// collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createLayout()) -// collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] -// view.addSubview(collectionView) -// collectionView.delegate = self -// } -// -// func configureDataSource() { -//// let cellRegistration = UICollectionView.CellRegistration { (cell, indexPath, item) in -//// cell.updateWithItem(item) -//// cell.accessories = [.disclosureIndicator()] -//// } -// -// let cellRegistration = UICollectionView.CellRegistration { [weak self] (cell, indexPath, itemRef) in -// var content = cell.defaultContentConfiguration() -// -// if let itemRecord = try? self?.driveListSubscription?.source?.record(forItemRef: itemRef) { -// if let item = itemRecord?.item { -// if let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil, withOptions: nil) as? OCDataItemPresentable { -// content.text = presentable.title -// content.secondaryText = presentable.subtitle -// } -// } else { -// // Request reconfiguration of cell -// itemRecord?.retrieveItem(completionHandler: { error, itemRecord in -// self?.dataSource.requestReconfigurationOfItems([itemRef]) -// }) -// } -// } -// -// cell.contentConfiguration = content -// cell.accessories = [ .disclosureIndicator() ] -// } -// -// dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { (collectionView: UICollectionView, indexPath: IndexPath, itemRef: NSObject) -> UICollectionViewCell? in -// return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemRef) -// } -// -// // initial data -// updateFromSubscription(animatingDifferences: false) -// } -// -// func updateFromSubscription(animatingDifferences: Bool = true) { -// if let datasourceSnapshot = driveListSubscription?.snapshotResettingChangeTracking(true) { -// var snapshot = NSDiffableDataSourceSnapshot() -// snapshot.appendSections([.spaces]) -// -// snapshot.appendItems(datasourceSnapshot.items) -// -// if let updatedItems = datasourceSnapshot.updatedItems, updatedItems.count > 0 { -// snapshot.reloadItems(Array(updatedItems)) -// } -// -// dataSource.apply(snapshot, animatingDifferences: animatingDifferences) -// } -// } -// -// public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { -// guard let itemRef = self.dataSource.itemIdentifier(for: indexPath) else { -// collectionView.deselectItem(at: indexPath, animated: true) -// return -// } -// -// driveListSubscription?.source?.retrieveItem(forRef: itemRef, reusing: nil, completionHandler: { [weak self] (error, record) in -// if let drive = record?.item as? OCDrive { -// if let core = self?.core, let rootViewController = self?.rootViewController { -// let query = OCQuery(for: drive.rootLocation) -// let rootFolderViewController = ClientQueryViewController(core: core, drive: drive, query: query, rootViewController: rootViewController) -// -// collectionView.deselectItem(at: indexPath, animated: true) -// -// self?.navigationController?.pushViewController(rootFolderViewController, animated: true) -// } -// } -// }) -// } -//} From 491facd98cb60b8760f6f05238705ea4f07c8700 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Thu, 21 Apr 2022 12:19:02 +0200 Subject: [PATCH 020/328] - SDK update for new functionality - OCImage+ViewProvider: add support for content mode context attributes - new collection view cell types - DriveListCell: list cell representing drives - DriveHeaderCell: subclass of DriveListCell with different layout, for representing drives as headers (with large image and text) - ExpandableResourceCell: shows resources at full width, allowing expansion/collapse if the resource's height exceeds a certain threshold - ItemListCell: representing an OCItem - CollectionViewCellConfiguration: - add .core property - add CollectionViewCellStyle type and property to pass styling information to cells - CollectionViewCellProvider+StandardImplementations: move cell provider registration code into specific cell classes - CollectionViewController: allow control over list appearance - CollectionViewSection: add support for section-wide and per-cell CollectionViewCellStyle configuration - ClientItemViewController: new view controller to form basis of new collection view based file list UI - refactor NSMutableAttributedString styling code from ClientItemCell.swift into NSMutableAttributedString+AppendStyled.swift - UIFont+Weight: extension providing preferred system fonts at specific weights - UILabel+Extension: factor out code to make labels wrap text into multiple lines (makeLabelWrapText) - OCResourceText+ViewProvider: add view provider for OCResourceText, supporting markdown (via Down), otherwise falling back to plain text - add Down Markdown library LICENSE into ownCloudAppShared and registration as license extension --- ios-sdk | 2 +- ownCloud.xcodeproj/project.pbxproj | 115 ++- .../Client/ClientRootViewController.swift | 40 +- .../View Providers/OCImage+ViewProvider.m | 13 +- .../Cells/DriveHeaderCell.swift | 83 +++ .../Cells/DriveListCell.swift | 239 +++++++ .../Cells/ExpandableResourceCell.swift | 163 +++++ .../Collection Views/Cells/ItemListCell.swift | 669 ++++++++++++++++++ .../CollectionViewCellConfiguration.swift | 24 +- ...CellProvider+StandardImplementations.swift | 105 +++ .../CollectionViewCellProvider.swift | 0 .../CollectionViewController.swift | 58 +- .../CollectionViewSection.swift | 14 +- ...CellProvider+StandardImplementations.swift | 73 -- .../ResourceSourceItemIcons.swift | 2 +- .../User Interface/ClientItemCell.swift | 23 - .../ClientItemViewController.swift | 167 +++++ ownCloudAppShared/Down.LICENSE | 218 ++++++ ...MutableAttributedString+AppendStyled.swift | 42 ++ ...llectionViewDiffableDataSource+Tools.swift | 2 +- .../UIKit Extension/UIFont+Weight.swift | 29 + .../UIKit Extension/UILabel+Extension.swift | 28 + .../User Interface/Theme/Theme.swift | 1 + .../Theme/UI/ThemeTableViewCell.swift | 10 +- .../OCResourceText+ViewProvider.swift | 62 ++ 25 files changed, 2032 insertions(+), 150 deletions(-) create mode 100644 ownCloudAppShared/Client/Collection Views/Cells/DriveHeaderCell.swift create mode 100644 ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift create mode 100644 ownCloudAppShared/Client/Collection Views/Cells/ExpandableResourceCell.swift create mode 100644 ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift rename ownCloudAppShared/Client/{File Lists => Collection Views}/CollectionViewCellConfiguration.swift (56%) create mode 100644 ownCloudAppShared/Client/Collection Views/CollectionViewCellProvider+StandardImplementations.swift rename ownCloudAppShared/Client/{File Lists => Collection Views}/CollectionViewCellProvider.swift (100%) rename ownCloudAppShared/Client/{File Lists => Collection Views}/CollectionViewController.swift (76%) rename ownCloudAppShared/Client/{File Lists => Collection Views}/CollectionViewSection.swift (78%) delete mode 100644 ownCloudAppShared/Client/File Lists/CollectionViewCellProvider+StandardImplementations.swift create mode 100644 ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift create mode 100644 ownCloudAppShared/Down.LICENSE create mode 100644 ownCloudAppShared/UIKit Extension/NSMutableAttributedString+AppendStyled.swift create mode 100644 ownCloudAppShared/UIKit Extension/UIFont+Weight.swift create mode 100644 ownCloudAppShared/UIKit Extension/UILabel+Extension.swift create mode 100644 ownCloudAppShared/View Providers/OCResourceText+ViewProvider.swift diff --git a/ios-sdk b/ios-sdk index 43dd85c87..8dfb50e35 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit 43dd85c87873b0044daa0833e4979377671450ba +Subproject commit 8dfb50e3565c4421129b4623ce4b26e7623d6b11 diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 1b1212230..63771aae0 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -304,6 +304,14 @@ DC36885D24DD916800333600 /* ownCloudApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCC0855C2293F1FD008CC05C /* ownCloudApp.framework */; }; DC36885F24DD917900333600 /* ownCloudUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2393697C2076110900BCE21A /* ownCloudUI.framework */; }; DC36886224DDA9AB00333600 /* ProgressIndicatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC36886024DDA3C300333600 /* ProgressIndicatorViewController.swift */; }; + DC3AB1982808C35300789435 /* ClientItemViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3AB1972808C35300789435 /* ClientItemViewController.swift */; }; + DC3AB23E280FFE3400789435 /* ItemListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3AB23D280FFE3400789435 /* ItemListCell.swift */; }; + DC3AB240280FFF2700789435 /* NSMutableAttributedString+AppendStyled.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3AB23F280FFF2700789435 /* NSMutableAttributedString+AppendStyled.swift */; }; + DC3AB2422810404000789435 /* DriveHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3AB2412810404000789435 /* DriveHeaderCell.swift */; }; + DC3AB24428104AA500789435 /* UIFont+Weight.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3AB24328104AA500789435 /* UIFont+Weight.swift */; }; + DC3AB2462810602500789435 /* UILabel+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3AB2452810602500789435 /* UILabel+Extension.swift */; }; + DC3AB2482810A10300789435 /* ExpandableResourceCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3AB2472810A10300789435 /* ExpandableResourceCell.swift */; }; + DC3AB24B2810A69600789435 /* OCResourceText+ViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3AB24A2810A69600789435 /* OCResourceText+ViewProvider.swift */; }; DC3BE0D82077BC5D002A0AC0 /* ownCloudSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 239369782076110900BCE21A /* ownCloudSDK.framework */; }; DC3BE0DA2077BC6B002A0AC0 /* ownCloudSDK.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 239369782076110900BCE21A /* ownCloudSDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DC3BE0DF2077CC14002A0AC0 /* ClientRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3BE0DD2077CC13002A0AC0 /* ClientRootViewController.swift */; }; @@ -417,6 +425,8 @@ DCD71E8027427463001592C6 /* BuildOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = DCD71E7D2742745D001592C6 /* BuildOptions.m */; }; DCD8109A23984AF2003B0053 /* OCLicenseDuration.h in Headers */ = {isa = PBXBuildFile; fileRef = DCD810922398492C003B0053 /* OCLicenseDuration.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCD8109B23984AF6003B0053 /* OCLicenseDuration.m in Sources */ = {isa = PBXBuildFile; fileRef = DCD810932398492C003B0053 /* OCLicenseDuration.m */; }; + DCD863FB28115C8700CA6631 /* Down.LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = DCD863FA28115C8700CA6631 /* Down.LICENSE */; }; + DCD8640628115FD600CA6631 /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = DCD8640528115FD600CA6631 /* OpenSSL */; }; DCD954DF247D62FA00E184E6 /* MessageTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCD954DE247D62FA00E184E6 /* MessageTableViewController.swift */; }; DCD9B87B2379612B00691929 /* OCLicenseManager+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = DCD9B873237960E600691929 /* OCLicenseManager+Internal.h */; }; DCDBB60A2525305600FAD707 /* NotificationAuthErrorForwarder.h in Headers */ = {isa = PBXBuildFile; fileRef = DCDBB60225252FDA00FAD707 /* NotificationAuthErrorForwarder.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -495,6 +505,9 @@ DCE4E4C724C255E00051722F /* AppExtensionNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE4E4C624C255E00051722F /* AppExtensionNavigationController.swift */; }; DCE684F6241BD4E800799C30 /* Branding.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3931206A2326451900E8DFBA /* Branding.plist */; }; DCEAF06D280767CF00980B6D /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = DCEAF06C280767CF00980B6D /* OpenSSL */; }; + DCEAF08A2808254800980B6D /* DriveListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCEAF0892808254800980B6D /* DriveListCell.swift */; }; + DCEAF08D28084B3800980B6D /* Down in Frameworks */ = {isa = PBXBuildFile; productRef = DCEAF08C28084B3800980B6D /* Down */; }; + DCEAF08F28084B5600980B6D /* Down in Frameworks */ = {isa = PBXBuildFile; productRef = DCEAF08E28084B5600980B6D /* Down */; }; DCEE1C9C23A0EADD00FE8D98 /* LicenseOfferView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCEE1C9B23A0EADD00FE8D98 /* LicenseOfferView.swift */; }; DCF072D72798558B00E0B01D /* OCCircularImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF072D5279850CC00E0B01D /* OCCircularImageView.m */; }; DCF072D82798559900E0B01D /* OCCircularImageView.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF072D4279850CC00E0B01D /* OCCircularImageView.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1256,6 +1269,14 @@ DC36885624DC98BF00333600 /* OCFileProviderServiceSession.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCFileProviderServiceSession.h; sourceTree = ""; }; DC36885724DC98BF00333600 /* OCFileProviderServiceSession.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCFileProviderServiceSession.m; sourceTree = ""; }; DC36886024DDA3C300333600 /* ProgressIndicatorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressIndicatorViewController.swift; sourceTree = ""; }; + DC3AB1972808C35300789435 /* ClientItemViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientItemViewController.swift; sourceTree = ""; }; + DC3AB23D280FFE3400789435 /* ItemListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListCell.swift; sourceTree = ""; }; + DC3AB23F280FFF2700789435 /* NSMutableAttributedString+AppendStyled.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSMutableAttributedString+AppendStyled.swift"; sourceTree = ""; }; + DC3AB2412810404000789435 /* DriveHeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DriveHeaderCell.swift; sourceTree = ""; }; + DC3AB24328104AA500789435 /* UIFont+Weight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Weight.swift"; sourceTree = ""; }; + DC3AB2452810602500789435 /* UILabel+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Extension.swift"; sourceTree = ""; }; + DC3AB2472810A10300789435 /* ExpandableResourceCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandableResourceCell.swift; sourceTree = ""; }; + DC3AB24A2810A69600789435 /* OCResourceText+ViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCResourceText+ViewProvider.swift"; sourceTree = ""; }; DC3BE0DC2077CC13002A0AC0 /* ClientQueryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientQueryViewController.swift; sourceTree = ""; }; DC3BE0DD2077CC13002A0AC0 /* ClientRootViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientRootViewController.swift; sourceTree = ""; }; DC3BE0E02077CD4B002A0AC0 /* Synchronized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Synchronized.swift; sourceTree = ""; }; @@ -1403,6 +1424,7 @@ DCD71E7D2742745D001592C6 /* BuildOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BuildOptions.m; sourceTree = ""; }; DCD810922398492C003B0053 /* OCLicenseDuration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCLicenseDuration.h; sourceTree = ""; }; DCD810932398492C003B0053 /* OCLicenseDuration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCLicenseDuration.m; sourceTree = ""; }; + DCD863FA28115C8700CA6631 /* Down.LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = Down.LICENSE; sourceTree = ""; }; DCD954DE247D62FA00E184E6 /* MessageTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageTableViewController.swift; sourceTree = ""; }; DCD9B873237960E600691929 /* OCLicenseManager+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCLicenseManager+Internal.h"; sourceTree = ""; }; DCDBB60225252FDA00FAD707 /* NotificationAuthErrorForwarder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationAuthErrorForwarder.h; sourceTree = ""; }; @@ -1456,6 +1478,7 @@ DCE93FEE21FCA434000E14F2 /* libzip.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = libzip.xcodeproj; path = external/libzip/libzip.xcodeproj; sourceTree = SOURCE_ROOT; }; DCE974B1207E3AF80069FC2B /* ThemeNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeNavigationController.swift; sourceTree = ""; }; DCE974BB207EACA60069FC2B /* UIImage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = ""; }; + DCEAF0892808254800980B6D /* DriveListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DriveListCell.swift; sourceTree = ""; }; DCEC3DE3242F665D0076B43C /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; DCEE1C9B23A0EADD00FE8D98 /* LicenseOfferView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicenseOfferView.swift; sourceTree = ""; }; DCF072D4279850CC00E0B01D /* OCCircularImageView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCCircularImageView.h; sourceTree = ""; }; @@ -1522,9 +1545,11 @@ 024F3A2124A3AB410083E11E /* CrashReporter in Frameworks */, 394A0B0022EEFC2C00603813 /* ownCloudAppShared.framework in Frameworks */, DC3BE0D82077BC5D002A0AC0 /* ownCloudSDK.framework in Frameworks */, + DCD8640628115FD600CA6631 /* OpenSSL in Frameworks */, DC080CF1238C8D850044C5D2 /* StoreKit.framework in Frameworks */, DCC085712293F1FD008CC05C /* ownCloudApp.framework in Frameworks */, DCE0275E21F1DF7E00F2544E /* ownCloudUI.framework in Frameworks */, + DCEAF08D28084B3800980B6D /* Down in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1544,6 +1569,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + DCEAF08F28084B5600980B6D /* Down in Frameworks */, DC04920B258CB06A00DEDC27 /* PocketSVG in Frameworks */, DCDC0ACF23CD186400DFE36D /* ownCloudApp.framework in Frameworks */, 394A0B0A22EEFCF500603813 /* ownCloudSDK.framework in Frameworks */, @@ -1810,6 +1836,8 @@ 3912208023436E9B0026C290 /* Client */ = { isa = PBXGroup; children = ( + DCEAF0842808250E00980B6D /* Collection Views */, + DC3AB1932808C2DE00789435 /* View Controllers */, DCA2EDDB279B0E5D001F04E6 /* Resource Sources */, DCE4E43424C199860051722F /* Actions */, DCE4E42D24C1961D0051722F /* File Lists */, @@ -1844,6 +1872,7 @@ DCCE54092080175B00067D1D /* TVGs */, DCE4E43824C19A950051722F /* Licensing */, DC0A354F24C0E18800FB58FC /* AppLock */, + DC3AB2492810A67F00789435 /* View Providers */, DC0A354C24C0E09300FB58FC /* User Interface */, 3912208023436E9B0026C290 /* Client */, DC0A355524C0E2DD00FB58FC /* Foundation Extensions */, @@ -1853,6 +1882,7 @@ 395E16FB22F0691F00DE89A1 /* Intent */, DCE4E4C024C255A70051722F /* App Extensions */, 394A0AFB22EEFC2C00603813 /* ownCloudAppShared.h */, + DCD863FA28115C8700CA6631 /* Down.LICENSE */, DC0492B0258CC4EE00DEDC27 /* PocketSVG.LICENSE */, 394A0AFC22EEFC2C00603813 /* Info.plist */, ); @@ -1910,6 +1940,9 @@ 398FD4502334CF66004B68A1 /* UIView+Extension.swift */, 4C235CED21F88C0300A989A8 /* UIViewController+Extension.swift */, DC66A9F7279F467200792AC8 /* UIKeyCommand+Extension.swift */, + DC3AB23F280FFF2700789435 /* NSMutableAttributedString+AppendStyled.swift */, + DC3AB24328104AA500789435 /* UIFont+Weight.swift */, + DC3AB2452810602500789435 /* UILabel+Extension.swift */, ); path = "UIKit Extension"; sourceTree = ""; @@ -2348,6 +2381,22 @@ path = "Item Policies"; sourceTree = ""; }; + DC3AB1932808C2DE00789435 /* View Controllers */ = { + isa = PBXGroup; + children = ( + DC3AB1972808C35300789435 /* ClientItemViewController.swift */, + ); + path = "View Controllers"; + sourceTree = ""; + }; + DC3AB2492810A67F00789435 /* View Providers */ = { + isa = PBXGroup; + children = ( + DC3AB24A2810A69600789435 /* OCResourceText+ViewProvider.swift */, + ); + path = "View Providers"; + sourceTree = ""; + }; DC3BE0DB2077CC13002A0AC0 /* Client */ = { isa = PBXGroup; children = ( @@ -2902,11 +2951,6 @@ DC3BE0DC2077CC13002A0AC0 /* ClientQueryViewController.swift */, 2308F93C21467F6200CF0B91 /* ClientDirectoryPickerViewController.swift */, DC9A116A27D0338400D90BA4 /* ClientSpacesTableViewController.swift */, - DCFC9ED428002F33005D9144 /* CollectionViewCellConfiguration.swift */, - DCFC9ED028002335005D9144 /* CollectionViewCellProvider.swift */, - DCFC9ED2280023BB005D9144 /* CollectionViewCellProvider+StandardImplementations.swift */, - DCFC9ECB28002303005D9144 /* CollectionViewSection.swift */, - DC04FFC727F5B79000F22569 /* CollectionViewController.swift */, ); path = "File Lists"; sourceTree = ""; @@ -3000,6 +3044,30 @@ name = Products; sourceTree = ""; }; + DCEAF0842808250E00980B6D /* Collection Views */ = { + isa = PBXGroup; + children = ( + DCEAF0882808252300980B6D /* Cells */, + DCFC9ED428002F33005D9144 /* CollectionViewCellConfiguration.swift */, + DCFC9ED028002335005D9144 /* CollectionViewCellProvider.swift */, + DCFC9ED2280023BB005D9144 /* CollectionViewCellProvider+StandardImplementations.swift */, + DCFC9ECB28002303005D9144 /* CollectionViewSection.swift */, + DC04FFC727F5B79000F22569 /* CollectionViewController.swift */, + ); + path = "Collection Views"; + sourceTree = ""; + }; + DCEAF0882808252300980B6D /* Cells */ = { + isa = PBXGroup; + children = ( + DCEAF0892808254800980B6D /* DriveListCell.swift */, + DC3AB2412810404000789435 /* DriveHeaderCell.swift */, + DC3AB23D280FFE3400789435 /* ItemListCell.swift */, + DC3AB2472810A10300789435 /* ExpandableResourceCell.swift */, + ); + path = Cells; + sourceTree = ""; + }; DCEC3DF5242F89B20076B43C /* QuickLook */ = { isa = PBXGroup; children = ( @@ -3321,6 +3389,8 @@ name = ownCloud; packageProductDependencies = ( 024F3A2024A3AB410083E11E /* CrashReporter */, + DCEAF08C28084B3800980B6D /* Down */, + DCD8640528115FD600CA6631 /* OpenSSL */, ); productName = ownCloud; productReference = 233BDE9C204FEFE500C06732 /* ownCloud.app */; @@ -3370,6 +3440,7 @@ name = ownCloudAppShared; packageProductDependencies = ( DC04920A258CB06A00DEDC27 /* PocketSVG */, + DCEAF08E28084B5600980B6D /* Down */, ); productName = ownCloudAppShared; productReference = 394A0AF922EEFC2C00603813 /* ownCloudAppShared.framework */; @@ -3648,6 +3719,7 @@ 024F3A1F24A3AB410083E11E /* XCRemoteSwiftPackageReference "plcrashreporter" */, DC049197258CAF8200DEDC27 /* XCRemoteSwiftPackageReference "PocketSVG" */, DCEAF066280767BC00980B6D /* XCRemoteSwiftPackageReference "OpenSSL" */, + DCEAF08B28084B3800980B6D /* XCRemoteSwiftPackageReference "Down" */, ); productRefGroup = 233BDE9D204FEFE500C06732 /* Products */; projectDirPath = ""; @@ -3797,6 +3869,7 @@ DCE4E49324C1FB750051722F /* folder-create.tvg in Resources */, DCE4E49824C1FB750051722F /* owncloud-logo.tvg in Resources */, DC0492B1258CC4EE00DEDC27 /* PocketSVG.LICENSE in Resources */, + DCD863FB28115C8700CA6631 /* Down.LICENSE in Resources */, DCE4E48A24C1FB750051722F /* application.tvg in Resources */, DCE4E49024C1FB750051722F /* folder-shared.tvg in Resources */, DCE4E4A224C1FB750051722F /* x-office-spreadsheet.tvg in Resources */, @@ -4211,10 +4284,12 @@ 399EA6F625E6544100B6FF11 /* ShareClientItemCell.swift in Sources */, DC0A357E24C0E43C00FB58FC /* ThemeStyle+Extensions.swift in Sources */, DC04FFC827F5B79000F22569 /* CollectionViewController.swift in Sources */, + DC3AB2422810404000789435 /* DriveHeaderCell.swift in Sources */, DCE4E44524C1A4260051722F /* FileListTableViewController.swift in Sources */, 399EA75A25E66DB000B6FF11 /* ClientItemResolvingCell.swift in Sources */, DCE4E43524C1999A0051722F /* Action.swift in Sources */, DCFC9ECC28002303005D9144 /* CollectionViewSection.swift in Sources */, + DC3AB2462810602500789435 /* UILabel+Extension.swift in Sources */, DC0A356C24C0E42200FB58FC /* AppLockWindow.swift in Sources */, DCFC9ED128002335005D9144 /* CollectionViewCellProvider.swift in Sources */, DCE4E43B24C19B4F0051722F /* NSLayoutConstraint+Extension.swift in Sources */, @@ -4260,18 +4335,22 @@ DCE4E45924C1F0F70051722F /* ClientQueryViewController.swift in Sources */, DCE4E45124C1E4430051722F /* UIBarButtonItem+Extension.swift in Sources */, DCA2EDE2279B16F1001F04E6 /* ResourceSourceItemIcons.swift in Sources */, + DCEAF08A2808254800980B6D /* DriveListCell.swift in Sources */, DC0A357724C0E43200FB58FC /* ProgressSummarizer.swift in Sources */, 392CFEB72705831700631D2B /* LAContext+Extension.swift in Sources */, DCE4E48724C1F9F50051722F /* CreateFolderAction.swift in Sources */, 39E6DE86233CDF1E008DAE04 /* OCItemTracker.swift in Sources */, DC0A358624C0E44600FB58FC /* ThemeTVGResource.swift in Sources */, + DC3AB24428104AA500789435 /* UIFont+Weight.swift in Sources */, DC0A358524C0E44600FB58FC /* ThemeResource.swift in Sources */, DCFC9ED528002F33005D9144 /* CollectionViewCellConfiguration.swift in Sources */, DC0A355624C0E33A00FB58FC /* Log.swift in Sources */, + DC3AB24B2810A69600789435 /* OCResourceText+ViewProvider.swift in Sources */, DC0A359624C0E61500FB58FC /* UIView+Extension.swift in Sources */, DCE4E43924C19AB20051722F /* MoreStaticTableViewController.swift in Sources */, DC0A358C24C0E44B00FB58FC /* ThemeWindow.swift in Sources */, DC0A357124C0E42700FB58FC /* StaticTableViewRow.swift in Sources */, + DC3AB1982808C35300789435 /* ClientItemViewController.swift in Sources */, DCE4E43124C197450051722F /* OpenItemUserActivity.swift in Sources */, DCFC9ED3280023BB005D9144 /* CollectionViewCellProvider+StandardImplementations.swift in Sources */, DC0A358D24C0E44B00FB58FC /* ThemedAlertController.swift in Sources */, @@ -4290,6 +4369,9 @@ 399EA72625E6565900B6FF11 /* OCCore+Extension.swift in Sources */, DC0A359424C0E5C800FB58FC /* GitCommit.swift in Sources */, DCE4E44B24C1D3780051722F /* QueryFileListTableViewController.swift in Sources */, + DC3AB23E280FFE3400789435 /* ItemListCell.swift in Sources */, + DC3AB2482810A10300789435 /* ExpandableResourceCell.swift in Sources */, + DC3AB240280FFF2700789435 /* NSMutableAttributedString+AppendStyled.swift in Sources */, DC0A357C24C0E43C00FB58FC /* ThemeCollection.swift in Sources */, 399EA6F525E6544100B6FF11 /* GroupSharingTableViewController.swift in Sources */, DC0A357924C0E43700FB58FC /* CardTransitionDelegate.swift in Sources */, @@ -5655,6 +5737,14 @@ minimumVersion = 1.0.0; }; }; + DCEAF08B28084B3800980B6D /* XCRemoteSwiftPackageReference "Down" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/johnxnguyen/Down"; + requirement = { + branch = master; + kind = branch; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -5683,11 +5773,26 @@ package = DC049197258CAF8200DEDC27 /* XCRemoteSwiftPackageReference "PocketSVG" */; productName = PocketSVG; }; + DCD8640528115FD600CA6631 /* OpenSSL */ = { + isa = XCSwiftPackageProductDependency; + package = DCEAF066280767BC00980B6D /* XCRemoteSwiftPackageReference "OpenSSL" */; + productName = OpenSSL; + }; DCEAF06C280767CF00980B6D /* OpenSSL */ = { isa = XCSwiftPackageProductDependency; package = DCEAF066280767BC00980B6D /* XCRemoteSwiftPackageReference "OpenSSL" */; productName = OpenSSL; }; + DCEAF08C28084B3800980B6D /* Down */ = { + isa = XCSwiftPackageProductDependency; + package = DCEAF08B28084B3800980B6D /* XCRemoteSwiftPackageReference "Down" */; + productName = Down; + }; + DCEAF08E28084B5600980B6D /* Down */ = { + isa = XCSwiftPackageProductDependency; + package = DCEAF08B28084B3800980B6D /* XCRemoteSwiftPackageReference "Down" */; + productName = Down; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 233BDE94204FEFE500C06732 /* Project object */; diff --git a/ownCloud/Client/ClientRootViewController.swift b/ownCloud/Client/ClientRootViewController.swift index 4951ed7fe..8646f0e51 100644 --- a/ownCloud/Client/ClientRootViewController.swift +++ b/ownCloud/Client/ClientRootViewController.swift @@ -415,23 +415,23 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa // return false // }) - composedDataSource.setSortComparator(OCDataSourceComposition.itemComparator(withItemRetrieval: false, fromRecordComparator: { record1, record2 in - var presentable1 : OCDataItemPresentable? - var presentable2 : OCDataItemPresentable? - - if let item = record1?.item { - presentable1 = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable - } - - if let item = record2?.item { - presentable2 = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable - } - - let title1 = presentable1?.title?.last?.lowercased() ?? "" - let title2 = presentable2?.title?.last?.lowercased() ?? "" - - return title1.localizedCompare(title2) - }), for: core.projectDrivesDataSource) +// composedDataSource.setSortComparator(OCDataSourceComposition.itemComparator(withItemRetrieval: false, fromRecordComparator: { record1, record2 in +// var presentable1 : OCDataItemPresentable? +// var presentable2 : OCDataItemPresentable? +// +// if let item = record1?.item { +// presentable1 = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable +// } +// +// if let item = record2?.item { +// presentable2 = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable +// } +// +// let title1 = presentable1?.title?.last?.lowercased() ?? "" +// let title2 = presentable2?.title?.last?.lowercased() ?? "" +// +// return title1.localizedCompare(title2) +// }), for: core.projectDrivesDataSource) // composedDataSource.setFilter(OCDataSourceComposition.itemFilter(withItemRetrieval: false, fromRecordFilter: { (record) in // if let item = record?.item, @@ -445,10 +445,10 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa }) topLevelViewController = CollectionViewController(core: core, rootViewController: self, sections: [ - CollectionViewSection(identifier: "composed", dataSource: composedDataSource) +// CollectionViewSection(identifier: "composed", dataSource: composedDataSource) -// CollectionViewSection(identifier: "top", dataSource: core.hierarchicDrivesDataSource), -// CollectionViewSection(identifier: "projects", dataSource: core.projectDrivesDataSource) + CollectionViewSection(identifier: "top", dataSource: core.hierarchicDrivesDataSource), + CollectionViewSection(identifier: "projects", dataSource: core.projectDrivesDataSource) ]) } else { let query = OCQuery(for: .legacyRoot) diff --git a/ownCloudAppFramework/View Providers/OCImage+ViewProvider.m b/ownCloudAppFramework/View Providers/OCImage+ViewProvider.m index b68698f2a..696e232c7 100644 --- a/ownCloudAppFramework/View Providers/OCImage+ViewProvider.m +++ b/ownCloudAppFramework/View Providers/OCImage+ViewProvider.m @@ -43,7 +43,18 @@ - (void)provideViewForSize:(CGSize)size inContext:(nullable OCViewProviderContex UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; imageView.translatesAutoresizingMaskIntoConstraints = NO; - imageView.contentMode = UIViewContentModeScaleAspectFit; + + // Apply content mode + NSNumber *contentMode; + if ((contentMode = context.attributes[OCViewProviderContextKeyContentMode]) != nil) + { + imageView.contentMode = contentMode.integerValue; + imageView.clipsToBounds = true; + } + else + { + imageView.contentMode = UIViewContentModeScaleAspectFit; + } completionHandler(imageView); } diff --git a/ownCloudAppShared/Client/Collection Views/Cells/DriveHeaderCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/DriveHeaderCell.swift new file mode 100644 index 000000000..802aec558 --- /dev/null +++ b/ownCloudAppShared/Client/Collection Views/Cells/DriveHeaderCell.swift @@ -0,0 +1,83 @@ +// +// DriveHeaderCell.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 20.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +import UIKit + +class DriveHeaderCell: DriveListCell, Themeable { + let darkBackgroundView = UIView() + + weak var collectionViewController : CollectionViewController? + + deinit { + Theme.shared.unregister(client: self) + } + + override func configure() { + darkBackgroundView.translatesAutoresizingMaskIntoConstraints = false + darkBackgroundView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.3) + + contentView.clipsToBounds = true + + super.configure() + + titleLabel.font = UIFont.preferredFont(forTextStyle: .title1, with: .bold) + titleLabel.makeLabelWrapText() + + subtitleLabel.font = UIFont.preferredFont(forTextStyle: .headline, with: .semibold) + subtitleLabel.makeLabelWrapText() + + textOuterSpacing = 20 + + coverImageResourceView.fallbackView = nil + + contentView.insertSubview(darkBackgroundView, belowSubview: titleLabel) + + Theme.shared.register(client: self, applyImmediately: true) + } + + func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { + coverImageResourceView.backgroundColor = collection.lightBrandColor + + titleLabel.textColor = .white + subtitleLabel.textColor = .white + } + + override func configureLayout() { + coverImageHeightConstraint = coverImageResourceView.heightAnchor.constraint(greaterThanOrEqualToConstant: 160) + + NSLayoutConstraint.activate([ + coverImageHeightConstraint!, + + coverImageResourceView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + coverImageResourceView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + coverImageResourceView.topAnchor.constraint(equalTo: contentView.topAnchor), + coverImageResourceView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + + titleLabel.topAnchor.constraint(greaterThanOrEqualTo: contentView.topAnchor, constant: textOuterSpacing), + + titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: textOuterSpacing), + titleLabel.trailingAnchor.constraint(lessThanOrEqualTo: contentView.trailingAnchor, constant: -textOuterSpacing), + titleLabel.bottomAnchor.constraint(equalTo: subtitleLabel.topAnchor, constant: -textInterSpacing), + + subtitleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: textOuterSpacing), + subtitleLabel.trailingAnchor.constraint(lessThanOrEqualTo: contentView.trailingAnchor, constant: -textOuterSpacing), + subtitleLabel.bottomAnchor.constraint(equalTo: coverImageResourceView.bottomAnchor, constant: -textOuterSpacing), + + darkBackgroundView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + darkBackgroundView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + darkBackgroundView.topAnchor.constraint(equalTo: titleLabel.topAnchor, constant: -textOuterSpacing), + darkBackgroundView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + ]) + } + + var coverImageHeightConstraint : NSLayoutConstraint? + + @objc func growHeight() { + coverImageHeightConstraint?.constant += 64 + } +} diff --git a/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift new file mode 100644 index 000000000..906ceca95 --- /dev/null +++ b/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift @@ -0,0 +1,239 @@ +// +// DriveCell.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 14.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK + +class DriveListCell: UICollectionViewListCell { + let coverImageResourceView = ResourceViewHost() + let spaceFallbackImageView = UIImageView() + + let titleLabel = UILabel() + let subtitleLabel = UILabel() + + override init(frame: CGRect) { + super.init(frame: frame) + configure() + configureLayout() + } + + required init?(coder: NSCoder) { + fatalError() + } + + var textOuterSpacing : CGFloat = 10 + var textInterSpacing : CGFloat = 5 + + var title : String? { + didSet { + titleLabel.text = title + } + } + var subtitle : String? { + didSet { + subtitleLabel.text = subtitle + } + } + + func configure() { + coverImageResourceView.translatesAutoresizingMaskIntoConstraints = false + spaceFallbackImageView.translatesAutoresizingMaskIntoConstraints = false + + titleLabel.translatesAutoresizingMaskIntoConstraints = false + subtitleLabel.translatesAutoresizingMaskIntoConstraints = false + + contentView.addSubview(coverImageResourceView) + contentView.addSubview(titleLabel) + contentView.addSubview(subtitleLabel) + + let configuration = UIImage.SymbolConfiguration(hierarchicalColor: .lightGray) + + spaceFallbackImageView.image = UIImage(systemName: "rectangle.grid.2x2.fill", withConfiguration: configuration)?.withAlignmentRectInsets(UIEdgeInsets(top: -textOuterSpacing*2, left: -textOuterSpacing*2, bottom: -textOuterSpacing*2, right: -textOuterSpacing*2)).withTintColor(.darkGray) + spaceFallbackImageView.contentMode = .scaleAspectFill + + coverImageResourceView.backgroundColor = .lightGray + coverImageResourceView.fallbackView = spaceFallbackImageView + coverImageResourceView.viewProviderContext = OCViewProviderContext(attributes: [.contentMode : UIView.ContentMode.scaleAspectFill.rawValue ]) + + titleLabel.font = UIFont.preferredFont(forTextStyle: .headline) + titleLabel.adjustsFontForContentSizeCategory = true + titleLabel.textColor = UIColor.label + subtitleLabel.font = UIFont.preferredFont(forTextStyle: .subheadline) + subtitleLabel.adjustsFontForContentSizeCategory = true + subtitleLabel.textColor = UIColor.secondaryLabel + + titleLabel.setContentHuggingPriority(.required, for: .vertical) + subtitleLabel.setContentHuggingPriority(.required, for: .vertical) + + coverImageResourceView.backgroundColor = .white + } + + func configureLayout() { + NSLayoutConstraint.activate([ + coverImageResourceView.widthAnchor.constraint(equalToConstant: 64), + + coverImageResourceView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + coverImageResourceView.topAnchor.constraint(equalTo: contentView.topAnchor), + coverImageResourceView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + + titleLabel.leadingAnchor.constraint(equalTo: coverImageResourceView.trailingAnchor, constant: textOuterSpacing), + titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -textOuterSpacing), + titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: textOuterSpacing), + titleLabel.bottomAnchor.constraint(equalTo: subtitleLabel.topAnchor, constant: -textInterSpacing), + + subtitleLabel.leadingAnchor.constraint(equalTo: coverImageResourceView.trailingAnchor, constant: textOuterSpacing), + subtitleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -textOuterSpacing), + subtitleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -textOuterSpacing), + + separatorLayoutGuide.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor) + ]) + } + +// func updateWith(item: OCDataItem?, cellConfiguration: CollectionViewCellConfiguration?) { +// var coverImageRequest : OCResourceRequest? +// +// if let item = item, +// let cellConfiguration = cellConfiguration, +// let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil, withOptions: nil) as? OCDataItemPresentable { +// title = presentable.title +// subtitle = presentable.subtitle +// +// if let resourceManager = cellConfiguration.core?.vault.resourceManager { +// coverImageRequest = try? presentable.provideResourceRequest(.coverImage, withOptions: nil) +// } +// } +// } +} + +extension DriveListCell { + static func registerCellProvider() { + let driveListCellRegistration = UICollectionView.CellRegistration { (cell, indexPath, collectionItemRef) in + var coverImageRequest : OCResourceRequest? + var resourceManager : OCResourceManager? + var title : String? + var subtitle : String? + + if let cellConfiguration = collectionItemRef.ocCellConfiguration { + var itemRecord = cellConfiguration.record + + resourceManager = cellConfiguration.core?.vault.resourceManager + + if itemRecord == nil { + if let collectionViewController = cellConfiguration.hostViewController { + let (itemRef, _) = collectionViewController.unwrap(collectionItemRef) + + if let retrievedItemRecord = try? cellConfiguration.source?.record(forItemRef: itemRef) { + itemRecord = retrievedItemRecord + } + } + } + + if let itemRecord = itemRecord { + if let item = itemRecord.item { + if let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil, withOptions: nil) as? OCDataItemPresentable { + + title = presentable.title + subtitle = presentable.subtitle + + coverImageRequest = try? presentable.provideResourceRequest(.coverImage, withOptions: nil) + } + } else { + // Request reconfiguration of cell + itemRecord.retrieveItem(completionHandler: { error, itemRecord in + if let collectionViewController = cellConfiguration.hostViewController { + collectionViewController.collectionViewDataSource.requestReconfigurationOfItems([collectionItemRef]) + } + }) + } + } + } + + cell.title = title + cell.subtitle = subtitle + + cell.coverImageResourceView.request = coverImageRequest + + if let coverImageRequest = coverImageRequest { + resourceManager?.start(coverImageRequest) + } + + cell.accessories = [ .disclosureIndicator() ] + } + + let driveHeaderCellRegistration = UICollectionView.CellRegistration { (cell, indexPath, collectionItemRef) in + var coverImageRequest : OCResourceRequest? + var resourceManager : OCResourceManager? + var title : String? + var subtitle : String? + + if let cellConfiguration = collectionItemRef.ocCellConfiguration { + var itemRecord = cellConfiguration.record + + cell.collectionViewController = cellConfiguration.hostViewController + + resourceManager = cellConfiguration.core?.vault.resourceManager + + if itemRecord == nil { + if let collectionViewController = cellConfiguration.hostViewController { + let (itemRef, _) = collectionViewController.unwrap(collectionItemRef) + + if let retrievedItemRecord = try? cellConfiguration.source?.record(forItemRef: itemRef) { + itemRecord = retrievedItemRecord + } + } + } + + if let itemRecord = itemRecord { + if let item = itemRecord.item { + if let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil, withOptions: nil) as? OCDataItemPresentable { + + title = presentable.title + subtitle = presentable.subtitle + + coverImageRequest = try? presentable.provideResourceRequest(.coverImage, withOptions: nil) + } + } else { + // Request reconfiguration of cell + itemRecord.retrieveItem(completionHandler: { error, itemRecord in + if let collectionViewController = cellConfiguration.hostViewController { + collectionViewController.collectionViewDataSource.requestReconfigurationOfItems([collectionItemRef]) + } + }) + } + } + } + + cell.title = title + cell.subtitle = subtitle + + cell.coverImageResourceView.request = coverImageRequest + + if let coverImageRequest = coverImageRequest { + resourceManager?.start(coverImageRequest) + } + } + + CollectionViewCellProvider.register(CollectionViewCellProvider(for: .drive, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in + if cellConfiguration?.style == .header { + return collectionView.dequeueConfiguredReusableCell(using: driveHeaderCellRegistration, for: indexPath, item: itemRef) + } else { + return collectionView.dequeueConfiguredReusableCell(using: driveListCellRegistration, for: indexPath, item: itemRef) + } + })) + } +} diff --git a/ownCloudAppShared/Client/Collection Views/Cells/ExpandableResourceCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/ExpandableResourceCell.swift new file mode 100644 index 000000000..fe51dd9ea --- /dev/null +++ b/ownCloudAppShared/Client/Collection Views/Cells/ExpandableResourceCell.swift @@ -0,0 +1,163 @@ +// +// ExpandableResourceCell.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 20.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK + +class ExpandableResourceCell: UICollectionViewListCell { + override init(frame: CGRect) { + super.init(frame: frame) + configure() + configureLayout() + } + + required init?(coder: NSCoder) { + fatalError() + } + + var resourceView: UIView? { + willSet { + resourceView?.removeFromSuperview() + } + + didSet { + if let resourceView = resourceView { + resourceView.translatesAutoresizingMaskIntoConstraints = false + contentView.insertSubview(resourceView, belowSubview: expandButton) + configureLayout() + } + } + } + + var expandButton : UIButton = UIButton() + + var resourceEdgeInsets: UIEdgeInsets = UIEdgeInsets(top: 5, left: 10, bottom: 5, right: 10) + + let collapsedHeight: CGFloat = 192 + + var expandItemImage : UIImage? + var collapseItemImage : UIImage? + + var resource : OCResource? { + didSet { + (resource as? OCViewProvider)?.provideView(for: .zero, in: nil, completion: { [weak self] textView in + self?.resourceView = textView + }) + } + } + + func configure() { + expandButton.translatesAutoresizingMaskIntoConstraints = false + + let configuration = UIImage.SymbolConfiguration(pointSize: 32, weight: .regular) + + expandItemImage = UIImage(systemName: "chevron.down.circle.fill", withConfiguration: configuration) + collapseItemImage = UIImage(systemName: "chevron.up.circle.fill", withConfiguration: configuration) + + expandButton.setImage(expandItemImage, for: .normal) + expandButton.addAction(UIAction(handler: { [weak self] action in + self?.openClose() + }), for: .primaryActionTriggered) + + contentView.addSubview(expandButton) + contentView.clipsToBounds = true + + collapsedConstraint = contentView.heightAnchor.constraint(lessThanOrEqualToConstant: collapsedHeight).with(priority: .required) + } + + var collapsedConstraint: NSLayoutConstraint? + + func configureLayout() { + guard let textView = resourceView else { return } + + NSLayoutConstraint.activate([ + textView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: resourceEdgeInsets.left), + textView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -resourceEdgeInsets.right), + textView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: resourceEdgeInsets.top), + textView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -resourceEdgeInsets.bottom).with(priority: .defaultHigh), + + expandButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -resourceEdgeInsets.right), + expandButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -resourceEdgeInsets.bottom), + + separatorLayoutGuide.leadingAnchor.constraint(equalTo: contentView.leadingAnchor) + ]) + + collapsedConstraint?.isActive = isCollapsed + } + + weak var collectionViewController: CollectionViewController? + var collectionItemRef: CollectionViewController.ItemRef? + + var isCollapsed : Bool = true + + func openClose() { + if let collapsedConstraint = collapsedConstraint { + isCollapsed = !isCollapsed + + expandButton.setImage(isCollapsed ? expandItemImage! : collapseItemImage!, for: .normal) + + collapsedConstraint.isActive = isCollapsed + } + + if let collectionViewController = collectionViewController, let collectionItemRef = collectionItemRef { + collectionViewController.collectionViewDataSource.requestReconfigurationOfItems([collectionItemRef], animated: false) + } + } + + override func layoutSubviews() { + super.layoutSubviews() + + if let height = resourceView?.frame.size.height { + if (height + resourceEdgeInsets.top + resourceEdgeInsets.bottom) > collapsedHeight { + expandButton.isHidden = false + } else { + expandButton.isHidden = true + } + } + } +} + +extension ExpandableResourceCell { + static func registerCellProvider() { + let itemListCellRegistration = UICollectionView.CellRegistration { (cell, indexPath, collectionItemRef) in + if let cellConfiguration = collectionItemRef.ocCellConfiguration { + if let itemRecord = cellConfiguration.record { + if let item = itemRecord.item { + if let textResource = item as? OCResource { + cell.resource = textResource + cell.collectionViewController = cellConfiguration.hostViewController + cell.collectionItemRef = collectionItemRef + } + } else { + // Request reconfiguration of cell + itemRecord.retrieveItem(completionHandler: { error, itemRecord in + if let collectionViewController = cellConfiguration.hostViewController { + collectionViewController.collectionViewDataSource.requestReconfigurationOfItems([collectionItemRef]) + } + }) + } + } + } + } + + CollectionViewCellProvider.register(CollectionViewCellProvider(for: .textResource, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in + return collectionView.dequeueConfiguredReusableCell(using: itemListCellRegistration, for: indexPath, item: itemRef) + })) + } + +} diff --git a/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift new file mode 100644 index 000000000..ca2bc66da --- /dev/null +++ b/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift @@ -0,0 +1,669 @@ +// +// ItemListCell.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 20.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK +import ownCloudApp + +/* +public protocol OpenItemHandling { + @discardableResult func open(item: OCItem, animated: Bool, pushViewController: Bool) -> UIViewController? +} + +public protocol MoreItemHandling { + @discardableResult func moreOptions(for item: OCItem, at location: OCExtensionLocationIdentifier, core: OCCore, query: OCQuery?, sender: AnyObject?) -> Bool +} + +public protocol RevealItemHandling { + @discardableResult func reveal(item: OCItem, core: OCCore, sender: AnyObject?) -> Bool + func showReveal(at path: IndexPath) -> Bool +} + +public protocol InlineMessageSupport { + func hasInlineMessage(for item: OCItem) -> Bool + func showInlineMessageFor(item: OCItem) +} +*/ + +public protocol ItemListCellDelegate: class { + + func moreButtonTapped(cell: ItemListCell) + func messageButtonTapped(cell: ItemListCell) + func revealButtonTapped(cell: ItemListCell) + + func hasMessage(for item: OCItem) -> Bool +} + +open class ItemListCell: UICollectionViewListCell { + private let horizontalMargin : CGFloat = 15 + private let verticalLabelMargin : CGFloat = 10 + private let verticalIconMargin : CGFloat = 10 + private let horizontalSmallMargin : CGFloat = 10 + private let spacing : CGFloat = 15 + private let smallSpacing : CGFloat = 2 + private let iconViewWidth : CGFloat = 40 + private let detailIconViewHeight : CGFloat = 15 + private let moreButtonWidth : CGFloat = 60 + private let revealButtonWidth : CGFloat = 35 + private let verticalLabelMarginFromCenter : CGFloat = 2 + private let iconSize : CGSize = CGSize(width: 40, height: 40) + private let thumbnailSize : CGSize = CGSize(width: 60, height: 60) + + open weak var delegate: ItemListCellDelegate? { + didSet { + isMoreButtonPermanentlyHidden = (delegate as? MoreItemHandling == nil) + } + } + + open var titleLabel : UILabel = UILabel() + open var detailLabel : UILabel = UILabel() + open var iconView : ResourceViewHost = ResourceViewHost() + open var cloudStatusIconView : UIImageView = UIImageView() + open var sharedStatusIconView : UIImageView = UIImageView() + open var publicLinkStatusIconView : UIImageView = UIImageView() + open var moreButton : UIButton = UIButton() + open var messageButton : UIButton = UIButton() + open var revealButton : UIButton = UIButton() + open var progressView : ProgressView? + + open var moreButtonWidthConstraint : NSLayoutConstraint? + open var revealButtonWidthConstraint : NSLayoutConstraint? + + open var sharedStatusIconViewZeroWidthConstraint : NSLayoutConstraint? + open var publicLinkStatusIconViewZeroWidthConstraint : NSLayoutConstraint? + open var cloudStatusIconViewZeroWidthConstraint : NSLayoutConstraint? + + open var sharedStatusIconViewRightMarginConstraint : NSLayoutConstraint? + open var publicLinkStatusIconViewRightMarginConstraint : NSLayoutConstraint? + open var cloudStatusIconViewRightMarginConstraint : NSLayoutConstraint? + + open var activeThumbnailRequest : OCResourceRequestItemThumbnail? + + open var hasMessageForItem : Bool = false + + open var isMoreButtonPermanentlyHidden = false { + didSet { + if isMoreButtonPermanentlyHidden { + moreButtonWidthConstraint?.constant = 0 + } else { + moreButtonWidthConstraint?.constant = showRevealButton ? revealButtonWidth : moreButtonWidth + } + } + } + + open var isActive = true { + didSet { + let alpha : CGFloat = self.isActive ? 1.0 : 0.5 + titleLabel.alpha = alpha + detailLabel.alpha = alpha + iconView.alpha = alpha + cloudStatusIconView.alpha = alpha + } + } + + open weak var core : OCCore? + + override init(frame: CGRect) { + super.init(frame: frame) + prepareViewAndConstraints() + + //! + /* + self.multipleSelectionBackgroundView = { + let blankView = UIView(frame: CGRect.zero) + blankView.backgroundColor = UIColor.clear + blankView.layer.masksToBounds = true + return blankView + }() + */ + + NotificationCenter.default.addObserver(self, selector: #selector(updateAvailableOfflineStatus(_:)), name: .OCCoreItemPoliciesChanged, object: OCItemPolicyKind.availableOffline) + + NotificationCenter.default.addObserver(self, selector: #selector(updateHasMessage(_:)), name: .ClientSyncRecordIDsWithMessagesChanged, object: nil) + + PointerEffect.install(on: self.contentView, effectStyle: .hover) + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + deinit { + NotificationCenter.default.removeObserver(self, name: .OCCoreItemPoliciesChanged, object: OCItemPolicyKind.availableOffline) + + NotificationCenter.default.removeObserver(self, name: .ClientSyncRecordIDsWithMessagesChanged, object: nil) + + self.localID = nil + self.core = nil + } + + func prepareViewAndConstraints() { + titleLabel.translatesAutoresizingMaskIntoConstraints = false + + detailLabel.translatesAutoresizingMaskIntoConstraints = false + + iconView.translatesAutoresizingMaskIntoConstraints = false + iconView.contentMode = .scaleAspectFit + + moreButton.translatesAutoresizingMaskIntoConstraints = false + + revealButton.translatesAutoresizingMaskIntoConstraints = false + + messageButton.translatesAutoresizingMaskIntoConstraints = false + + cloudStatusIconView.translatesAutoresizingMaskIntoConstraints = false + cloudStatusIconView.contentMode = .center + cloudStatusIconView.contentMode = .scaleAspectFit + + sharedStatusIconView.translatesAutoresizingMaskIntoConstraints = false + sharedStatusIconView.contentMode = .center + sharedStatusIconView.contentMode = .scaleAspectFit + + publicLinkStatusIconView.translatesAutoresizingMaskIntoConstraints = false + publicLinkStatusIconView.contentMode = .center + publicLinkStatusIconView.contentMode = .scaleAspectFit + + titleLabel.font = UIFont.preferredFont(forTextStyle: .callout) + titleLabel.adjustsFontForContentSizeCategory = true + titleLabel.lineBreakMode = .byTruncatingMiddle + + detailLabel.font = UIFont.preferredFont(forTextStyle: .footnote) + detailLabel.adjustsFontForContentSizeCategory = true + + self.contentView.addSubview(titleLabel) + self.contentView.addSubview(detailLabel) + self.contentView.addSubview(iconView) + self.contentView.addSubview(sharedStatusIconView) + self.contentView.addSubview(publicLinkStatusIconView) + self.contentView.addSubview(cloudStatusIconView) + self.contentView.addSubview(moreButton) + self.contentView.addSubview(revealButton) + self.contentView.addSubview(messageButton) + + moreButton.setImage(UIImage(named: "more-dots"), for: .normal) + moreButton.contentMode = .center + moreButton.isPointerInteractionEnabled = true + + revealButton.setImage(UIImage(systemName: "arrow.right.circle.fill"), for: .normal) + revealButton.isPointerInteractionEnabled = true + revealButton.contentMode = .center + revealButton.isHidden = !showRevealButton + revealButton.accessibilityLabel = "Reveal in folder".localized + + messageButton.setTitle("⚠️", for: .normal) + messageButton.contentMode = .center + messageButton.isPointerInteractionEnabled = true + messageButton.isHidden = true + + moreButton.addTarget(self, action: #selector(moreButtonTapped), for: .touchUpInside) + revealButton.addTarget(self, action: #selector(revealButtonTapped), for: .touchUpInside) + messageButton.addTarget(self, action: #selector(messageButtonTapped), for: .touchUpInside) + + sharedStatusIconView.setContentHuggingPriority(.required, for: .vertical) + sharedStatusIconView.setContentHuggingPriority(.required, for: .horizontal) + sharedStatusIconView.setContentCompressionResistancePriority(.required, for: .vertical) + sharedStatusIconView.setContentCompressionResistancePriority(.required, for: .horizontal) + + publicLinkStatusIconView.setContentHuggingPriority(.required, for: .vertical) + publicLinkStatusIconView.setContentHuggingPriority(.required, for: .horizontal) + publicLinkStatusIconView.setContentCompressionResistancePriority(.required, for: .vertical) + publicLinkStatusIconView.setContentCompressionResistancePriority(.required, for: .horizontal) + + cloudStatusIconView.setContentHuggingPriority(.required, for: .vertical) + cloudStatusIconView.setContentHuggingPriority(.required, for: .horizontal) + cloudStatusIconView.setContentCompressionResistancePriority(.required, for: .vertical) + cloudStatusIconView.setContentCompressionResistancePriority(.required, for: .horizontal) + + iconView.setContentCompressionResistancePriority(.defaultLow, for: .vertical) + + titleLabel.setContentCompressionResistancePriority(.defaultHigh, for: .vertical) + detailLabel.setContentCompressionResistancePriority(.defaultHigh, for: .vertical) + + moreButtonWidthConstraint = moreButton.widthAnchor.constraint(equalToConstant: showRevealButton ? revealButtonWidth : moreButtonWidth) + revealButtonWidthConstraint = revealButton.widthAnchor.constraint(equalToConstant: showRevealButton ? revealButtonWidth : 0) + + cloudStatusIconViewZeroWidthConstraint = cloudStatusIconView.widthAnchor.constraint(equalToConstant: 0) + sharedStatusIconViewZeroWidthConstraint = sharedStatusIconView.widthAnchor.constraint(equalToConstant: 0) + publicLinkStatusIconViewZeroWidthConstraint = publicLinkStatusIconView.widthAnchor.constraint(equalToConstant: 0) + + cloudStatusIconViewRightMarginConstraint = sharedStatusIconView.leadingAnchor.constraint(equalTo: cloudStatusIconView.trailingAnchor) + sharedStatusIconViewRightMarginConstraint = publicLinkStatusIconView.leadingAnchor.constraint(equalTo: sharedStatusIconView.trailingAnchor) + publicLinkStatusIconViewRightMarginConstraint = detailLabel.leadingAnchor.constraint(equalTo: publicLinkStatusIconView.trailingAnchor) + + NSLayoutConstraint.activate([ + iconView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: horizontalMargin), + iconView.trailingAnchor.constraint(equalTo: titleLabel.leadingAnchor, constant: -spacing), + iconView.widthAnchor.constraint(equalToConstant: iconViewWidth), + iconView.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: verticalIconMargin), + iconView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -verticalIconMargin), + + titleLabel.trailingAnchor.constraint(equalTo: moreButton.leadingAnchor, constant: 0), + detailLabel.trailingAnchor.constraint(equalTo: moreButton.leadingAnchor, constant: 0), + + cloudStatusIconViewZeroWidthConstraint!, + sharedStatusIconViewZeroWidthConstraint!, + publicLinkStatusIconViewZeroWidthConstraint!, + + cloudStatusIconView.leadingAnchor.constraint(equalTo: iconView.trailingAnchor, constant: spacing), + cloudStatusIconViewRightMarginConstraint!, + sharedStatusIconViewRightMarginConstraint!, + publicLinkStatusIconViewRightMarginConstraint!, + + titleLabel.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: verticalLabelMargin), + titleLabel.bottomAnchor.constraint(equalTo: self.contentView.centerYAnchor, constant: -verticalLabelMarginFromCenter), + detailLabel.topAnchor.constraint(equalTo: self.contentView.centerYAnchor, constant: verticalLabelMarginFromCenter), + detailLabel.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -verticalLabelMargin), + + cloudStatusIconView.centerYAnchor.constraint(equalTo: detailLabel.centerYAnchor), + sharedStatusIconView.centerYAnchor.constraint(equalTo: detailLabel.centerYAnchor), + publicLinkStatusIconView.centerYAnchor.constraint(equalTo: detailLabel.centerYAnchor), + + cloudStatusIconView.heightAnchor.constraint(equalToConstant: detailIconViewHeight), + sharedStatusIconView.heightAnchor.constraint(equalToConstant: detailIconViewHeight), + publicLinkStatusIconView.heightAnchor.constraint(equalToConstant: detailIconViewHeight), + + moreButton.topAnchor.constraint(equalTo: self.contentView.topAnchor), + moreButton.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor), + moreButtonWidthConstraint!, + moreButton.trailingAnchor.constraint(equalTo: revealButton.leadingAnchor), + + revealButton.topAnchor.constraint(equalTo: self.contentView.topAnchor), + revealButton.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor), + revealButtonWidthConstraint!, + revealButton.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor), + + messageButton.leadingAnchor.constraint(equalTo: moreButton.leadingAnchor), + messageButton.trailingAnchor.constraint(equalTo: moreButton.trailingAnchor), + messageButton.topAnchor.constraint(equalTo: moreButton.topAnchor), + messageButton.bottomAnchor.constraint(equalTo: moreButton.bottomAnchor) + ]) + + self.accessibilityElements = [titleLabel, detailLabel, moreButton, revealButton] + } + + // MARK: - Present item + open var item : OCItem? { + didSet { + localID = item?.localID as NSString? + + if let newItem = item { + updateWith(newItem) + } + } + } + + open func titleLabelString(for item: OCItem?) -> NSAttributedString { + guard let item = item else { return NSAttributedString(string: "") } + + if item.type == .file, let itemName = item.baseName, let itemExtension = item.fileExtension { + return NSMutableAttributedString() + .appendBold(itemName) + .appendNormal(".") + .appendNormal(itemExtension) + } else if item.type == .collection, let itemName = item.name { + return NSMutableAttributedString() + .appendBold(itemName) + } + + return NSAttributedString(string: "") + } + + open func detailLabelString(for item: OCItem?) -> String { + if let item = item { + var size: String = item.sizeLocalized + + if item.size < 0 { + size = "Pending".localized + } + + return size + " - " + item.lastModifiedLocalized + } + + return "" + } + + open func updateWith(_ item: OCItem) { + // Cancel any already active request + if let activeThumbnailRequest = activeThumbnailRequest { + core?.vault.resourceManager?.stop(activeThumbnailRequest) + self.activeThumbnailRequest = nil + } + + // Set has message + self.hasMessageForItem = delegate?.hasMessage(for: item) ?? false + + // Set the icon and initiate thumbnail generation + let thumbnailRequest = OCResourceRequestItemThumbnail.request(for: item, maximumSize: self.thumbnailSize, scale: 0, waitForConnectivity: true, changeHandler: nil) + + iconView.request = thumbnailRequest + + // Start new thumbnail request + core?.vault.resourceManager?.start(thumbnailRequest) + + if item.isSharedWithUser || item.sharedByUserOrGroup { + sharedStatusIconView.image = UIImage(named: "group") + sharedStatusIconViewRightMarginConstraint?.constant = smallSpacing + sharedStatusIconViewZeroWidthConstraint?.isActive = false + } else { + sharedStatusIconView.image = nil + sharedStatusIconViewRightMarginConstraint?.constant = 0 + sharedStatusIconViewZeroWidthConstraint?.isActive = true + } + sharedStatusIconView.invalidateIntrinsicContentSize() + + if item.sharedByPublicLink { + publicLinkStatusIconView.image = UIImage(named: "link") + publicLinkStatusIconViewRightMarginConstraint?.constant = smallSpacing + publicLinkStatusIconViewZeroWidthConstraint?.isActive = false + } else { + publicLinkStatusIconView.image = nil + publicLinkStatusIconViewRightMarginConstraint?.constant = 0 + publicLinkStatusIconViewZeroWidthConstraint?.isActive = true + } + publicLinkStatusIconView.invalidateIntrinsicContentSize() + + self.updateCloudStatusIcon(with: item) + + self.updateLabels(with: item) + + self.iconView.alpha = item.isPlaceholder ? 0.5 : 1.0 + self.moreButton.isHidden = (item.isPlaceholder || (progressView != nil)) ? true : false + + self.moreButton.accessibilityLabel = "Actions".localized + self.moreButton.accessibilityIdentifier = (item.name != nil) ? (item.name! + " " + "Actions".localized) : "Actions".localized + + self.updateStatus() + } + + open func updateCloudStatusIcon(with item: OCItem?) { + var cloudStatusIcon : UIImage? + var cloudStatusIconAlpha : CGFloat = 1.0 + + if let item = item { + let availableOfflineCoverage : OCCoreAvailableOfflineCoverage = core?.availableOfflinePolicyCoverage(of: item) ?? .none + + switch availableOfflineCoverage { + case .direct, .none: cloudStatusIconAlpha = 1.0 + case .indirect: cloudStatusIconAlpha = 0.5 + } + + if item.type == .file { + switch item.cloudStatus { + case .cloudOnly: + cloudStatusIcon = UIImage(named: "cloud-only") + cloudStatusIconAlpha = 1.0 + + case .localCopy: + cloudStatusIcon = (item.downloadTriggerIdentifier == OCItemDownloadTriggerID.availableOffline) ? UIImage(named: "cloud-available-offline") : nil + + case .locallyModified, .localOnly: + cloudStatusIcon = UIImage(named: "cloud-local-only") + cloudStatusIconAlpha = 1.0 + } + } else { + if availableOfflineCoverage == .none { + cloudStatusIcon = nil + } else { + cloudStatusIcon = UIImage(named: "cloud-available-offline") + } + } + } + + cloudStatusIconView.image = cloudStatusIcon + cloudStatusIconView.alpha = cloudStatusIconAlpha + + cloudStatusIconViewZeroWidthConstraint?.isActive = (cloudStatusIcon == nil) + cloudStatusIconViewRightMarginConstraint?.constant = (cloudStatusIcon == nil) ? 0 : smallSpacing + + cloudStatusIconView.invalidateIntrinsicContentSize() + } + + open func updateLabels(with item: OCItem?) { + self.titleLabel.attributedText = titleLabelString(for: item) + self.detailLabel.text = detailLabelString(for: item) + } + + // MARK: - Available offline tracking + @objc open func updateAvailableOfflineStatus(_ notification: Notification) { + OnMainThread { [weak self] in + self?.updateCloudStatusIcon(with: self?.item) + } + } + + // MARK: - Has Message tracking + @objc open func updateHasMessage(_ notification: Notification) { + if let notificationCore = notification.object as? OCCore, let core = self.core, notificationCore === core { + OnMainThread { [weak self] in + let oldMessageForItem = self?.hasMessageForItem ?? false + + if let item = self?.item, let hasMessage = self?.delegate?.hasMessage(for: item) { + self?.hasMessageForItem = hasMessage + } else { + self?.hasMessageForItem = false + } + + if oldMessageForItem != self?.hasMessageForItem { + self?.updateStatus() + } + } + } + } + + // MARK: - Progress + open var localID : OCLocalID? { + willSet { + if localID != nil { + NotificationCenter.default.removeObserver(self, name: .OCCoreItemChangedProgress, object: nil) + } + } + + didSet { + if localID != nil { + NotificationCenter.default.addObserver(self, selector: #selector(progressChangedForItem(_:)), name: .OCCoreItemChangedProgress, object: nil) + } + } + } + + @objc open func progressChangedForItem(_ notification : Notification) { + if notification.object as? NSString == localID { + OnMainThread { + self.updateStatus() + } + } + } + + open func updateStatus() { + var progress : Progress? + + if let item = item, (item.syncActivity.rawValue & (OCItemSyncActivity.downloading.rawValue | OCItemSyncActivity.uploading.rawValue) != 0), !hasMessageForItem { + progress = self.core?.progress(for: item, matching: .none)?.first + + if progress == nil { + progress = Progress.indeterminate() + } + } + + if progress != nil { + if progressView == nil { + let progressView = ProgressView() + progressView.contentMode = .center + progressView.translatesAutoresizingMaskIntoConstraints = false + + self.contentView.addSubview(progressView) + + NSLayoutConstraint.activate([ + progressView.leftAnchor.constraint(equalTo: moreButton.leftAnchor), + progressView.rightAnchor.constraint(equalTo: moreButton.rightAnchor), + progressView.topAnchor.constraint(equalTo: moreButton.topAnchor), + progressView.bottomAnchor.constraint(equalTo: moreButton.bottomAnchor) + ]) + + self.progressView = progressView + } + + self.progressView?.progress = progress + + moreButton.isHidden = true + messageButton.isHidden = true + } else { + moreButton.isHidden = hasMessageForItem + messageButton.isHidden = !hasMessageForItem + + progressView?.removeFromSuperview() + progressView = nil + } + } + + // MARK: - Themeing + open var revealHighlight : Bool = false { + didSet { + if revealHighlight { + Log.debug("Highlighted!") + } + + //!! applyThemeCollectionToCellContents(theme: Theme.shared, collection: Theme.shared.activeCollection) + } + } + + //!! + /* + override open func applyThemeCollectionToCellContents(theme: Theme, collection: ThemeCollection) { + let itemState = ThemeItemState(selected: self.isSelected) + + titleLabel.applyThemeCollection(collection, itemStyle: .title, itemState: itemState) + detailLabel.applyThemeCollection(collection, itemStyle: .message, itemState: itemState) + + sharedStatusIconView.tintColor = collection.tableRowColors.secondaryLabelColor + publicLinkStatusIconView.tintColor = collection.tableRowColors.secondaryLabelColor + cloudStatusIconView.tintColor = collection.tableRowColors.secondaryLabelColor + detailLabel.textColor = collection.tableRowColors.secondaryLabelColor + + moreButton.tintColor = collection.tableRowColors.secondaryLabelColor + + if revealHighlight { + backgroundColor = collection.tableRowHighlightColors.backgroundColor?.withAlphaComponent(0.5) + } else { + backgroundColor = collection.tableBackgroundColor + } + } + */ + + // MARK: - Editing mode + open func setMoreButton(hidden:Bool, animated: Bool = false) { + if hidden || isMoreButtonPermanentlyHidden { + moreButtonWidthConstraint?.constant = 0 + } else { + moreButtonWidthConstraint?.constant = showRevealButton ? revealButtonWidth : moreButtonWidth + } + moreButton.isHidden = ((item?.isPlaceholder == true) || (progressView != nil)) ? true : hidden + if animated { + UIView.animate(withDuration: 0.25) { + self.contentView.layoutIfNeeded() + } + } else { + self.contentView.layoutIfNeeded() + } + } + + var showRevealButton : Bool = false { + didSet { + if showRevealButton != oldValue { + self.setRevealButton(hidden: !showRevealButton, animated: false) + } + } + } + + open func setRevealButton(hidden:Bool, animated: Bool = false) { + if hidden { + revealButtonWidthConstraint?.constant = 0 + } else { + revealButtonWidthConstraint?.constant = revealButtonWidth + } + revealButton.isHidden = hidden + if animated { + UIView.animate(withDuration: 0.25) { + self.contentView.layoutIfNeeded() + } + } else { + self.contentView.layoutIfNeeded() + } + } + + //!! + /* + override open func setEditing(_ editing: Bool, animated: Bool) { + super.setEditing(editing, animated: animated) + + setMoreButton(hidden: editing, animated: animated) + setRevealButton(hidden: editing ? true : !showRevealButton, animated: animated) + } + */ + + // MARK: - Actions + @objc open func moreButtonTapped() { + self.delegate?.moreButtonTapped(cell: self) + } + @objc open func messageButtonTapped() { + self.delegate?.messageButtonTapped(cell: self) + } + @objc open func revealButtonTapped() { + self.delegate?.revealButtonTapped(cell: self) + } +} + +extension ItemListCell { + static func registerCellProvider() { + let itemListCellRegistration = UICollectionView.CellRegistration { (cell, indexPath, collectionItemRef) in + if let cellConfiguration = collectionItemRef.ocCellConfiguration { + var itemRecord = cellConfiguration.record + + cell.delegate = cellConfiguration.hostViewController as? ItemListCellDelegate + cell.core = cellConfiguration.core + + if itemRecord == nil { + if let collectionViewController = cellConfiguration.hostViewController { + let (itemRef, _) = collectionViewController.unwrap(collectionItemRef) + + if let retrievedItemRecord = try? cellConfiguration.source?.record(forItemRef: itemRef) { + itemRecord = retrievedItemRecord + } + } + } + + if let itemRecord = itemRecord { + if let item = itemRecord.item { + if let ocItem = item as? OCItem { + cell.updateWith(ocItem) + } + } else { + // Request reconfiguration of cell + itemRecord.retrieveItem(completionHandler: { error, itemRecord in + if let collectionViewController = cellConfiguration.hostViewController { + collectionViewController.collectionViewDataSource.requestReconfigurationOfItems([collectionItemRef]) + } + }) + } + } + } + } + + CollectionViewCellProvider.register(CollectionViewCellProvider(for: .item, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in + return collectionView.dequeueConfiguredReusableCell(using: itemListCellRegistration, for: indexPath, item: itemRef) + })) + } +} diff --git a/ownCloudAppShared/Client/File Lists/CollectionViewCellConfiguration.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift similarity index 56% rename from ownCloudAppShared/Client/File Lists/CollectionViewCellConfiguration.swift rename to ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift index b65860a18..4736c8ed4 100644 --- a/ownCloudAppShared/Client/File Lists/CollectionViewCellConfiguration.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift @@ -6,10 +6,27 @@ // Copyright © 2022 ownCloud GmbH. All rights reserved. // +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + import UIKit import ownCloudSDK +public enum CollectionViewCellStyle : CaseIterable { + case regular + case header + case footer +} + public class CollectionViewCellConfiguration: NSObject { + public weak var core: OCCore? public weak var source: OCDataSource? public var collectionItemRef: CollectionViewController.ItemRef? @@ -17,10 +34,15 @@ public class CollectionViewCellConfiguration: NSObject { public weak var hostViewController: CollectionViewController? - public init(source: OCDataSource? = nil, collectionItemRef: CollectionViewController.ItemRef? = nil, record: OCDataItemRecord? = nil, hostViewController: CollectionViewController?) { + public var style : CollectionViewCellStyle + + public init(source: OCDataSource? = nil, core: OCCore? = nil, collectionItemRef: CollectionViewController.ItemRef? = nil, record: OCDataItemRecord? = nil, hostViewController: CollectionViewController?, style: CollectionViewCellStyle = .regular) { + self.style = style + super.init() self.source = source + self.core = core self.collectionItemRef = collectionItemRef self.record = record self.hostViewController = hostViewController diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewCellProvider+StandardImplementations.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewCellProvider+StandardImplementations.swift new file mode 100644 index 000000000..5f8cfa0f4 --- /dev/null +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewCellProvider+StandardImplementations.swift @@ -0,0 +1,105 @@ +// +// CollectionViewCellProvider+StandardImplementations.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 08.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK + +public extension CollectionViewCellProvider { + static func registerStandardImplementations() { + // Register cell providers for .drive and .presentable + DriveListCell.registerCellProvider() + ItemListCell.registerCellProvider() + ExpandableResourceCell.registerCellProvider() + + registerPresentableCellProvider() + } + + static func registerPresentableCellProvider() { + let presentableCellRegistration = UICollectionView.CellRegistration { (cell, indexPath, collectionItemRef) in + var content = cell.defaultContentConfiguration() + + if let cellConfiguration = collectionItemRef.ocCellConfiguration { + var itemRecord = cellConfiguration.record + + if itemRecord == nil { + if let collectionViewController = cellConfiguration.hostViewController { + let (itemRef, _) = collectionViewController.unwrap(collectionItemRef) + + if let retrievedItemRecord = try? cellConfiguration.source?.record(forItemRef: itemRef) { + itemRecord = retrievedItemRecord + } + } + } + + if let itemRecord = itemRecord { + if let item = itemRecord.item { + if let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil, withOptions: nil) as? OCDataItemPresentable { + content.text = presentable.title + content.secondaryText = presentable.subtitle + + let coverImageRequest = try? presentable.provideResourceRequest(.coverImage, withOptions: nil) + let readmeRequest = try? presentable.provideResourceRequest(.coverDescription, withOptions: nil) + + coverImageRequest?.changeHandler = { (request, error, isOngoing, previousResource, newResource) in + Log.debug("REQ_Cover image request: \(String(describing: request)) | error: \(String(describing: error)) | isOngoing: \(isOngoing) | newResource: \(String(describing: newResource))") + if let imageResource = newResource as? OCResourceImage { + imageResource.image?.request(completionHandler: { ocImage, error, image in + Log.debug("REQ_Cover image: \(String(describing: image))") + }) + } + } + + readmeRequest?.changeHandler = { (request, error, isOngoing, previousResource, newResource) in + Log.debug("REQ_Readme request: \(String(describing: request)) | error: \(String(describing: error)) | isOngoing: \(isOngoing) | newResource: \(String(describing: newResource))") + if let textResource = newResource as? OCResourceText { + Log.debug("REQ_Readme text: \(String(describing: textResource.text))") + } + } + + if let coverImageRequest = coverImageRequest { + cellConfiguration.core?.vault.resourceManager?.start(coverImageRequest) + } + + if let readmeRequest = readmeRequest { + cellConfiguration.core?.vault.resourceManager?.start(readmeRequest) + } + } + } else { + // Request reconfiguration of cell + itemRecord.retrieveItem(completionHandler: { error, itemRecord in + if let collectionViewController = cellConfiguration.hostViewController { + collectionViewController.collectionViewDataSource.requestReconfigurationOfItems([collectionItemRef]) + } + }) + } + } + } + + cell.contentConfiguration = content + cell.accessories = [ .disclosureIndicator() ] + } + + CollectionViewCellProvider.register(CollectionViewCellProvider(for: .presentable, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in + return collectionView.dequeueConfiguredReusableCell(using: presentableCellRegistration, for: indexPath, item: itemRef) + })) +// +// CollectionViewCellProvider.register(CollectionViewCellProvider(for: .item, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in +// return collectionView.dequeueConfiguredReusableCell(using: presentableCellRegistration, for: indexPath, item: itemRef) +// })) + } +} diff --git a/ownCloudAppShared/Client/File Lists/CollectionViewCellProvider.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewCellProvider.swift similarity index 100% rename from ownCloudAppShared/Client/File Lists/CollectionViewCellProvider.swift rename to ownCloudAppShared/Client/Collection Views/CollectionViewCellProvider.swift diff --git a/ownCloudAppShared/Client/File Lists/CollectionViewController.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift similarity index 76% rename from ownCloudAppShared/Client/File Lists/CollectionViewController.swift rename to ownCloudAppShared/Client/Collection Views/CollectionViewController.swift index 40ef13444..7d759069a 100644 --- a/ownCloudAppShared/Client/File Lists/CollectionViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift @@ -26,8 +26,9 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat public var supportsHierarchicContent: Bool - public init(core inCore: OCCore?, rootViewController inRootViewController: UIViewController?, sections inSections: [CollectionViewSection]?, hierarchic: Bool = false) { + public init(core inCore: OCCore?, rootViewController inRootViewController: UIViewController?, sections inSections: [CollectionViewSection]?, hierarchic: Bool = false, listAppearance inListAppearance: UICollectionLayoutListConfiguration.Appearance = .insetGrouped) { supportsHierarchicContent = hierarchic + listAppearance = inListAppearance super.init(nibName: nil, bundle: nil) @@ -52,18 +53,20 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat var collectionView : UICollectionView! = nil var collectionViewDataSource: UICollectionViewDiffableDataSource! = nil + public var listAppearance : UICollectionLayoutListConfiguration.Appearance + public override func viewDidLoad() { super.viewDidLoad() configureViews() configureDataSource() } - func createCollectionViewLayout() -> UICollectionViewLayout { - let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped) + public func createCollectionViewLayout() -> UICollectionViewLayout { + let config = UICollectionLayoutListConfiguration(appearance: listAppearance) return UICollectionViewCompositionalLayout.list(using: config) } - func configureViews() { + public func configureViews() { collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createCollectionViewLayout()) collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] view.addSubview(collectionView) @@ -71,7 +74,7 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat } // MARK: - Collection View Datasource - func configureDataSource() { + public func configureDataSource() { collectionViewDataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { [weak self] (collectionView: UICollectionView, indexPath: IndexPath, collectionItemRef: CollectionViewController.ItemRef) -> UICollectionViewCell? in if let sectionIdentifier = self?.collectionViewDataSource.sectionIdentifier(for: indexPath.section), let section = self?.sectionsByID[sectionIdentifier] { @@ -194,17 +197,44 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat let (itemRef, _) = unwrap(collectionItemRef) dataSource.retrieveItem(forRef: itemRef, reusing: nil, completionHandler: { [weak self] (error, record) in - if let drive = record?.item as? OCDrive { - if let core = self?.core, let rootViewController = self?.rootViewController { - let query = OCQuery(for: drive.rootLocation) - let rootFolderViewController = ClientQueryViewController(core: core, drive: drive, query: query, rootViewController: rootViewController) - - collectionView.deselectItem(at: indexPath, animated: true) + guard let record = record else { return } - self?.navigationController?.pushViewController(rootFolderViewController, animated: true) - } - } + _ = self?.handleSelection(of: record, at: indexPath) }) } } + + public func handleSelection(of record: OCDataItemRecord, at indexPath: IndexPath) -> Bool { + if let core = self.core, let rootViewController = self.rootViewController { + if let drive = record.item as? OCDrive { + let query = OCQuery(for: drive.rootLocation) + let rootFolderViewController = ClientItemViewController(core: core, drive: drive, query: query, rootViewController: rootViewController) + + collectionView.deselectItem(at: indexPath, animated: true) + + self.navigationController?.pushViewController(rootFolderViewController, animated: true) + + return true + } + } + + return false + } +} + +public extension CollectionViewController { + func relayout(cell: UICollectionViewCell) { +// collectionView.setCollectionViewLayout(collectionView.collectionViewLayout, animated: true, completion: nil) + + collectionViewDataSource.apply(collectionViewDataSource.snapshot(), animatingDifferences: true) + +// collectionView.setNeedsLayout() +// collectionView.layoutIfNeeded() + +// if let indexPath = collectionView.indexPath(for: cell) { +// let invalidationContext = UICollectionViewLayoutInvalidationContext() +// invalidationContext.invalidateItems(at: collectionView.indexPathsForVisibleItems) +// collectionView.collectionViewLayout.invalidateLayout(with: invalidationContext) +// } + } } diff --git a/ownCloudAppShared/Client/File Lists/CollectionViewSection.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift similarity index 78% rename from ownCloudAppShared/Client/File Lists/CollectionViewSection.swift rename to ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift index 8ffdd889c..1c704ee5b 100644 --- a/ownCloudAppShared/Client/File Lists/CollectionViewSection.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift @@ -21,6 +21,7 @@ import ownCloudSDK public class CollectionViewSection: NSObject { public typealias SectionIdentifier = String + public typealias CellConfigurationCustomizer = (_ collectionView: UICollectionView, _ cellConfiguration: CollectionViewCellConfiguration, _ itemRecord: OCDataItemRecord, _ collectionItemRef: CollectionViewController.ItemRef, _ indexPath: IndexPath) -> Void public var identifier: SectionIdentifier @@ -38,6 +39,9 @@ public class CollectionViewSection: NSObject { weak public var collectionViewController : CollectionViewController? + public var cellStyle : CollectionViewCellStyle //!< Use .cellConfigurationCustomizer for per-cell styling + public var cellConfigurationCustomizer : CellConfigurationCustomizer? + func updateDatasourceSubscription() { if let dataSource = dataSource { dataSourceSubscription = dataSource.subscribe(updateHandler: { [weak self] (subscription) in @@ -46,8 +50,10 @@ public class CollectionViewSection: NSObject { } } - public init(identifier: SectionIdentifier, dataSource inDataSource: OCDataSource?) { + public init(identifier: SectionIdentifier, dataSource inDataSource: OCDataSource?, cellStyle : CollectionViewCellStyle = .regular ) { self.identifier = identifier + self.cellStyle = cellStyle + super.init() self.dataSource = inDataSource @@ -87,7 +93,11 @@ public class CollectionViewSection: NSObject { } if let cellProvider = cellProvider, let dataSource = dataSource { - let cellConfiguration = CollectionViewCellConfiguration(source: dataSource, collectionItemRef: collectionItemRef, record: itemRecord, hostViewController: collectionViewController) + let cellConfiguration = CollectionViewCellConfiguration(source: dataSource, core: collectionViewController?.core, collectionItemRef: collectionItemRef, record: itemRecord, hostViewController: collectionViewController, style: cellStyle) + + if let cellConfigurationCustomizer = cellConfigurationCustomizer { + cellConfigurationCustomizer(collectionView, cellConfiguration, itemRecord, collectionItemRef, indexPath) + } cell = cellProvider.provideCell(for: collectionView, cellConfiguration: cellConfiguration, itemRecord: itemRecord, collectionItemRef: collectionItemRef, indexPath: indexPath) } diff --git a/ownCloudAppShared/Client/File Lists/CollectionViewCellProvider+StandardImplementations.swift b/ownCloudAppShared/Client/File Lists/CollectionViewCellProvider+StandardImplementations.swift deleted file mode 100644 index ec47107ff..000000000 --- a/ownCloudAppShared/Client/File Lists/CollectionViewCellProvider+StandardImplementations.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -// CollectionViewCellProvider+StandardImplementations.swift -// ownCloudAppShared -// -// Created by Felix Schwarz on 08.04.22. -// Copyright © 2022 ownCloud GmbH. All rights reserved. -// - -/* - * Copyright (C) 2022, ownCloud GmbH. - * - * This code is covered by the GNU Public License Version 3. - * - * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ - * You should have received a copy of this license along with this program. If not, see . - * - */ - -import UIKit -import ownCloudSDK - -public extension CollectionViewCellProvider { - static func registerStandardImplementations() { - // Register cell providers for .drive and .presentable - let cellRegistration = UICollectionView.CellRegistration { (cell, indexPath, collectionItemRef) in - var content = cell.defaultContentConfiguration() - - if let cellConfiguration = collectionItemRef.ocCellConfiguration { - var itemRecord = cellConfiguration.record - - if itemRecord == nil { - if let collectionViewController = cellConfiguration.hostViewController { - let (itemRef, _) = collectionViewController.unwrap(collectionItemRef) - - if let retrievedItemRecord = try? cellConfiguration.source?.record(forItemRef: itemRef) { - itemRecord = retrievedItemRecord - } - } - } - - if let itemRecord = itemRecord { - if let item = itemRecord.item { - if let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil, withOptions: nil) as? OCDataItemPresentable { - content.text = presentable.title - content.secondaryText = presentable.subtitle - -// presentable.requestResource(.coverImage, withOptions: [.core : core], completionHandler: { error, resource in -// }) - } - } else { - // Request reconfiguration of cell - itemRecord.retrieveItem(completionHandler: { error, itemRecord in - if let collectionViewController = cellConfiguration.hostViewController { - collectionViewController.collectionViewDataSource.requestReconfigurationOfItems([collectionItemRef]) - } - }) - } - } - } - - cell.contentConfiguration = content - cell.accessories = [ .disclosureIndicator() ] - } - - CollectionViewCellProvider.register(CollectionViewCellProvider(for: .drive, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in - return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemRef) - })) - - CollectionViewCellProvider.register(CollectionViewCellProvider(for: .presentable, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in - return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemRef) - })) - } -} diff --git a/ownCloudAppShared/Client/Resource Sources/ResourceSourceItemIcons.swift b/ownCloudAppShared/Client/Resource Sources/ResourceSourceItemIcons.swift index 5a4b58fed..f92bdbbf3 100644 --- a/ownCloudAppShared/Client/Resource Sources/ResourceSourceItemIcons.swift +++ b/ownCloudAppShared/Client/Resource Sources/ResourceSourceItemIcons.swift @@ -44,7 +44,7 @@ public class ResourceSourceItemIcons: OCResourceSource { let resource = ResourceItemIcon(request: request) resource.iconName = iconName - resource.mimeType = OCResourceMIMEType(rawValue: "image/tvg") + resource.mimeType = "image/tvg" resource.quality = .fallback resultHandler(nil, resource) diff --git a/ownCloudAppShared/Client/User Interface/ClientItemCell.swift b/ownCloudAppShared/Client/User Interface/ClientItemCell.swift index a4242198b..02e205834 100644 --- a/ownCloudAppShared/Client/User Interface/ClientItemCell.swift +++ b/ownCloudAppShared/Client/User Interface/ClientItemCell.swift @@ -20,29 +20,6 @@ import UIKit import ownCloudSDK import ownCloudApp -extension NSMutableAttributedString { - var boldFont:UIFont { return UIFont.preferredFont(forTextStyle: .headline) } - var normalFont:UIFont { return UIFont.preferredFont(forTextStyle: .subheadline) } - - func appendBold(_ value:String) -> NSMutableAttributedString { - let attributes:[NSAttributedString.Key : Any] = [ - .font : boldFont - ] - - self.append(NSAttributedString(string: value, attributes:attributes)) - return self - } - - func appendNormal(_ value:String) -> NSMutableAttributedString { - let attributes:[NSAttributedString.Key : Any] = [ - .font : normalFont - ] - - self.append(NSAttributedString(string: value, attributes:attributes)) - return self - } -} - public protocol ClientItemCellDelegate: class { func moreButtonTapped(cell: ClientItemCell) diff --git a/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift b/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift new file mode 100644 index 000000000..0a4926cf6 --- /dev/null +++ b/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift @@ -0,0 +1,167 @@ +// +// ClientItemViewController.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 14.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK + +public class ClientItemViewController: CollectionViewController { + + public weak var drive: OCDrive? + public var query: OCQuery? + + public var queryItemDataSourceSection : CollectionViewSection? + + public var driveSection : CollectionViewSection? + + public var driveSectionDataSource : OCDataSourceComposition? + public var singleDriveDatasource : OCDataSourceComposition? + private var singleDriveDatasourceSubscription : OCDataSourceSubscription? + public var driveAdditionalItemsDataSource : OCDataSourceArray = OCDataSourceArray() + + public init(core inCore: OCCore, drive inDrive: OCDrive?, query inQuery: OCQuery, reveal inItem: OCItem? = nil, rootViewController: UIViewController?) { + drive = inDrive + query = inQuery + + var sections : [ CollectionViewSection ] = [] + + if let queryDatasource = query?.queryResultsDataSource { + singleDriveDatasource = OCDataSourceComposition(sources: [inCore.drivesDataSource]) + + if query?.queryLocation?.isRoot == true { + // Create data source from one drive + singleDriveDatasource?.filter = OCDataSourceComposition.itemFilter(withItemRetrieval: false, fromRecordFilter: { itemRecord in + if let drive = itemRecord?.item as? OCDrive { + if drive.identifier == inDrive?.identifier { + return true + } + } + + return false + }) + + // Create combined data source from drive + additional items + driveSectionDataSource = OCDataSourceComposition(sources: [ singleDriveDatasource!, driveAdditionalItemsDataSource ]) + + // Create drive section from combined data source + driveSection = CollectionViewSection(identifier: "drive", dataSource: driveSectionDataSource, cellStyle: .header) + } + + queryItemDataSourceSection = CollectionViewSection(identifier: "items", dataSource: queryDatasource) + + if let driveSection = driveSection { + sections.append(driveSection) + } + + if let queryItemDataSourceSection = queryItemDataSourceSection { + sections.append(queryItemDataSourceSection) + } + } + + super.init(core: inCore, rootViewController: rootViewController, sections: sections, listAppearance: .plain) + + // Subscribe to singleDriveDatasource for changes, to update driveSectionDataSource + singleDriveDatasourceSubscription = singleDriveDatasource?.subscribe(updateHandler: { [weak self] subscription in + self?.updateAdditionalDriveItems(from: subscription) + }, on: .main, trackDifferences: true, performIntialUpdate: true) + + query?.sortComparator = SortMethod.alphabetically.comparator(direction: .ascendant) + + if let navigationTitle = query?.queryLocation?.isRoot == true ? drive?.name : query?.queryLocation?.lastPathComponent { + navigationItem.title = navigationTitle + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + singleDriveDatasourceSubscription?.terminate() + } + + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if let query = query { + core?.start(query) + } + } + + open override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + if let query = query { + core?.stop(query) + } + } + + public override func handleSelection(of record: OCDataItemRecord, at indexPath: IndexPath) -> Bool { + if let core = self.core, let rootViewController = self.rootViewController { + if let item = record.item as? OCItem, let location = item.location { + let query = OCQuery(for: location) + let rootFolderViewController = ClientItemViewController(core: core, drive: drive, query: query, rootViewController: rootViewController) + + collectionView.deselectItem(at: indexPath, animated: true) + + self.navigationController?.pushViewController(rootFolderViewController, animated: true) + + return true + } + } + + return super.handleSelection(of: record, at: indexPath) + } + + public func updateAdditionalDriveItems(from subscription: OCDataSourceSubscription) { + let snapshot = subscription.snapshotResettingChangeTracking(true) + + if let firstItemRef = snapshot.items.first, + let itemRecord = try? subscription.source?.record(forItemRef: firstItemRef), + let drive = itemRecord?.item as? OCDrive, + let driveRepresentation = OCDataRenderer.default.renderItem(drive, asType: .presentable, error: nil) as? OCDataItemPresentable, + let descriptionResourceRequest = try? driveRepresentation.provideResourceRequest(.coverDescription) { + descriptionResourceRequest.lifetime = .singleRun + descriptionResourceRequest.changeHandler = { [weak self] (request, error, isOngoing, previousResource, newResource) in + // Log.debug("REQ_Readme request: \(String(describing: request)) | error: \(String(describing: error)) | isOngoing: \(isOngoing) | newResource: \(String(describing: newResource))") + if let textResource = newResource as? OCResourceText { + self?.driveAdditionalItemsDataSource.setItems([textResource], updated: [textResource]) + } + } + + core?.vault.resourceManager?.start(descriptionResourceRequest) + } + } +} + +extension ClientItemViewController: ItemListCellDelegate { + public func moreButtonTapped(cell: ItemListCell) { + + } + + public func messageButtonTapped(cell: ItemListCell) { + + } + + public func revealButtonTapped(cell: ItemListCell) { + + } + + public func hasMessage(for item: OCItem) -> Bool { + return false + } +} diff --git a/ownCloudAppShared/Down.LICENSE b/ownCloudAppShared/Down.LICENSE new file mode 100644 index 000000000..2e9e440bf --- /dev/null +++ b/ownCloudAppShared/Down.LICENSE @@ -0,0 +1,218 @@ +The MIT License (MIT) + +Copyright (c) 2016 Rob Phillips. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +----- + +cmark + +Copyright (c) 2014, John MacFarlane + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----- + +houdini.h, houdini_href_e.c, houdini_html_e.c, houdini_html_u.c, +html_unescape.gperf, html_unescape.h + +derive from https://github.com/vmg/houdini (with some modifications) + +Copyright (C) 2012 Vicent Martí + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +----- + +buffer.h, buffer.c, chunk.h + +are derived from code (C) 2012 Github, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +----- + +utf8.c and utf8.c + +are derived from utf8proc +(), +(C) 2009 Public Software Group e. V., Berlin, Germany. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +----- + +The normalization code in runtests.py was derived from the +markdowntest project, Copyright 2013 Karl Dubost: + +The MIT License (MIT) + +Copyright (c) 2013 Karl Dubost + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +----- + +The CommonMark spec (test/spec.txt) is + +Copyright (C) 2014-15 John MacFarlane + +Released under the Creative Commons CC-BY-SA 4.0 license: +. + +----- + +The test software in test/ is + +Copyright (c) 2014, John MacFarlane + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----- + +The normalization code in runtests.py was derived from the +markdowntest project, Copyright 2013 Karl Dubost: + +The MIT License (MIT) + +Copyright (c) 2013 Karl Dubost + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/ownCloudAppShared/UIKit Extension/NSMutableAttributedString+AppendStyled.swift b/ownCloudAppShared/UIKit Extension/NSMutableAttributedString+AppendStyled.swift new file mode 100644 index 000000000..5e9e89884 --- /dev/null +++ b/ownCloudAppShared/UIKit Extension/NSMutableAttributedString+AppendStyled.swift @@ -0,0 +1,42 @@ +// +// NSMutableAttributedString+AppendStyled.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 20.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit + +public extension NSMutableAttributedString { + var boldFont: UIFont { return UIFont.preferredFont(forTextStyle: .headline) } + var normalFont: UIFont { return UIFont.preferredFont(forTextStyle: .subheadline) } + + func appendBold(_ value:String) -> NSMutableAttributedString { + let attributes:[NSAttributedString.Key : Any] = [ + .font : boldFont + ] + + self.append(NSAttributedString(string: value, attributes:attributes)) + return self + } + + func appendNormal(_ value:String) -> NSMutableAttributedString { + let attributes:[NSAttributedString.Key : Any] = [ + .font : normalFont + ] + + self.append(NSAttributedString(string: value, attributes:attributes)) + return self + } +} diff --git a/ownCloudAppShared/UIKit Extension/UICollectionViewDiffableDataSource+Tools.swift b/ownCloudAppShared/UIKit Extension/UICollectionViewDiffableDataSource+Tools.swift index f02e8361d..f13304196 100644 --- a/ownCloudAppShared/UIKit Extension/UICollectionViewDiffableDataSource+Tools.swift +++ b/ownCloudAppShared/UIKit Extension/UICollectionViewDiffableDataSource+Tools.swift @@ -12,6 +12,6 @@ public extension UICollectionViewDiffableDataSource { func requestReconfigurationOfItems(_ items: [ItemIdentifierType], animated: Bool = true) { var snapshot = snapshot() snapshot.reconfigureItems(items) - apply(snapshot, animatingDifferences: true) + apply(snapshot, animatingDifferences: animated) } } diff --git a/ownCloudAppShared/UIKit Extension/UIFont+Weight.swift b/ownCloudAppShared/UIKit Extension/UIFont+Weight.swift new file mode 100644 index 000000000..baedc1fc2 --- /dev/null +++ b/ownCloudAppShared/UIKit Extension/UIFont+Weight.swift @@ -0,0 +1,29 @@ +// +// UIFont+Weight.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 20.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit + +public extension UIFont { + static func preferredFont(forTextStyle textStyle: UIFont.TextStyle, with weight: UIFont.Weight) -> UIFont { + let fontDescriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: textStyle) + let font = UIFont.systemFont(ofSize: fontDescriptor.pointSize, weight: weight) + let fontMetrics = UIFontMetrics(forTextStyle: textStyle) + + return fontMetrics.scaledFont(for: font) + } +} diff --git a/ownCloudAppShared/UIKit Extension/UILabel+Extension.swift b/ownCloudAppShared/UIKit Extension/UILabel+Extension.swift new file mode 100644 index 000000000..ede8f1c7d --- /dev/null +++ b/ownCloudAppShared/UIKit Extension/UILabel+Extension.swift @@ -0,0 +1,28 @@ +// +// UILabel+Extension.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 20.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit + +public extension UILabel { + func makeLabelWrapText() { + setContentHuggingPriority(.required, for: .vertical) + setContentCompressionResistancePriority(.required, for: .vertical) + numberOfLines = 0 + lineBreakMode = .byWordWrapping + } +} diff --git a/ownCloudAppShared/User Interface/Theme/Theme.swift b/ownCloudAppShared/User Interface/Theme/Theme.swift index 0146866bd..e77e46de2 100644 --- a/ownCloudAppShared/User Interface/Theme/Theme.swift +++ b/ownCloudAppShared/User Interface/Theme/Theme.swift @@ -64,6 +64,7 @@ public class Theme: NSObject { let sharedInstance = Theme() OCExtensionManager.shared.addExtension(OCExtension.license(withIdentifier: "license.PocketSVG", bundleOf: Theme.self, title: "PocketSVG", resourceName: "PocketSVG", fileExtension: "LICENSE")) + OCExtensionManager.shared.addExtension(OCExtension.license(withIdentifier: "license.Down", bundleOf: Theme.self, title: "Down", resourceName: "Down", fileExtension: "LICENSE")) return sharedInstance }() diff --git a/ownCloudAppShared/User Interface/Theme/UI/ThemeTableViewCell.swift b/ownCloudAppShared/User Interface/Theme/UI/ThemeTableViewCell.swift index 9fc77daf5..1211992e0 100644 --- a/ownCloudAppShared/User Interface/Theme/UI/ThemeTableViewCell.swift +++ b/ownCloudAppShared/User Interface/Theme/UI/ThemeTableViewCell.swift @@ -121,10 +121,7 @@ open class ThemeTableViewCell: UITableViewCell, Themeable { var replacementDetailLabel : UILabel? replacementTextLabel.translatesAutoresizingMaskIntoConstraints = false - replacementTextLabel.setContentHuggingPriority(.required, for: .vertical) - replacementTextLabel.setContentCompressionResistancePriority(.required, for: .vertical) - replacementTextLabel.numberOfLines = 0 - replacementTextLabel.lineBreakMode = .byWordWrapping + replacementTextLabel.makeLabelWrapText() replacementTextLabel.font = (style == .subtitle) ? UIFont.systemFont(ofSize: UIFont.systemFontSize) : @@ -137,10 +134,7 @@ open class ThemeTableViewCell: UITableViewCell, Themeable { replacementDetailLabel = UILabel() replacementDetailLabel?.translatesAutoresizingMaskIntoConstraints = false - replacementDetailLabel?.setContentHuggingPriority(.required, for: .vertical) - replacementDetailLabel?.setContentCompressionResistancePriority(.required, for: .vertical) - replacementDetailLabel?.numberOfLines = 0 - replacementDetailLabel?.lineBreakMode = .byWordWrapping + replacementDetailLabel?.makeLabelWrapText() replacementDetailLabel?.font = UIFont.systemFont(ofSize: UIFont.smallSystemFontSize) diff --git a/ownCloudAppShared/View Providers/OCResourceText+ViewProvider.swift b/ownCloudAppShared/View Providers/OCResourceText+ViewProvider.swift new file mode 100644 index 000000000..1a92619ba --- /dev/null +++ b/ownCloudAppShared/View Providers/OCResourceText+ViewProvider.swift @@ -0,0 +1,62 @@ +// +// OCResourceText+ViewProvider.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 20.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK +import Down + +extension OCResourceText : OCViewProvider { + public func provideView(for size: CGSize, in context: OCViewProviderContext?, completion completionHandler: @escaping (UIView?) -> Void) { + var attributedText : NSAttributedString? + + if let mimeType = mimeType { + switch mimeType { + case "text/markdown": + // Render mark down + if let text = text?.trimmingCharacters(in: .whitespacesAndNewlines) { + let down = Down(markdownString: text) + let styler = DownStyler() + + if let attributedString = try? down.toAttributedString(.default, styler: styler) { + attributedText = attributedString + } + } + + default: break + } + } + + let textView = UITextView() + + textView.translatesAutoresizingMaskIntoConstraints = false + + textView.isEditable = false + textView.isScrollEnabled = false + + if let attributedText = attributedText { + textView.attributedText = attributedText + } else if let text = text { + textView.text = text + textView.font = UIFont.preferredFont(forTextStyle: .body) + } + + textView.setContentCompressionResistancePriority(.required, for: .vertical) + + completionHandler(textView) + } +} From 4e141554e99619dc25c6a8aa4eab7c7689aa56f8 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Thu, 21 Apr 2022 21:36:33 +0200 Subject: [PATCH 021/328] - ClientItemViewController+ItemActions: provide standard implementations for MoreItemHandling and OpenItemHandling for ClientItemViewController - CollectionViewCellConfiguration: add new item style passing along ItemCellActionHandlers - ItemCellActionHandlers: new class to encapsulate the different handlers for an item - ClientItemViewController: provide ItemCellActionHandlers and make the most used item actions available --- ios-sdk | 2 +- ownCloud.xcodeproj/project.pbxproj | 4 ++ ...ClientItemViewController+ItemActions.swift | 71 +++++++++++++++++++ .../Cells/DriveListCell.swift | 10 +-- .../Collection Views/Cells/ItemListCell.swift | 44 ++++++++---- .../CollectionViewCellConfiguration.swift | 3 +- .../FileListTableViewController.swift | 8 +-- .../ClientItemViewController.swift | 56 +++++++++++---- 8 files changed, 161 insertions(+), 37 deletions(-) create mode 100644 ownCloud/Client/ClientItemViewController+ItemActions.swift diff --git a/ios-sdk b/ios-sdk index 8dfb50e35..358c8f93b 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit 8dfb50e3565c4421129b4623ce4b26e7623d6b11 +Subproject commit 358c8f93baa7676b0f7a85032f6c6e09d0a0bfbd diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 63771aae0..a6dd0f9c1 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -427,6 +427,7 @@ DCD8109B23984AF6003B0053 /* OCLicenseDuration.m in Sources */ = {isa = PBXBuildFile; fileRef = DCD810932398492C003B0053 /* OCLicenseDuration.m */; }; DCD863FB28115C8700CA6631 /* Down.LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = DCD863FA28115C8700CA6631 /* Down.LICENSE */; }; DCD8640628115FD600CA6631 /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = DCD8640528115FD600CA6631 /* OpenSSL */; }; + DCD864102811821200CA6631 /* ClientItemViewController+ItemActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCD8640F2811821200CA6631 /* ClientItemViewController+ItemActions.swift */; }; DCD954DF247D62FA00E184E6 /* MessageTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCD954DE247D62FA00E184E6 /* MessageTableViewController.swift */; }; DCD9B87B2379612B00691929 /* OCLicenseManager+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = DCD9B873237960E600691929 /* OCLicenseManager+Internal.h */; }; DCDBB60A2525305600FAD707 /* NotificationAuthErrorForwarder.h in Headers */ = {isa = PBXBuildFile; fileRef = DCDBB60225252FDA00FAD707 /* NotificationAuthErrorForwarder.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1425,6 +1426,7 @@ DCD810922398492C003B0053 /* OCLicenseDuration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCLicenseDuration.h; sourceTree = ""; }; DCD810932398492C003B0053 /* OCLicenseDuration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCLicenseDuration.m; sourceTree = ""; }; DCD863FA28115C8700CA6631 /* Down.LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = Down.LICENSE; sourceTree = ""; }; + DCD8640F2811821200CA6631 /* ClientItemViewController+ItemActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ClientItemViewController+ItemActions.swift"; sourceTree = ""; }; DCD954DE247D62FA00E184E6 /* MessageTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageTableViewController.swift; sourceTree = ""; }; DCD9B873237960E600691929 /* OCLicenseManager+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCLicenseManager+Internal.h"; sourceTree = ""; }; DCDBB60225252FDA00FAD707 /* NotificationAuthErrorForwarder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationAuthErrorForwarder.h; sourceTree = ""; }; @@ -2416,6 +2418,7 @@ 4C1561E7222321E0009C4EF3 /* PhotoSelectionViewController.swift */, 4C1561EE22232357009C4EF3 /* PhotoSelectionViewCell.swift */, DC6CC3142642C3560040ECAC /* ExternalBrowserBusyHandler.swift */, + DCD8640F2811821200CA6631 /* ClientItemViewController+ItemActions.swift */, ); path = Client; sourceTree = ""; @@ -4150,6 +4153,7 @@ 39E42D1C2315288B00B82AC3 /* KeyCommands.swift in Sources */, 23D5241521491C670002C566 /* DisplayViewController.swift in Sources */, DCFB74C221AD5D10005796AF /* StaticLoginSetupViewController.swift in Sources */, + DCD864102811821200CA6631 /* ClientItemViewController+ItemActions.swift in Sources */, DC0B379420514E4700189B9A /* ServerListBookmarkCell.swift in Sources */, 397E276A23D04D7100117B07 /* StaticLoginSingleAccountServerListViewController.swift in Sources */, DCE20272249AB50E0015A22A /* OCMessage+Extension.swift in Sources */, diff --git a/ownCloud/Client/ClientItemViewController+ItemActions.swift b/ownCloud/Client/ClientItemViewController+ItemActions.swift new file mode 100644 index 000000000..2f87da6b0 --- /dev/null +++ b/ownCloud/Client/ClientItemViewController+ItemActions.swift @@ -0,0 +1,71 @@ +// +// ClientItemViewController+ItemActions.swift +// ownCloud +// +// Created by Felix Schwarz on 21.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK +import ownCloudAppShared + +extension ClientItemViewController : MoreItemHandling { + public func moreOptions(for item: OCItem, at locationIdentifier: OCExtensionLocationIdentifier, core: OCCore, query: OCQuery?, sender: AnyObject?) -> Bool { + guard let sender = sender else { + return false + } + let actionsLocation = OCExtensionLocation(ofType: .action, identifier: locationIdentifier) + let actionContext = ActionContext(viewController: self, core: core, query: query, items: [item], location: actionsLocation, sender: sender) + + if let moreViewController = Action.cardViewController(for: item, with: actionContext, progressHandler: makeActionProgressHandler(), completionHandler: nil) { + self.present(asCard: moreViewController, animated: true) + } + + return true + } +} + +extension ClientItemViewController : OpenItemHandling { + @discardableResult public func open(item: OCItem, animated: Bool, pushViewController: Bool) -> UIViewController? { + if let core = self.core { + if let bookmarkContainer = self.tabBarController as? BookmarkContainer { + let activity = OpenItemUserActivity(detailItem: item, detailBookmark: bookmarkContainer.bookmark) + view.window?.windowScene?.userActivity = activity.openItemUserActivity + } + + switch item.type { + case .collection: + if let location = item.location { + let queryViewController = ClientItemViewController(core: core, drive: drive, query: OCQuery(for: location), rootViewController: rootViewController) + if pushViewController { + self.navigationController?.pushViewController(queryViewController, animated: animated) + } + return queryViewController + } + + case .file: + guard let query = self.query else { + return nil + } + + let itemViewController = DisplayHostViewController(core: core, selectedItem: item, query: query) + itemViewController.hidesBottomBarWhenPushed = true + //!! itemViewController.progressSummarizer = self.progressSummarizer + self.navigationController?.pushViewController(itemViewController, animated: animated) + } + } + + return nil + } +} diff --git a/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift index 906ceca95..512e9028c 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift @@ -229,10 +229,12 @@ extension DriveListCell { } CollectionViewCellProvider.register(CollectionViewCellProvider(for: .drive, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in - if cellConfiguration?.style == .header { - return collectionView.dequeueConfiguredReusableCell(using: driveHeaderCellRegistration, for: indexPath, item: itemRef) - } else { - return collectionView.dequeueConfiguredReusableCell(using: driveListCellRegistration, for: indexPath, item: itemRef) + switch cellConfiguration?.style { + case .header: + return collectionView.dequeueConfiguredReusableCell(using: driveHeaderCellRegistration, for: indexPath, item: itemRef) + + default: + return collectionView.dequeueConfiguredReusableCell(using: driveListCellRegistration, for: indexPath, item: itemRef) } })) } diff --git a/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift index ca2bc66da..d7aefe0b0 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift @@ -40,13 +40,19 @@ public protocol InlineMessageSupport { } */ -public protocol ItemListCellDelegate: class { +public protocol ItemCellDelegate: class { + func moreButtonTapped(cell: ItemListCell, item: OCItem) + func messageButtonTapped(cell: ItemListCell, item: OCItem) + func revealButtonTapped(cell: ItemListCell, item: OCItem) +} - func moreButtonTapped(cell: ItemListCell) - func messageButtonTapped(cell: ItemListCell) - func revealButtonTapped(cell: ItemListCell) +open class ItemCellActionHandlers { + weak var openItemHandler: OpenItemHandling? + weak var moreItemHandler: MoreItemHandling? + weak var revealItemHandler: RevealItemHandling? + weak var inlineMessageHandler: InlineMessageSupport? - func hasMessage(for item: OCItem) -> Bool + weak var delegate: ItemCellDelegate? } open class ItemListCell: UICollectionViewListCell { @@ -64,9 +70,9 @@ open class ItemListCell: UICollectionViewListCell { private let iconSize : CGSize = CGSize(width: 40, height: 40) private let thumbnailSize : CGSize = CGSize(width: 60, height: 60) - open weak var delegate: ItemListCellDelegate? { + open weak var actionHandlers: ItemCellActionHandlers? { didSet { - isMoreButtonPermanentlyHidden = (delegate as? MoreItemHandling == nil) + isMoreButtonPermanentlyHidden = (actionHandlers?.moreItemHandler as? MoreItemHandling == nil) } } @@ -345,7 +351,7 @@ open class ItemListCell: UICollectionViewListCell { } // Set has message - self.hasMessageForItem = delegate?.hasMessage(for: item) ?? false + self.hasMessageForItem = actionHandlers?.inlineMessageHandler?.hasInlineMessage(for: item) ?? false // Set the icon and initiate thumbnail generation let thumbnailRequest = OCResourceRequestItemThumbnail.request(for: item, maximumSize: self.thumbnailSize, scale: 0, waitForConnectivity: true, changeHandler: nil) @@ -451,7 +457,7 @@ open class ItemListCell: UICollectionViewListCell { OnMainThread { [weak self] in let oldMessageForItem = self?.hasMessageForItem ?? false - if let item = self?.item, let hasMessage = self?.delegate?.hasMessage(for: item) { + if let item = self?.item, let hasMessage = self?.actionHandlers?.inlineMessageHandler?.hasInlineMessage(for: item) { self?.hasMessageForItem = hasMessage } else { self?.hasMessageForItem = false @@ -616,13 +622,19 @@ open class ItemListCell: UICollectionViewListCell { // MARK: - Actions @objc open func moreButtonTapped() { - self.delegate?.moreButtonTapped(cell: self) + if let item = item { + self.actionHandlers?.delegate?.moreButtonTapped(cell: self, item: item) + } } @objc open func messageButtonTapped() { - self.delegate?.messageButtonTapped(cell: self) + if let item = item { + self.actionHandlers?.inlineMessageHandler?.showInlineMessageFor(item: item) + } } @objc open func revealButtonTapped() { - self.delegate?.revealButtonTapped(cell: self) + if let item = item { + self.actionHandlers?.delegate?.revealButtonTapped(cell: self, item: item) + } } } @@ -632,7 +644,11 @@ extension ItemListCell { if let cellConfiguration = collectionItemRef.ocCellConfiguration { var itemRecord = cellConfiguration.record - cell.delegate = cellConfiguration.hostViewController as? ItemListCellDelegate + switch cellConfiguration.style { + case .item(let actionHandlers): cell.actionHandlers = actionHandlers + default: break + } + cell.core = cellConfiguration.core if itemRecord == nil { @@ -648,7 +664,7 @@ extension ItemListCell { if let itemRecord = itemRecord { if let item = itemRecord.item { if let ocItem = item as? OCItem { - cell.updateWith(ocItem) + cell.item = ocItem } } else { // Request reconfiguration of cell diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift index 4736c8ed4..a7bd9326e 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift @@ -19,10 +19,11 @@ import UIKit import ownCloudSDK -public enum CollectionViewCellStyle : CaseIterable { +public enum CollectionViewCellStyle { case regular case header case footer + case item(actionHandlers: ItemCellActionHandlers) } public class CollectionViewCellConfiguration: NSObject { diff --git a/ownCloudAppShared/Client/File Lists/FileListTableViewController.swift b/ownCloudAppShared/Client/File Lists/FileListTableViewController.swift index ff313819c..a06bc4b99 100644 --- a/ownCloudAppShared/Client/File Lists/FileListTableViewController.swift +++ b/ownCloudAppShared/Client/File Lists/FileListTableViewController.swift @@ -19,20 +19,20 @@ import UIKit import ownCloudSDK -public protocol OpenItemHandling { +public protocol OpenItemHandling : class { @discardableResult func open(item: OCItem, animated: Bool, pushViewController: Bool) -> UIViewController? } -public protocol MoreItemHandling { +public protocol MoreItemHandling : class { @discardableResult func moreOptions(for item: OCItem, at location: OCExtensionLocationIdentifier, core: OCCore, query: OCQuery?, sender: AnyObject?) -> Bool } -public protocol RevealItemHandling { +public protocol RevealItemHandling : class { @discardableResult func reveal(item: OCItem, core: OCCore, sender: AnyObject?) -> Bool func showReveal(at path: IndexPath) -> Bool } -public protocol InlineMessageSupport { +public protocol InlineMessageSupport : class { func hasInlineMessage(for item: OCItem) -> Bool func showInlineMessageFor(item: OCItem) } diff --git a/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift b/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift index 0a4926cf6..cadc6ee71 100644 --- a/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift +++ b/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift @@ -18,6 +18,7 @@ import UIKit import ownCloudSDK +import ownCloudApp public class ClientItemViewController: CollectionViewController { @@ -33,12 +34,16 @@ public class ClientItemViewController: CollectionViewController { private var singleDriveDatasourceSubscription : OCDataSourceSubscription? public var driveAdditionalItemsDataSource : OCDataSourceArray = OCDataSourceArray() + public var itemActionHandlers : ItemCellActionHandlers + public init(core inCore: OCCore, drive inDrive: OCDrive?, query inQuery: OCQuery, reveal inItem: OCItem? = nil, rootViewController: UIViewController?) { drive = inDrive query = inQuery var sections : [ CollectionViewSection ] = [] + itemActionHandlers = ItemCellActionHandlers() + if let queryDatasource = query?.queryResultsDataSource { singleDriveDatasource = OCDataSourceComposition(sources: [inCore.drivesDataSource]) @@ -61,7 +66,7 @@ public class ClientItemViewController: CollectionViewController { driveSection = CollectionViewSection(identifier: "drive", dataSource: driveSectionDataSource, cellStyle: .header) } - queryItemDataSourceSection = CollectionViewSection(identifier: "items", dataSource: queryDatasource) + queryItemDataSourceSection = CollectionViewSection(identifier: "items", dataSource: queryDatasource, cellStyle: .item(actionHandlers: itemActionHandlers)) if let driveSection = driveSection { sections.append(driveSection) @@ -74,6 +79,12 @@ public class ClientItemViewController: CollectionViewController { super.init(core: inCore, rootViewController: rootViewController, sections: sections, listAppearance: .plain) + // Configure item actions handlers + itemActionHandlers.inlineMessageHandler = rootViewController as? InlineMessageSupport + itemActionHandlers.moreItemHandler = self as? MoreItemHandling + itemActionHandlers.openItemHandler = self as? OpenItemHandling + itemActionHandlers.delegate = self + // Subscribe to singleDriveDatasource for changes, to update driveSectionDataSource singleDriveDatasourceSubscription = singleDriveDatasource?.subscribe(updateHandler: { [weak self] subscription in self?.updateAdditionalDriveItems(from: subscription) @@ -113,12 +124,14 @@ public class ClientItemViewController: CollectionViewController { public override func handleSelection(of record: OCDataItemRecord, at indexPath: IndexPath) -> Bool { if let core = self.core, let rootViewController = self.rootViewController { if let item = record.item as? OCItem, let location = item.location { - let query = OCQuery(for: location) - let rootFolderViewController = ClientItemViewController(core: core, drive: drive, query: query, rootViewController: rootViewController) - collectionView.deselectItem(at: indexPath, animated: true) - self.navigationController?.pushViewController(rootFolderViewController, animated: true) + if let openHandler = itemActionHandlers.openItemHandler { + openHandler.open(item: item, animated: true, pushViewController: true) + } else { + let rootFolderViewController = ClientItemViewController(core: core, drive: drive, query: OCQuery(for: location), rootViewController: rootViewController) + self.navigationController?.pushViewController(rootFolderViewController, animated: true) + } return true } @@ -146,22 +159,39 @@ public class ClientItemViewController: CollectionViewController { core?.vault.resourceManager?.start(descriptionResourceRequest) } } -} -extension ClientItemViewController: ItemListCellDelegate { - public func moreButtonTapped(cell: ItemListCell) { + var _actionProgressHandler : ActionProgressHandler? + open func makeActionProgressHandler() -> ActionProgressHandler { + if _actionProgressHandler == nil { + _actionProgressHandler = { [weak self] (progress, publish) in + if publish { + //!! self?.progressSummarizer?.startTracking(progress: progress) + } else { + //!! self?.progressSummarizer?.stopTracking(progress: progress) + } + } + } + + return _actionProgressHandler! } +} - public func messageButtonTapped(cell: ItemListCell) { +extension ClientItemViewController: ItemCellDelegate { + public func moreButtonTapped(cell: ItemListCell, item: OCItem) { + guard let core = core, let query = query else { + return + } + if let moreItemHandling = self as? MoreItemHandling { + moreItemHandling.moreOptions(for: item, at: .moreItem, core: core, query: query, sender: cell) + } } - public func revealButtonTapped(cell: ItemListCell) { - + public func messageButtonTapped(cell: ItemListCell, item: OCItem) { } - public func hasMessage(for item: OCItem) -> Bool { - return false + public func revealButtonTapped(cell: ItemListCell, item: OCItem) { } } + From 3036f54cd371fdee1642a7570e1c32354e4d6331 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Mon, 25 Apr 2022 09:54:35 +0200 Subject: [PATCH 022/328] - address code review findings by @hosy in https://github.com/owncloud/ios-app/pull/1092 --- .../StaticLoginSingleAccountServerListViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ownCloud/Static Login/Interface/StaticLoginSingleAccountServerListViewController.swift b/ownCloud/Static Login/Interface/StaticLoginSingleAccountServerListViewController.swift index d12627000..e54148bad 100644 --- a/ownCloud/Static Login/Interface/StaticLoginSingleAccountServerListViewController.swift +++ b/ownCloud/Static Login/Interface/StaticLoginSingleAccountServerListViewController.swift @@ -257,12 +257,12 @@ class StaticLoginSingleAccountServerListViewController: ServerListTableViewContr let attributedTitle = NSMutableAttributedString() attributedTitle.append(NSAttributedString(string: displayName.appendingFormat("\n"), attributes: [ - NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 24) + NSAttributedString.Key.font : UIFont.preferredFont(forTextStyle: .title2, with: .bold) ])) if let serverName = bookmark.url?.host { attributedTitle.append(NSAttributedString(string: serverName, attributes: [ - NSAttributedString.Key.font : UIFont.systemFont(ofSize: 18) + NSAttributedString.Key.font : UIFont.preferredFont(forTextStyle: .title3) ])) } From 217ebe5c21eca82109ca8e085a5297d70aaf0afe Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Mon, 25 Apr 2022 22:59:56 +0200 Subject: [PATCH 023/328] - switch to new UTType APIs - fix most deprecation warnings - FileProvider: replace .typeIdentifier with .contentType - new class ClientContext: - used to encapsulate and pass around all important API / UI objects to all parts of the UI - allow customization for specific purposes through primitive "inheritance" - can be passed around "strongly" while storing OCCore reference "weakly" (structural barrier to accidential retains) - replace ItemCellActionHandlers with ClientContext - create updated "Action" versions of MoreItemHandling, OpenItemHandling, RevealItemAction, rename InlineMessageSupport to InlineMessageCenter - restructure code to move MoreItemAction, OpenItemAction and InlineMessageCenter to the ClientRootViewController level (ClientRootViewController+ItemActions) - add theming support and shadow effect to ExpandableResourceCell (via new GradientView) - ItemListCell, CollectionViewCellConfiguration, event handling: rewire for ClientContext - Theme: replace WeakThemeable with NSHashTable for more speed / memory efficiency - OCResourceText+OCViewProvider: implement ThemeableTextView to add theming support - update minimum version of PLCrashReporter to 1.10.1 (from 1.7.0) - update SDK --- ios-sdk | 2 +- .../OCItem+FileProviderItem.m | 21 ++-- .../ShareViewController.swift | 13 +-- ownCloud.xcodeproj/project.pbxproj | 28 ++++- .../xcshareddata/xcschemes/ownCloud.xcscheme | 1 + .../Actions+Extensions/CopyAction.swift | 4 +- .../ImportPasteboardAction.swift | 25 +++-- .../UploadCameraMediaAction.swift | 5 +- .../Actions+Extensions/UploadFileAction.swift | 3 +- .../Actions/EditDocumentViewController.swift | 6 +- ...ClientItemViewController+ItemActions.swift | 71 ------------ ...ClientRootViewController+ItemActions.swift | 106 ++++++++++++++++++ .../Client/ClientRootViewController.swift | 20 +++- ...yViewController+InlineMessageSupport.swift | 2 +- .../Client/PhotoSelectionViewController.swift | 3 +- ownCloud/Client/Viewer/DisplayExtension.swift | 7 +- .../Media/MediaDisplayViewController.swift | 3 +- .../PhotoKit Extensions/PHAsset+Upload.swift | 9 +- .../Settings/LogFilesViewController.swift | 3 +- ownCloud/Tools/PasswordManagerAccess.swift | 5 +- .../Notifications/NotificationManager.m | 2 +- .../Cells/ExpandableResourceCell.swift | 33 +++++- .../Collection Views/Cells/ItemListCell.swift | 75 ++++--------- .../CollectionViewCellConfiguration.swift | 8 +- .../CollectionViewController.swift | 30 ++--- .../CollectionViewSection.swift | 6 +- .../ClientItemViewController.swift | 106 +++++++----------- .../Client/Context/ClientContext.swift | 97 ++++++++++++++++ .../ClientQueryViewController.swift | 31 +++-- .../FileListTableViewController.swift | 9 +- .../PublicLinkTableViewController.swift | 3 +- .../User Interface/ClientItemCell.swift | 6 +- .../Client/User Interface/GradientView.swift | 87 ++++++++++++++ .../SDK Extensions/OCItem+Extension.swift | 5 +- .../Theme/Resources/ThemeImage.swift | 2 +- .../User Interface/Theme/Theme.swift | 31 ++--- .../Theme/UI/ThemeTableViewCell.swift | 4 - .../OCResourceText+ViewProvider.swift | 43 ++++++- 38 files changed, 574 insertions(+), 341 deletions(-) delete mode 100644 ownCloud/Client/ClientItemViewController+ItemActions.swift create mode 100644 ownCloud/Client/ClientRootViewController+ItemActions.swift rename ownCloudAppShared/Client/{ => Collection Views}/View Controllers/ClientItemViewController.swift (61%) create mode 100644 ownCloudAppShared/Client/Context/ClientContext.swift create mode 100644 ownCloudAppShared/Client/User Interface/GradientView.swift diff --git a/ios-sdk b/ios-sdk index 358c8f93b..a6a3eb149 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit 358c8f93baa7676b0f7a85032f6c6e09d0a0bfbd +Subproject commit a6a3eb1496dff2b3e56e5a2f1ca8422b7b66169f diff --git a/ownCloud File Provider/OCItem+FileProviderItem.m b/ownCloud File Provider/OCItem+FileProviderItem.m index 2ab255fe7..400e85d35 100644 --- a/ownCloud File Provider/OCItem+FileProviderItem.m +++ b/ownCloud File Provider/OCItem+FileProviderItem.m @@ -17,6 +17,7 @@ */ #import +#import #import "OCItem+FileProviderItem.h" #import "NSError+MessageResolution.h" @@ -156,14 +157,14 @@ - (NSString *)filename return (utiBySuffix); } -- (NSString *)typeIdentifier +- (UTType *)contentType { - NSString *uti = nil; + UTType *uti = nil; // Return special UTI type for folders if (self.type == OCItemTypeCollection) { - return ((__bridge NSString *)kUTTypeFolder); + return (UTTypeFolder); } // Workaround for broken MIMEType->UTI conversions @@ -172,7 +173,7 @@ - (NSString *)typeIdentifier // Override by MIMEType if (self.mimeType != nil) { - uti = OCItem.overriddenUTIByMIMEType[self.mimeType]; + uti = [UTType typeWithIdentifier:OCItem.overriddenUTIByMIMEType[self.mimeType]]; OCLogVerbose(@"Mapped %@ MIMEType %@ to UTI %@", self.name, self.mimeType, uti); } @@ -185,7 +186,7 @@ - (NSString *)typeIdentifier // Override by suffix if ((suffix = self.name.pathExtension.lowercaseString) != nil) { - uti = OCItem.overriddenUTIBySuffix[suffix]; + uti = [UTType typeWithIdentifier:OCItem.overriddenUTIBySuffix[suffix]]; OCLogVerbose(@"Mapped %@ suffix %@ to UTI %@", self.name, suffix, uti); } @@ -196,22 +197,22 @@ - (NSString *)typeIdentifier { if (self.mimeType != nil) { - uti = ((NSString *)CFBridgingRelease(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (__bridge CFStringRef)self.mimeType, NULL))); + uti = [UTType typeWithMIMEType:self.mimeType]; } else { - uti = (__bridge NSString *)kUTTypeData; + uti = UTTypeData; } OCLogVerbose(@"Converted %@ MIMEType %@ to UTI %@", self.name, self.mimeType, uti); } // Reject "dyn.*" types - if ([uti hasPrefix:@"dyn."]) + if (uti.isDynamic) { // Use generic data UTI instead // Rationale: https://github.com/owncloud/ios-app/issues/747#issuecomment-689797261 - uti = (__bridge NSString *)kUTTypeData; + uti = UTTypeData; OCLogVerbose(@"Rejected dynamic %@ UTI for %@, using %@ instead", self.name, self.mimeType, uti); } @@ -244,7 +245,7 @@ - (NSFileProviderItemCapabilities)capabilities break; } - return (NSFileProviderItemCapabilitiesAllowsAll); + return (NSFileProviderItemCapabilitiesAllowsContentEnumerating); // previously NSFileProviderItemCapabilitiesAllowsAll, but since it shouldn't be used anyway… } - (NSData *)versionIdentifier diff --git a/ownCloud Share Extension/ShareViewController.swift b/ownCloud Share Extension/ShareViewController.swift index 0342fe71c..ab55532e7 100644 --- a/ownCloud Share Extension/ShareViewController.swift +++ b/ownCloud Share Extension/ShareViewController.swift @@ -21,6 +21,7 @@ import ownCloudSDK import ownCloudApp import ownCloudAppShared import CoreServices +import UniformTypeIdentifiers extension NSErrorDomain { static let ShareViewErrorDomain = "ShareViewErrorDomain" @@ -305,7 +306,7 @@ class ShareViewController: MoreStaticTableViewController { break } - if var type = attachment.registeredTypeIdentifiers.first, attachment.hasItemConformingToTypeIdentifier(kUTTypeItem as String) { + if var type = attachment.registeredTypeIdentifiers.first, attachment.hasItemConformingToTypeIdentifier(UTType.item.identifier) { if type == "public.plain-text" || type == "public.url" || attachment.registeredTypeIdentifiers.contains("public.file-url") { asyncQueue.async({ (jobDone) in if progressViewController?.cancelled == true { @@ -325,7 +326,7 @@ class ShareViewController: MoreStaticTableViewController { var tempFileURL : URL? if let text = item as? String { // Save plain text content - let ext = self.utiToFileExtension(type) + let ext = UTType(type)?.preferredFilenameExtension tempFilePath = NSTemporaryDirectory() + (attachment.suggestedName ?? "Text".localized) + "." + (ext ?? type) data = Data(text.utf8) } else if let url = item as? URL { // Download URL content @@ -449,11 +450,3 @@ class ShareViewController: MoreStaticTableViewController { } } } - -extension ShareViewController { - public func utiToFileExtension(_ utiType: String) -> String? { - guard let ext = UTTypeCopyPreferredTagWithClass(utiType as CFString, kUTTagClassFilenameExtension) else { return nil } - - return ext.takeRetainedValue() as String - } -} diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index a6dd0f9c1..9877fcde0 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -360,6 +360,7 @@ DC7C101124B5FA7700227085 /* OCBookmark+AppExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = DC7C100E24B5F81E00227085 /* OCBookmark+AppExtensions.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC7C101224B5FD6500227085 /* OCBookmark+AppExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = DC7C100F24B5F81E00227085 /* OCBookmark+AppExtensions.m */; }; DC7DBA37207F84BF00E7337D /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC7DBA36207F84BF00E7337D /* main.swift */; }; + DC82663C28168D2800F91F7D /* ClientContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC82663B28168D2800F91F7D /* ClientContext.swift */; }; DC82D6FA23171339001551C5 /* ScanAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC82D6F923171339001551C5 /* ScanAction.swift */; }; DC854936218331CF00782BA8 /* UserInterfaceSettingsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC854935218331CF00782BA8 /* UserInterfaceSettingsSection.swift */; }; DC85572C20513B8C00189B9A /* ServerListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC85572A20513B8C00189B9A /* ServerListTableViewController.swift */; }; @@ -427,7 +428,8 @@ DCD8109B23984AF6003B0053 /* OCLicenseDuration.m in Sources */ = {isa = PBXBuildFile; fileRef = DCD810932398492C003B0053 /* OCLicenseDuration.m */; }; DCD863FB28115C8700CA6631 /* Down.LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = DCD863FA28115C8700CA6631 /* Down.LICENSE */; }; DCD8640628115FD600CA6631 /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = DCD8640528115FD600CA6631 /* OpenSSL */; }; - DCD864102811821200CA6631 /* ClientItemViewController+ItemActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCD8640F2811821200CA6631 /* ClientItemViewController+ItemActions.swift */; }; + DCD864102811821200CA6631 /* ClientRootViewController+ItemActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCD8640F2811821200CA6631 /* ClientRootViewController+ItemActions.swift */; }; + DCD864122811FC5700CA6631 /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCD864112811FC5700CA6631 /* GradientView.swift */; }; DCD954DF247D62FA00E184E6 /* MessageTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCD954DE247D62FA00E184E6 /* MessageTableViewController.swift */; }; DCD9B87B2379612B00691929 /* OCLicenseManager+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = DCD9B873237960E600691929 /* OCLicenseManager+Internal.h */; }; DCDBB60A2525305600FAD707 /* NotificationAuthErrorForwarder.h in Headers */ = {isa = PBXBuildFile; fileRef = DCDBB60225252FDA00FAD707 /* NotificationAuthErrorForwarder.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1342,6 +1344,7 @@ DC7DBA36207F84BF00E7337D /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; DC7DBA52207F8BD600E7337D /* img */ = {isa = PBXFileReference; lastKnownFileType = folder; path = img; sourceTree = SOURCE_ROOT; }; DC7DBA53207FA80C00E7337D /* TVGImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVGImage.swift; sourceTree = ""; }; + DC82663B28168D2800F91F7D /* ClientContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientContext.swift; sourceTree = ""; }; DC82D6F923171339001551C5 /* ScanAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanAction.swift; sourceTree = ""; }; DC85493321831B0B00782BA8 /* GitCommit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitCommit.swift; sourceTree = ""; }; DC854935218331CF00782BA8 /* UserInterfaceSettingsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInterfaceSettingsSection.swift; sourceTree = ""; }; @@ -1426,7 +1429,8 @@ DCD810922398492C003B0053 /* OCLicenseDuration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCLicenseDuration.h; sourceTree = ""; }; DCD810932398492C003B0053 /* OCLicenseDuration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCLicenseDuration.m; sourceTree = ""; }; DCD863FA28115C8700CA6631 /* Down.LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = Down.LICENSE; sourceTree = ""; }; - DCD8640F2811821200CA6631 /* ClientItemViewController+ItemActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ClientItemViewController+ItemActions.swift"; sourceTree = ""; }; + DCD8640F2811821200CA6631 /* ClientRootViewController+ItemActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ClientRootViewController+ItemActions.swift"; sourceTree = ""; }; + DCD864112811FC5700CA6631 /* GradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientView.swift; sourceTree = ""; }; DCD954DE247D62FA00E184E6 /* MessageTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageTableViewController.swift; sourceTree = ""; }; DCD9B873237960E600691929 /* OCLicenseManager+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCLicenseManager+Internal.h"; sourceTree = ""; }; DCDBB60225252FDA00FAD707 /* NotificationAuthErrorForwarder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationAuthErrorForwarder.h; sourceTree = ""; }; @@ -1838,8 +1842,8 @@ 3912208023436E9B0026C290 /* Client */ = { isa = PBXGroup; children = ( + DC82664028168DAA00F91F7D /* Context */, DCEAF0842808250E00980B6D /* Collection Views */, - DC3AB1932808C2DE00789435 /* View Controllers */, DCA2EDDB279B0E5D001F04E6 /* Resource Sources */, DCE4E43424C199860051722F /* Actions */, DCE4E42D24C1961D0051722F /* File Lists */, @@ -2410,6 +2414,7 @@ DC63208421FCEBE9007EC0A8 /* ClientActivityCell.swift */, DC63208221FCAC1E007EC0A8 /* ClientActivityViewController.swift */, DC3BE0DD2077CC13002A0AC0 /* ClientRootViewController.swift */, + DCD8640F2811821200CA6631 /* ClientRootViewController+ItemActions.swift */, DCE28F5F2433683700879DEC /* ClientSessionManager.swift */, DCB6C4D62453A6CA00C1EAE1 /* ClientAuthenticationUpdater.swift */, DCB6C4DD24559B1600C1EAE1 /* ClientAuthenticationUpdaterViewController.swift */, @@ -2418,7 +2423,6 @@ 4C1561E7222321E0009C4EF3 /* PhotoSelectionViewController.swift */, 4C1561EE22232357009C4EF3 /* PhotoSelectionViewCell.swift */, DC6CC3142642C3560040ECAC /* ExternalBrowserBusyHandler.swift */, - DCD8640F2811821200CA6631 /* ClientItemViewController+ItemActions.swift */, ); path = Client; sourceTree = ""; @@ -2607,6 +2611,14 @@ path = "Server List"; sourceTree = ""; }; + DC82664028168DAA00F91F7D /* Context */ = { + isa = PBXGroup; + children = ( + DC82663B28168D2800F91F7D /* ClientContext.swift */, + ); + path = Context; + sourceTree = ""; + }; DC85573120513C7500189B9A /* Resources */ = { isa = PBXGroup; children = ( @@ -2968,6 +2980,7 @@ 3912208123436EB80026C290 /* SortMethod.swift */, 39DE75C722F960CF0064C1E2 /* SortMethodTableViewController.swift */, 23FA23E520BFD3D8009A6D73 /* SortBar.swift */, + DCD864112811FC5700CA6631 /* GradientView.swift */, ); path = "User Interface"; sourceTree = ""; @@ -3056,6 +3069,7 @@ DCFC9ED2280023BB005D9144 /* CollectionViewCellProvider+StandardImplementations.swift */, DCFC9ECB28002303005D9144 /* CollectionViewSection.swift */, DC04FFC727F5B79000F22569 /* CollectionViewController.swift */, + DC3AB1932808C2DE00789435 /* View Controllers */, ); path = "Collection Views"; sourceTree = ""; @@ -4153,7 +4167,7 @@ 39E42D1C2315288B00B82AC3 /* KeyCommands.swift in Sources */, 23D5241521491C670002C566 /* DisplayViewController.swift in Sources */, DCFB74C221AD5D10005796AF /* StaticLoginSetupViewController.swift in Sources */, - DCD864102811821200CA6631 /* ClientItemViewController+ItemActions.swift in Sources */, + DCD864102811821200CA6631 /* ClientRootViewController+ItemActions.swift in Sources */, DC0B379420514E4700189B9A /* ServerListBookmarkCell.swift in Sources */, 397E276A23D04D7100117B07 /* StaticLoginSingleAccountServerListViewController.swift in Sources */, DCE20272249AB50E0015A22A /* OCMessage+Extension.swift in Sources */, @@ -4347,11 +4361,13 @@ DC0A358624C0E44600FB58FC /* ThemeTVGResource.swift in Sources */, DC3AB24428104AA500789435 /* UIFont+Weight.swift in Sources */, DC0A358524C0E44600FB58FC /* ThemeResource.swift in Sources */, + DCD864122811FC5700CA6631 /* GradientView.swift in Sources */, DCFC9ED528002F33005D9144 /* CollectionViewCellConfiguration.swift in Sources */, DC0A355624C0E33A00FB58FC /* Log.swift in Sources */, DC3AB24B2810A69600789435 /* OCResourceText+ViewProvider.swift in Sources */, DC0A359624C0E61500FB58FC /* UIView+Extension.swift in Sources */, DCE4E43924C19AB20051722F /* MoreStaticTableViewController.swift in Sources */, + DC82663C28168D2800F91F7D /* ClientContext.swift in Sources */, DC0A358C24C0E44B00FB58FC /* ThemeWindow.swift in Sources */, DC0A357124C0E42700FB58FC /* StaticTableViewRow.swift in Sources */, DC3AB1982808C35300789435 /* ClientItemViewController.swift in Sources */, @@ -5722,7 +5738,7 @@ repositoryURL = "https://github.com/microsoft/plcrashreporter.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 1.7.0; + minimumVersion = 1.10.1; }; }; DC049197258CAF8200DEDC27 /* XCRemoteSwiftPackageReference "PocketSVG" */ = { diff --git a/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme b/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme index 45f5993ea..f66769198 100644 --- a/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme +++ b/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme @@ -123,6 +123,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "en" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift b/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift index 913e944a2..6e16e3851 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift @@ -20,6 +20,7 @@ import Foundation import MobileCoreServices import ownCloudSDK import ownCloudAppShared +import UniformTypeIdentifiers class OCItemPasteboardValue : NSObject, NSSecureCoding { static var supportsSecureCoding: Bool = true @@ -192,8 +193,7 @@ class CopyAction : Action { if item.type == .file { // only files can be added to the globale pasteboard guard let itemMimeType = item.mimeType else { return } - let mimeTypeCF = itemMimeType as CFString - guard let rawUti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeTypeCF, nil)?.takeRetainedValue() as String? else { return } + guard let rawUti = UTType(mimeType: itemMimeType)?.identifier else { return } itemProvider.registerFileRepresentation(forTypeIdentifier: rawUti, fileOptions: [], visibility: .all, loadHandler: { [weak core] (completionHandler) -> Progress? in var progress : Progress? diff --git a/ownCloud/Client/Actions/Actions+Extensions/ImportPasteboardAction.swift b/ownCloud/Client/Actions/Actions+Extensions/ImportPasteboardAction.swift index 9366d791b..4fba94a12 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/ImportPasteboardAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/ImportPasteboardAction.swift @@ -20,6 +20,7 @@ import Foundation import ownCloudSDK import MobileCoreServices import ownCloudAppShared +import UniformTypeIdentifiers extension String { func trimIllegalCharacters() -> String { @@ -157,21 +158,21 @@ class ImportPasteboardAction : Action { for item in generalPasteboard.itemProviders { let typeIdentifiers = item.registeredTypeIdentifiers let preferredUTIs = [ - kUTTypeImage, - kUTTypeMovie, - kUTTypePDF, - kUTTypeText, - kUTTypeRTF, - kUTTypeHTML, - kUTTypePlainText + UTType.image, + UTType.movie, + UTType.pdf, + UTType.text, + UTType.rtf, + UTType.html, + UTType.plainText ] var useUTI : String? var useIndex : Int = Int.max for typeIdentifier in typeIdentifiers { - if !typeIdentifier.hasPrefix("dyn.") { + if !typeIdentifier.hasPrefix("dyn."), let typeIdentifierUTI = UTType(typeIdentifier) { for preferredUTI in preferredUTIs { - let conforms = UTTypeConformsTo(typeIdentifier as CFString, preferredUTI) + let conforms = typeIdentifierUTI.conforms(to: preferredUTI) // Log.log("\(preferredUTI) vs \(typeIdentifier) -> \(conforms)") @@ -190,7 +191,7 @@ class ImportPasteboardAction : Action { } if useUTI == nil { - useUTI = kUTTypeData as String + useUTI = UTType.data.identifier } var fileName: String? @@ -200,11 +201,11 @@ class ImportPasteboardAction : Action { let fileNameMaxLength = 16 - if useUTI == kUTTypeUTF8PlainText as String { + if useUTI == UTType.utf8PlainText.identifier { fileName = try? String(String(contentsOf: url, encoding: .utf8).prefix(fileNameMaxLength) + ".txt") } - if useUTI == kUTTypeRTF as String { + if useUTI == UTType.rtf.identifier { let options = [NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.rtf] fileName = try? String(NSAttributedString(url: url, options: options, documentAttributes: nil).string.prefix(fileNameMaxLength) + ".rtf") } diff --git a/ownCloud/Client/Actions/Actions+Extensions/UploadCameraMediaAction.swift b/ownCloud/Client/Actions/Actions+Extensions/UploadCameraMediaAction.swift index 8c80a8d83..7d55f632c 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/UploadCameraMediaAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/UploadCameraMediaAction.swift @@ -20,6 +20,7 @@ import UIKit import ownCloudSDK import ownCloudAppShared import CoreServices +import UniformTypeIdentifiers import ImageIO import AVFoundation @@ -145,7 +146,7 @@ class CameraViewPresenter: NSObject, UIImagePickerControllerDelegate, UINavigati // Generate a timestamp string which will be used in the name of uploaded media let timeStamp = CameraViewPresenter.dateFormatter.string(from: Date()) - if type == String(kUTTypeImage) { + if type == UTType.image.identifier { // Retrieve UIImage image = info[.originalImage] as? UIImage @@ -184,7 +185,7 @@ class CameraViewPresenter: NSObject, UIImagePickerControllerDelegate, UINavigati Log.log(tagged: ["CAMERA_UPLOAD"], "Finalized writing image with UTI \(uti) to disk") } - } else if type == String(kUTTypeMovie) { + } else if type == UTType.movie.identifier { guard let videoURL = info[.mediaURL] as? URL else { return } let fileName = "Video-\(timeStamp)" diff --git a/ownCloud/Client/Actions/Actions+Extensions/UploadFileAction.swift b/ownCloud/Client/Actions/Actions+Extensions/UploadFileAction.swift index e964582bb..9ec1b1cfc 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/UploadFileAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/UploadFileAction.swift @@ -20,6 +20,7 @@ import UIKit import ownCloudSDK import ownCloudAppShared import CoreServices +import UniformTypeIdentifiers class UploadFileAction: UploadBaseAction { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.uploadfile") } @@ -40,7 +41,7 @@ class UploadFileAction: UploadBaseAction { return } - let documentPickerViewController = UIDocumentPickerViewController(documentTypes: [kUTTypeData as String], in: .import) + let documentPickerViewController = UIDocumentPickerViewController(documentTypes: [UTType.data.identifier], in: .import) documentPickerViewController.delegate = self documentPickerViewController.allowsMultipleSelection = true diff --git a/ownCloud/Client/Actions/EditDocumentViewController.swift b/ownCloud/Client/Actions/EditDocumentViewController.swift index 086e6808e..31e092d31 100644 --- a/ownCloud/Client/Actions/EditDocumentViewController.swift +++ b/ownCloud/Client/Actions/EditDocumentViewController.swift @@ -125,7 +125,7 @@ class EditDocumentViewController: QLPreviewController, Themeable { @objc func enableEditingMode() { // Activate editing mode by performing the action on pencil icon. Unfortunately that's the only way to do it apparently - if #available(iOS 15.0, *) { + // if #available(iOS 15.0, *) { if self.navigationItem.rightBarButtonItems?.count ?? 0 > 2 { guard let markupButton = self.navigationItem.rightBarButtonItems?[1] else { return } _ = markupButton.target?.perform(markupButton.action, with: markupButton) @@ -136,7 +136,7 @@ class EditDocumentViewController: QLPreviewController, Themeable { guard let markupButton = self.navigationItem.rightBarButtonItems?.first else { return } _ = markupButton.target?.perform(markupButton.action, with: markupButton) } - } else if #available(iOS 14.0, *) { + /*} else if #available(iOS 14.0, *) { if self.navigationItem.rightBarButtonItems?.count ?? 0 > 1 { guard let markupButton = self.navigationItem.rightBarButtonItems?.last else { return } _ = markupButton.target?.perform(markupButton.action, with: markupButton) @@ -147,7 +147,7 @@ class EditDocumentViewController: QLPreviewController, Themeable { } else { // action and target is nil on iOS 13 guard let markupButton = self.navigationItem.rightBarButtonItems?.filter({$0.customView != nil}).first?.customView as? UIButton else { return } markupButton.sendActions(for: .touchUpInside) - } + } */ } @objc func dismissAnimated() { diff --git a/ownCloud/Client/ClientItemViewController+ItemActions.swift b/ownCloud/Client/ClientItemViewController+ItemActions.swift deleted file mode 100644 index 2f87da6b0..000000000 --- a/ownCloud/Client/ClientItemViewController+ItemActions.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// ClientItemViewController+ItemActions.swift -// ownCloud -// -// Created by Felix Schwarz on 21.04.22. -// Copyright © 2022 ownCloud GmbH. All rights reserved. -// - -/* - * Copyright (C) 2022, ownCloud GmbH. - * - * This code is covered by the GNU Public License Version 3. - * - * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ - * You should have received a copy of this license along with this program. If not, see . - * - */ - -import UIKit -import ownCloudSDK -import ownCloudAppShared - -extension ClientItemViewController : MoreItemHandling { - public func moreOptions(for item: OCItem, at locationIdentifier: OCExtensionLocationIdentifier, core: OCCore, query: OCQuery?, sender: AnyObject?) -> Bool { - guard let sender = sender else { - return false - } - let actionsLocation = OCExtensionLocation(ofType: .action, identifier: locationIdentifier) - let actionContext = ActionContext(viewController: self, core: core, query: query, items: [item], location: actionsLocation, sender: sender) - - if let moreViewController = Action.cardViewController(for: item, with: actionContext, progressHandler: makeActionProgressHandler(), completionHandler: nil) { - self.present(asCard: moreViewController, animated: true) - } - - return true - } -} - -extension ClientItemViewController : OpenItemHandling { - @discardableResult public func open(item: OCItem, animated: Bool, pushViewController: Bool) -> UIViewController? { - if let core = self.core { - if let bookmarkContainer = self.tabBarController as? BookmarkContainer { - let activity = OpenItemUserActivity(detailItem: item, detailBookmark: bookmarkContainer.bookmark) - view.window?.windowScene?.userActivity = activity.openItemUserActivity - } - - switch item.type { - case .collection: - if let location = item.location { - let queryViewController = ClientItemViewController(core: core, drive: drive, query: OCQuery(for: location), rootViewController: rootViewController) - if pushViewController { - self.navigationController?.pushViewController(queryViewController, animated: animated) - } - return queryViewController - } - - case .file: - guard let query = self.query else { - return nil - } - - let itemViewController = DisplayHostViewController(core: core, selectedItem: item, query: query) - itemViewController.hidesBottomBarWhenPushed = true - //!! itemViewController.progressSummarizer = self.progressSummarizer - self.navigationController?.pushViewController(itemViewController, animated: animated) - } - } - - return nil - } -} diff --git a/ownCloud/Client/ClientRootViewController+ItemActions.swift b/ownCloud/Client/ClientRootViewController+ItemActions.swift new file mode 100644 index 000000000..71d356b3b --- /dev/null +++ b/ownCloud/Client/ClientRootViewController+ItemActions.swift @@ -0,0 +1,106 @@ +// +// ClientRootViewController+ItemActions.swift +// ownCloud +// +// Created by Felix Schwarz on 21.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK +import ownCloudAppShared + +extension ClientRootViewController : MoreItemAction { + func makeActionProgressHandler() -> ActionProgressHandler { + return { [weak self] (progress, publish) in + if publish { + self?.rootContext?.progressSummarizer?.startTracking(progress: progress) + } else { + self?.rootContext?.progressSummarizer?.stopTracking(progress: progress) + } + } + } + + func moreOptions(for item: OCItem, at locationIdentifier: OCExtensionLocationIdentifier, context: ClientContext, sender: AnyObject?) -> Bool { + guard let sender = sender, let core = context.core else { + return false + } + let actionsLocation = OCExtensionLocation(ofType: .action, identifier: locationIdentifier) + let actionContext = ActionContext(viewController: self, core: core, query: context.query, items: [item], location: actionsLocation, sender: sender) + + if let moreViewController = Action.cardViewController(for: item, with: actionContext, progressHandler: makeActionProgressHandler(), completionHandler: nil) { + self.present(asCard: moreViewController, animated: true) + } + + return true + } +} + +extension ClientRootViewController : OpenItemAction { + @discardableResult public func open(item: OCItem, context: ClientContext, animated: Bool, pushViewController: Bool) -> UIViewController? { + if let core = context.core { + if let bookmarkContainer = self.tabBarController as? BookmarkContainer { + let activity = OpenItemUserActivity(detailItem: item, detailBookmark: bookmarkContainer.bookmark) + view.window?.windowScene?.userActivity = activity.openItemUserActivity + } + + switch item.type { + case .collection: + if let location = item.location { + let queryViewController = ClientItemViewController(context: context, query: OCQuery(for: location)) + if pushViewController { + context.navigationController?.pushViewController(queryViewController, animated: animated) + } + return queryViewController + } + + case .file: + guard let query = context.query else { + return nil + } + + let itemViewController = DisplayHostViewController(core: core, selectedItem: item, query: query) + itemViewController.hidesBottomBarWhenPushed = true + //!! itemViewController.progressSummarizer = self.progressSummarizer + context.navigationController?.pushViewController(itemViewController, animated: animated) + } + } + + return nil + } +} + +extension ClientRootViewController : InlineMessageCenter { + public func hasInlineMessage(for item: OCItem) -> Bool { + guard let activeSyncRecordIDs = item.activeSyncRecordIDs, let syncRecordIDsWithMessages = self.syncRecordIDsWithMessages else { + return false + } + + return syncRecordIDsWithMessages.contains { (syncRecordID) -> Bool in + return activeSyncRecordIDs.contains(syncRecordID) + } + } + + public func showInlineMessageFor(item: OCItem) { + if let messages = self.messageSelector?.selection, + let firstMatchingMessage = messages.first(where: { (message) -> Bool in + guard let syncRecordID = message.syncIssue?.syncRecordID, let containsSyncRecordID = item.activeSyncRecordIDs?.contains(syncRecordID) else { + return false + } + + return containsSyncRecordID + }) { + firstMatchingMessage.showInApp() + } + } +} diff --git a/ownCloud/Client/ClientRootViewController.swift b/ownCloud/Client/ClientRootViewController.swift index 8646f0e51..21ae4c15a 100644 --- a/ownCloud/Client/ClientRootViewController.swift +++ b/ownCloud/Client/ClientRootViewController.swift @@ -372,9 +372,17 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa } } + var rootContext : ClientContext? + func coreReady(_ lastVisibleItemId: String?) { OnMainThread { if let core = self.core { + self.rootContext = ClientContext(core: core, rootViewController: self, progressSummarizer: self.progressSummarizer, modifier: { context in + context.inlineMessageCenter = self + context.openItemHandler = self + context.moreItemHandler = self + }) + core.vault.resourceManager?.add(ResourceSourceItemIcons(core: core)) if let localItemId = lastVisibleItemId { @@ -383,10 +391,10 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa let topLevelViewController : UIViewController? if core.useDrives { - let composedDataSource = OCDataSourceComposition(sources: [ - core.hierarchicDrivesDataSource, - core.projectDrivesDataSource - ], applyCustomizations: { (composedDataSource) in +// let composedDataSource = OCDataSourceComposition(sources: [ +// core.hierarchicDrivesDataSource, +// core.projectDrivesDataSource +// ], applyCustomizations: { (composedDataSource) in // composedDataSource.sortComparator = OCDataSourceComposition.itemComparator(withItemRetrieval: false, fromRecordComparator: { record1, record2 in // var presentable1 : OCDataItemPresentable? // var presentable2 : OCDataItemPresentable? @@ -442,9 +450,9 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa // // return false // }), for: core.projectDrivesDataSource) - }) +// }) - topLevelViewController = CollectionViewController(core: core, rootViewController: self, sections: [ + topLevelViewController = CollectionViewController(context: ClientContext(with: self.rootContext, navigationController: self.filesNavigationController), sections: [ // CollectionViewSection(identifier: "composed", dataSource: composedDataSource) CollectionViewSection(identifier: "top", dataSource: core.hierarchicDrivesDataSource), diff --git a/ownCloud/Client/FileList Extensions/ClientQueryViewController+InlineMessageSupport.swift b/ownCloud/Client/FileList Extensions/ClientQueryViewController+InlineMessageSupport.swift index ddbb5455e..2cdf23104 100644 --- a/ownCloud/Client/FileList Extensions/ClientQueryViewController+InlineMessageSupport.swift +++ b/ownCloud/Client/FileList Extensions/ClientQueryViewController+InlineMessageSupport.swift @@ -20,7 +20,7 @@ import UIKit import ownCloudSDK import ownCloudAppShared -extension ClientQueryViewController : InlineMessageSupport { +extension ClientQueryViewController : InlineMessageCenter { public func hasInlineMessage(for item: OCItem) -> Bool { guard let activeSyncRecordIDs = item.activeSyncRecordIDs, let syncRecordIDsWithMessages = (clientRootViewController as? ClientRootViewController)?.syncRecordIDsWithMessages else { return false diff --git a/ownCloud/Client/PhotoSelectionViewController.swift b/ownCloud/Client/PhotoSelectionViewController.swift index 7389d904e..f01da6577 100644 --- a/ownCloud/Client/PhotoSelectionViewController.swift +++ b/ownCloud/Client/PhotoSelectionViewController.swift @@ -22,6 +22,7 @@ import PhotosUI import ownCloudApp import ownCloudAppShared import CoreServices +import UniformTypeIdentifiers private extension UICollectionView { func indexPathsForElements(in rect: CGRect) -> [IndexPath] { @@ -32,7 +33,7 @@ private extension UICollectionView { private extension PHAssetResource { var isRaw: Bool { - self.type == .alternatePhoto || self.uniformTypeIdentifier == String(kUTTypeRawImage) || self.uniformTypeIdentifier == AVFileType.dng.rawValue + self.type == .alternatePhoto || self.uniformTypeIdentifier == UTType.rawImage.identifier || self.uniformTypeIdentifier == AVFileType.dng.rawValue } } diff --git a/ownCloud/Client/Viewer/DisplayExtension.swift b/ownCloud/Client/Viewer/DisplayExtension.swift index 976076007..b5f4b9479 100644 --- a/ownCloud/Client/Viewer/DisplayExtension.swift +++ b/ownCloud/Client/Viewer/DisplayExtension.swift @@ -19,6 +19,7 @@ import UIKit import ownCloudSDK import CoreServices +import UniformTypeIdentifiers protocol DisplayExtension where Self: DisplayViewController { @@ -50,15 +51,15 @@ extension DisplayExtension { return displayExtension } - static func mimeTypeConformsTo(mime: String, utTypeClass: CFString) -> Bool { + static func mimeTypeConformsTo(mime: String, utType: UTType) -> Bool { // Quick check if mime type looks plausible to avoid expensive lookups done by CoreServices APIs guard !mime.contains(" ") && mime.contains("/") else { return false } - guard let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mime as CFString, nil) else { + guard let uti = UTType(mimeType: mime) else { return false } - return UTTypeConformsTo(uti.takeUnretainedValue(), utTypeClass) + return uti.conforms(to: utType) } } diff --git a/ownCloud/Client/Viewer/Media/MediaDisplayViewController.swift b/ownCloud/Client/Viewer/Media/MediaDisplayViewController.swift index c41514fc9..645bd9062 100644 --- a/ownCloud/Client/Viewer/Media/MediaDisplayViewController.swift +++ b/ownCloud/Client/Viewer/Media/MediaDisplayViewController.swift @@ -22,6 +22,7 @@ import MediaPlayer import ownCloudSDK import ownCloudAppShared import CoreServices +import UniformTypeIdentifiers class MediaDisplayViewController : DisplayViewController { @@ -426,7 +427,7 @@ extension MediaDisplayViewController: DisplayExtension { static var customMatcher: OCExtensionCustomContextMatcher? = { (context, defaultPriority) in if let mimeType = context.location?.identifier?.rawValue { - if MediaDisplayViewController.mimeTypeConformsTo(mime: mimeType, utTypeClass: kUTTypeAudiovisualContent) { + if MediaDisplayViewController.mimeTypeConformsTo(mime: mimeType, utType: UTType.audiovisualContent) { return OCExtensionPriority.locationMatch } } diff --git a/ownCloud/Media Uploads/PhotoKit Extensions/PHAsset+Upload.swift b/ownCloud/Media Uploads/PhotoKit Extensions/PHAsset+Upload.swift index eb31cd126..d1c789ac3 100644 --- a/ownCloud/Media Uploads/PhotoKit Extensions/PHAsset+Upload.swift +++ b/ownCloud/Media Uploads/PhotoKit Extensions/PHAsset+Upload.swift @@ -21,6 +21,7 @@ import ownCloudSDK import ownCloudAppShared import ownCloudApp import CoreServices +import UniformTypeIdentifiers extension URL { var audioVideoAssetType : AVFileType? { @@ -79,9 +80,9 @@ extension PHAssetResource { switch uti { case AVFileType.jpg.rawValue: ext = "jpg" - case String(kUTTypePNG): + case UTType.png.identifier: ext = "png" - case String(kUTTypeGIF): + case UTType.gif.identifier: ext = "gif" case AVFileType.heic.rawValue: ext = "heic" @@ -93,9 +94,9 @@ extension PHAssetResource { ext = "mp4" case AVFileType.m4v.rawValue: ext = "m4v" - case String(kUTTypeTIFF): + case UTType.tiff.identifier: ext = "tiff" - case String(kUTTypeRawImage), AVFileType.dng.rawValue: + case UTType.rawImage.identifier, AVFileType.dng.rawValue: ext = "dng" default: break diff --git a/ownCloud/Settings/LogFilesViewController.swift b/ownCloud/Settings/LogFilesViewController.swift index feb02b546..9da9e20f8 100644 --- a/ownCloud/Settings/LogFilesViewController.swift +++ b/ownCloud/Settings/LogFilesViewController.swift @@ -18,6 +18,7 @@ import UIKit import CoreServices +import UniformTypeIdentifiers import ownCloudSDK import ownCloudApp @@ -189,7 +190,7 @@ class LogFilesViewController : UITableViewController, UITableViewDragDelegate, T if let logURL = logURL { let itemProvider = NSItemProvider() - itemProvider.registerFileRepresentation(forTypeIdentifier: kUTTypeUTF8PlainText as String, fileOptions: [], visibility: .all, loadHandler: { (completionHandler) -> Progress? in + itemProvider.registerFileRepresentation(forTypeIdentifier: UTType.utf8PlainText.identifier, fileOptions: [], visibility: .all, loadHandler: { (completionHandler) -> Progress? in completionHandler(logURL, true, nil) return nil }) diff --git a/ownCloud/Tools/PasswordManagerAccess.swift b/ownCloud/Tools/PasswordManagerAccess.swift index 704015f39..92805e9dd 100644 --- a/ownCloud/Tools/PasswordManagerAccess.swift +++ b/ownCloud/Tools/PasswordManagerAccess.swift @@ -18,6 +18,7 @@ import UIKit import CoreServices +import UniformTypeIdentifiers let PasswordManagerAccessErrorDomain : NSErrorDomain = "PasswordManagerAccessErrorDomain" @@ -78,9 +79,9 @@ class PasswordManagerAccess { if let attachments = extensionItem.attachments, attachments.count > 0, - let itemProvider = extensionItem.attachments?.first, itemProvider.hasItemConformingToTypeIdentifier(kUTTypePropertyList as String) { + let itemProvider = extensionItem.attachments?.first, itemProvider.hasItemConformingToTypeIdentifier(UTType.propertyList.identifier) { - itemProvider.loadItem(forTypeIdentifier: kUTTypePropertyList as String, options: nil) { (itemDictionary, error) in + itemProvider.loadItem(forTypeIdentifier: UTType.propertyList.identifier, options: nil) { (itemDictionary, error) in if error == nil, let credentialsDict = itemDictionary as? [String:Any], credentialsDict.count > 0 { diff --git a/ownCloudAppFramework/Notifications/NotificationManager.m b/ownCloudAppFramework/Notifications/NotificationManager.m index a7d1698df..62c44a5cd 100644 --- a/ownCloudAppFramework/Notifications/NotificationManager.m +++ b/ownCloudAppFramework/Notifications/NotificationManager.m @@ -108,7 +108,7 @@ - (void)requestAuthorizationWithOptions:(UNAuthorizationOptions)options completi #pragma mark - Delegate - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler { - completionHandler(UNNotificationPresentationOptionAlert|UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionSound); + completionHandler(UNNotificationPresentationOptionList|UNNotificationPresentationOptionBanner|UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionSound); } - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler diff --git a/ownCloudAppShared/Client/Collection Views/Cells/ExpandableResourceCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/ExpandableResourceCell.swift index fe51dd9ea..83d004ae0 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/ExpandableResourceCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/ExpandableResourceCell.swift @@ -19,17 +19,28 @@ import UIKit import ownCloudSDK -class ExpandableResourceCell: UICollectionViewListCell { +class ExpandableResourceCell: UICollectionViewListCell, Themeable { override init(frame: CGRect) { super.init(frame: frame) configure() configureLayout() + + Theme.shared.register(client: self, applyImmediately: true) } required init?(coder: NSCoder) { fatalError() } + deinit { + Theme.shared.unregister(client: self) + } + + func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { + self.contentView.backgroundColor = collection.tableBackgroundColor + expandButton.tintColor = collection.tintColor + } + var resourceView: UIView? { willSet { resourceView?.removeFromSuperview() @@ -45,6 +56,7 @@ class ExpandableResourceCell: UICollectionViewListCell { } var expandButton : UIButton = UIButton() + var shadowView : ShadowBarView = ShadowBarView() var resourceEdgeInsets: UIEdgeInsets = UIEdgeInsets(top: 5, left: 10, bottom: 5, right: 10) @@ -63,6 +75,7 @@ class ExpandableResourceCell: UICollectionViewListCell { func configure() { expandButton.translatesAutoresizingMaskIntoConstraints = false + shadowView.translatesAutoresizingMaskIntoConstraints = false let configuration = UIImage.SymbolConfiguration(pointSize: 32, weight: .regular) @@ -75,6 +88,7 @@ class ExpandableResourceCell: UICollectionViewListCell { }), for: .primaryActionTriggered) contentView.addSubview(expandButton) + contentView.addSubview(shadowView) contentView.clipsToBounds = true collapsedConstraint = contentView.heightAnchor.constraint(lessThanOrEqualToConstant: collapsedHeight).with(priority: .required) @@ -83,17 +97,22 @@ class ExpandableResourceCell: UICollectionViewListCell { var collapsedConstraint: NSLayoutConstraint? func configureLayout() { - guard let textView = resourceView else { return } + guard let resourceView = resourceView else { return } NSLayoutConstraint.activate([ - textView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: resourceEdgeInsets.left), - textView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -resourceEdgeInsets.right), - textView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: resourceEdgeInsets.top), - textView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -resourceEdgeInsets.bottom).with(priority: .defaultHigh), + resourceView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: resourceEdgeInsets.left), + resourceView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -resourceEdgeInsets.right), + resourceView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: resourceEdgeInsets.top), + resourceView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -resourceEdgeInsets.bottom).with(priority: .defaultHigh), expandButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -resourceEdgeInsets.right), expandButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -resourceEdgeInsets.bottom), + shadowView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + shadowView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + shadowView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + shadowView.heightAnchor.constraint(equalToConstant: 10), + separatorLayoutGuide.leadingAnchor.constraint(equalTo: contentView.leadingAnchor) ]) @@ -125,8 +144,10 @@ class ExpandableResourceCell: UICollectionViewListCell { if let height = resourceView?.frame.size.height { if (height + resourceEdgeInsets.top + resourceEdgeInsets.bottom) > collapsedHeight { expandButton.isHidden = false + shadowView.isHidden = false } else { expandButton.isHidden = true + shadowView.isHidden = true } } } diff --git a/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift index d7aefe0b0..5d65eda3e 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift @@ -20,41 +20,6 @@ import UIKit import ownCloudSDK import ownCloudApp -/* -public protocol OpenItemHandling { - @discardableResult func open(item: OCItem, animated: Bool, pushViewController: Bool) -> UIViewController? -} - -public protocol MoreItemHandling { - @discardableResult func moreOptions(for item: OCItem, at location: OCExtensionLocationIdentifier, core: OCCore, query: OCQuery?, sender: AnyObject?) -> Bool -} - -public protocol RevealItemHandling { - @discardableResult func reveal(item: OCItem, core: OCCore, sender: AnyObject?) -> Bool - func showReveal(at path: IndexPath) -> Bool -} - -public protocol InlineMessageSupport { - func hasInlineMessage(for item: OCItem) -> Bool - func showInlineMessageFor(item: OCItem) -} -*/ - -public protocol ItemCellDelegate: class { - func moreButtonTapped(cell: ItemListCell, item: OCItem) - func messageButtonTapped(cell: ItemListCell, item: OCItem) - func revealButtonTapped(cell: ItemListCell, item: OCItem) -} - -open class ItemCellActionHandlers { - weak var openItemHandler: OpenItemHandling? - weak var moreItemHandler: MoreItemHandling? - weak var revealItemHandler: RevealItemHandling? - weak var inlineMessageHandler: InlineMessageSupport? - - weak var delegate: ItemCellDelegate? -} - open class ItemListCell: UICollectionViewListCell { private let horizontalMargin : CGFloat = 15 private let verticalLabelMargin : CGFloat = 10 @@ -68,17 +33,17 @@ open class ItemListCell: UICollectionViewListCell { private let revealButtonWidth : CGFloat = 35 private let verticalLabelMarginFromCenter : CGFloat = 2 private let iconSize : CGSize = CGSize(width: 40, height: 40) - private let thumbnailSize : CGSize = CGSize(width: 60, height: 60) + private let thumbnailSize : CGSize = CGSize(width: 60, height: 60) // when changing size, also update .iconView/fallbackSize - open weak var actionHandlers: ItemCellActionHandlers? { + open weak var clientContext: ClientContext? { didSet { - isMoreButtonPermanentlyHidden = (actionHandlers?.moreItemHandler as? MoreItemHandling == nil) + isMoreButtonPermanentlyHidden = (clientContext?.moreItemHandler as? MoreItemAction == nil) } } open var titleLabel : UILabel = UILabel() open var detailLabel : UILabel = UILabel() - open var iconView : ResourceViewHost = ResourceViewHost() + open var iconView : ResourceViewHost = ResourceViewHost(fallbackSize: CGSize(width: 60, height: 60)) // when changing size, also update .thumbnailSize open var cloudStatusIconView : UIImageView = UIImageView() open var sharedStatusIconView : UIImageView = UIImageView() open var publicLinkStatusIconView : UIImageView = UIImageView() @@ -351,7 +316,7 @@ open class ItemListCell: UICollectionViewListCell { } // Set has message - self.hasMessageForItem = actionHandlers?.inlineMessageHandler?.hasInlineMessage(for: item) ?? false + self.hasMessageForItem = clientContext?.inlineMessageCenter?.hasInlineMessage(for: item) ?? false // Set the icon and initiate thumbnail generation let thumbnailRequest = OCResourceRequestItemThumbnail.request(for: item, maximumSize: self.thumbnailSize, scale: 0, waitForConnectivity: true, changeHandler: nil) @@ -457,7 +422,7 @@ open class ItemListCell: UICollectionViewListCell { OnMainThread { [weak self] in let oldMessageForItem = self?.hasMessageForItem ?? false - if let item = self?.item, let hasMessage = self?.actionHandlers?.inlineMessageHandler?.hasInlineMessage(for: item) { + if let item = self?.item, let hasMessage = self?.clientContext?.inlineMessageCenter?.hasInlineMessage(for: item) { self?.hasMessageForItem = hasMessage } else { self?.hasMessageForItem = false @@ -622,19 +587,29 @@ open class ItemListCell: UICollectionViewListCell { // MARK: - Actions @objc open func moreButtonTapped() { - if let item = item { - self.actionHandlers?.delegate?.moreButtonTapped(cell: self, item: item) + guard let item = item, let clientContext = clientContext else { + return + } + + if let moreItemHandling = clientContext.moreItemHandler { + moreItemHandling.moreOptions(for: item, at: .moreItem, context: clientContext, sender: self) } } + @objc open func messageButtonTapped() { - if let item = item { - self.actionHandlers?.inlineMessageHandler?.showInlineMessageFor(item: item) + guard let item = item, let clientContext = clientContext else { + return } + + clientContext.inlineMessageCenter?.showInlineMessageFor(item: item) } + @objc open func revealButtonTapped() { - if let item = item { - self.actionHandlers?.delegate?.revealButtonTapped(cell: self, item: item) + guard let item = item, let clientContext = clientContext else { + return } + + clientContext.revealItemHandler?.reveal(item: item, context: clientContext, sender: self) } } @@ -644,11 +619,7 @@ extension ItemListCell { if let cellConfiguration = collectionItemRef.ocCellConfiguration { var itemRecord = cellConfiguration.record - switch cellConfiguration.style { - case .item(let actionHandlers): cell.actionHandlers = actionHandlers - default: break - } - + cell.clientContext = cellConfiguration.clientContext cell.core = cellConfiguration.core if itemRecord == nil { diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift index a7bd9326e..5b8160e5b 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift @@ -20,10 +20,10 @@ import UIKit import ownCloudSDK public enum CollectionViewCellStyle { - case regular case header case footer - case item(actionHandlers: ItemCellActionHandlers) + case tableCell + case gridCell } public class CollectionViewCellConfiguration: NSObject { @@ -34,10 +34,11 @@ public class CollectionViewCellConfiguration: NSObject { public var record: OCDataItemRecord? public weak var hostViewController: CollectionViewController? + public weak var clientContext: ClientContext? public var style : CollectionViewCellStyle - public init(source: OCDataSource? = nil, core: OCCore? = nil, collectionItemRef: CollectionViewController.ItemRef? = nil, record: OCDataItemRecord? = nil, hostViewController: CollectionViewController?, style: CollectionViewCellStyle = .regular) { + public init(source: OCDataSource? = nil, core: OCCore? = nil, collectionItemRef: CollectionViewController.ItemRef? = nil, record: OCDataItemRecord? = nil, hostViewController: CollectionViewController?, style: CollectionViewCellStyle = .tableCell, clientContext: ClientContext? = nil) { self.style = style super.init() @@ -47,6 +48,7 @@ public class CollectionViewCellConfiguration: NSObject { self.collectionItemRef = collectionItemRef self.record = record self.hostViewController = hostViewController + self.clientContext = clientContext } } diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift index 7d759069a..947feed43 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift @@ -21,21 +21,21 @@ import ownCloudSDK public class CollectionViewController: UIViewController, UICollectionViewDelegate { - public weak var core: OCCore? - public weak var rootViewController: UIViewController? + public var clientContext: ClientContext? public var supportsHierarchicContent: Bool - public init(core inCore: OCCore?, rootViewController inRootViewController: UIViewController?, sections inSections: [CollectionViewSection]?, hierarchic: Bool = false, listAppearance inListAppearance: UICollectionLayoutListConfiguration.Appearance = .insetGrouped) { + public init(context inContext: ClientContext?, sections inSections: [CollectionViewSection]?, hierarchic: Bool = false, listAppearance inListAppearance: UICollectionLayoutListConfiguration.Appearance = .insetGrouped) { supportsHierarchicContent = hierarchic listAppearance = inListAppearance super.init(nibName: nil, bundle: nil) - core = inCore - rootViewController = inRootViewController + inContext?.postInitialize(owner: self) - if let core = inCore { + clientContext = inContext + + if let core = clientContext?.core { self.navigationItem.title = core.bookmark.shortName } @@ -205,17 +205,19 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat } public func handleSelection(of record: OCDataItemRecord, at indexPath: IndexPath) -> Bool { - if let core = self.core, let rootViewController = self.rootViewController { - if let drive = record.item as? OCDrive { - let query = OCQuery(for: drive.rootLocation) - let rootFolderViewController = ClientItemViewController(core: core, drive: drive, query: query, rootViewController: rootViewController) + if let drive = record.item as? OCDrive { + let driveContext = ClientContext(with: clientContext, modifier: { context in + context.drive = drive + }) + let query = OCQuery(for: drive.rootLocation) + + let rootFolderViewController = ClientItemViewController(context: driveContext, query: query) - collectionView.deselectItem(at: indexPath, animated: true) + collectionView.deselectItem(at: indexPath, animated: true) - self.navigationController?.pushViewController(rootFolderViewController, animated: true) + self.navigationController?.pushViewController(rootFolderViewController, animated: true) - return true - } + return true } return false diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift index 1c704ee5b..4bb12fb33 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift @@ -40,6 +40,7 @@ public class CollectionViewSection: NSObject { weak public var collectionViewController : CollectionViewController? public var cellStyle : CollectionViewCellStyle //!< Use .cellConfigurationCustomizer for per-cell styling + public var clientContext: ClientContext? public var cellConfigurationCustomizer : CellConfigurationCustomizer? func updateDatasourceSubscription() { @@ -50,12 +51,13 @@ public class CollectionViewSection: NSObject { } } - public init(identifier: SectionIdentifier, dataSource inDataSource: OCDataSource?, cellStyle : CollectionViewCellStyle = .regular ) { + public init(identifier: SectionIdentifier, dataSource inDataSource: OCDataSource?, cellStyle : CollectionViewCellStyle = .tableCell, clientContext: ClientContext? = nil ) { self.identifier = identifier self.cellStyle = cellStyle super.init() + self.clientContext = clientContext self.dataSource = inDataSource updateDatasourceSubscription() // dataSource.didSet is not called during initialization } @@ -93,7 +95,7 @@ public class CollectionViewSection: NSObject { } if let cellProvider = cellProvider, let dataSource = dataSource { - let cellConfiguration = CollectionViewCellConfiguration(source: dataSource, core: collectionViewController?.core, collectionItemRef: collectionItemRef, record: itemRecord, hostViewController: collectionViewController, style: cellStyle) + let cellConfiguration = CollectionViewCellConfiguration(source: dataSource, core: collectionViewController?.clientContext?.core, collectionItemRef: collectionItemRef, record: itemRecord, hostViewController: collectionViewController, style: cellStyle, clientContext: clientContext) if let cellConfigurationCustomizer = cellConfigurationCustomizer { cellConfigurationCustomizer(collectionView, cellConfiguration, itemRecord, collectionItemRef, indexPath) diff --git a/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift similarity index 61% rename from ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift rename to ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift index cadc6ee71..d4af456d5 100644 --- a/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift @@ -21,8 +21,6 @@ import ownCloudSDK import ownCloudApp public class ClientItemViewController: CollectionViewController { - - public weak var drive: OCDrive? public var query: OCQuery? public var queryItemDataSourceSection : CollectionViewSection? @@ -34,24 +32,31 @@ public class ClientItemViewController: CollectionViewController { private var singleDriveDatasourceSubscription : OCDataSourceSubscription? public var driveAdditionalItemsDataSource : OCDataSourceArray = OCDataSourceArray() - public var itemActionHandlers : ItemCellActionHandlers - - public init(core inCore: OCCore, drive inDrive: OCDrive?, query inQuery: OCQuery, reveal inItem: OCItem? = nil, rootViewController: UIViewController?) { - drive = inDrive + public init(context inContext: ClientContext?, query inQuery: OCQuery, reveal inItem: OCItem? = nil) { query = inQuery var sections : [ CollectionViewSection ] = [] - itemActionHandlers = ItemCellActionHandlers() + let itemControllerContext = ClientContext(with: inContext) + itemControllerContext.postInitializationModifier = { (owner, context) in + if context.openItemHandler == nil { + context.openItemHandler = owner as? OpenItemAction + } + if context.moreItemHandler == nil { + context.moreItemHandler = owner as? MoreItemAction + } + + context.query = (owner as? ClientItemViewController)?.query + } - if let queryDatasource = query?.queryResultsDataSource { - singleDriveDatasource = OCDataSourceComposition(sources: [inCore.drivesDataSource]) + if let queryDatasource = query?.queryResultsDataSource, let core = itemControllerContext.core { + singleDriveDatasource = OCDataSourceComposition(sources: [core.drivesDataSource]) if query?.queryLocation?.isRoot == true { // Create data source from one drive singleDriveDatasource?.filter = OCDataSourceComposition.itemFilter(withItemRetrieval: false, fromRecordFilter: { itemRecord in if let drive = itemRecord?.item as? OCDrive { - if drive.identifier == inDrive?.identifier { + if drive.identifier == itemControllerContext.drive?.identifier { return true } } @@ -66,7 +71,7 @@ public class ClientItemViewController: CollectionViewController { driveSection = CollectionViewSection(identifier: "drive", dataSource: driveSectionDataSource, cellStyle: .header) } - queryItemDataSourceSection = CollectionViewSection(identifier: "items", dataSource: queryDatasource, cellStyle: .item(actionHandlers: itemActionHandlers)) + queryItemDataSourceSection = CollectionViewSection(identifier: "items", dataSource: queryDatasource, clientContext: itemControllerContext) if let driveSection = driveSection { sections.append(driveSection) @@ -77,13 +82,7 @@ public class ClientItemViewController: CollectionViewController { } } - super.init(core: inCore, rootViewController: rootViewController, sections: sections, listAppearance: .plain) - - // Configure item actions handlers - itemActionHandlers.inlineMessageHandler = rootViewController as? InlineMessageSupport - itemActionHandlers.moreItemHandler = self as? MoreItemHandling - itemActionHandlers.openItemHandler = self as? OpenItemHandling - itemActionHandlers.delegate = self + super.init(context: itemControllerContext, sections: sections, listAppearance: .plain) // Subscribe to singleDriveDatasource for changes, to update driveSectionDataSource singleDriveDatasourceSubscription = singleDriveDatasource?.subscribe(updateHandler: { [weak self] subscription in @@ -92,7 +91,7 @@ public class ClientItemViewController: CollectionViewController { query?.sortComparator = SortMethod.alphabetically.comparator(direction: .ascendant) - if let navigationTitle = query?.queryLocation?.isRoot == true ? drive?.name : query?.queryLocation?.lastPathComponent { + if let navigationTitle = query?.queryLocation?.isRoot == true ? clientContext?.drive?.name : query?.queryLocation?.lastPathComponent { navigationItem.title = navigationTitle } } @@ -109,7 +108,7 @@ public class ClientItemViewController: CollectionViewController { super.viewWillAppear(animated) if let query = query { - core?.start(query) + clientContext?.core?.start(query) } } @@ -117,24 +116,27 @@ public class ClientItemViewController: CollectionViewController { super.viewWillDisappear(animated) if let query = query { - core?.stop(query) + clientContext?.core?.stop(query) } } public override func handleSelection(of record: OCDataItemRecord, at indexPath: IndexPath) -> Bool { - if let core = self.core, let rootViewController = self.rootViewController { - if let item = record.item as? OCItem, let location = item.location { - collectionView.deselectItem(at: indexPath, animated: true) - - if let openHandler = itemActionHandlers.openItemHandler { - openHandler.open(item: item, animated: true, pushViewController: true) - } else { - let rootFolderViewController = ClientItemViewController(core: core, drive: drive, query: OCQuery(for: location), rootViewController: rootViewController) - self.navigationController?.pushViewController(rootFolderViewController, animated: true) - } - - return true + if let item = record.item as? OCItem, let location = item.location, let clientContext = clientContext { + collectionView.deselectItem(at: indexPath, animated: true) + + if let openHandler = clientContext.openItemHandler { + openHandler.open(item: item, context: clientContext, animated: true, pushViewController: true) + } else { + let rootFolderViewController = ClientItemViewController(context: clientContext, query: OCQuery(for: location)) + self.navigationController?.pushViewController(rootFolderViewController, animated: true) } + + return true + } + + if (record.item as? OCDrive) != nil { + // Do not react to taps on the drive header cells (=> or show image in the future) + return false } return super.handleSelection(of: record, at: indexPath) @@ -143,7 +145,8 @@ public class ClientItemViewController: CollectionViewController { public func updateAdditionalDriveItems(from subscription: OCDataSourceSubscription) { let snapshot = subscription.snapshotResettingChangeTracking(true) - if let firstItemRef = snapshot.items.first, + if let core = clientContext?.core, + let firstItemRef = snapshot.items.first, let itemRecord = try? subscription.source?.record(forItemRef: firstItemRef), let drive = itemRecord?.item as? OCDrive, let driveRepresentation = OCDataRenderer.default.renderItem(drive, asType: .presentable, error: nil) as? OCDataItemPresentable, @@ -156,42 +159,9 @@ public class ClientItemViewController: CollectionViewController { } } - core?.vault.resourceManager?.start(descriptionResourceRequest) + core.vault.resourceManager?.start(descriptionResourceRequest) } } var _actionProgressHandler : ActionProgressHandler? - - open func makeActionProgressHandler() -> ActionProgressHandler { - if _actionProgressHandler == nil { - _actionProgressHandler = { [weak self] (progress, publish) in - if publish { - //!! self?.progressSummarizer?.startTracking(progress: progress) - } else { - //!! self?.progressSummarizer?.stopTracking(progress: progress) - } - } - } - - return _actionProgressHandler! - } -} - -extension ClientItemViewController: ItemCellDelegate { - public func moreButtonTapped(cell: ItemListCell, item: OCItem) { - guard let core = core, let query = query else { - return - } - - if let moreItemHandling = self as? MoreItemHandling { - moreItemHandling.moreOptions(for: item, at: .moreItem, core: core, query: query, sender: cell) - } - } - - public func messageButtonTapped(cell: ItemListCell, item: OCItem) { - } - - public func revealButtonTapped(cell: ItemListCell, item: OCItem) { - } } - diff --git a/ownCloudAppShared/Client/Context/ClientContext.swift b/ownCloudAppShared/Client/Context/ClientContext.swift new file mode 100644 index 000000000..99792ef58 --- /dev/null +++ b/ownCloudAppShared/Client/Context/ClientContext.swift @@ -0,0 +1,97 @@ +// +// ClientContext.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 25.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK + +// ClientContext: +// - used to encapsulate and pass around all important API / UI objects to all parts of the UI +// - allow customization for specific purposes through primitive "inheritance" +// - can be passed around "strongly" while storing OCCore reference "weakly" (structural barrier to accidential retains) + +public protocol OpenItemAction : class { + @discardableResult func open(item: OCItem, context: ClientContext, animated: Bool, pushViewController: Bool) -> UIViewController? +} + +public protocol MoreItemAction : class { + @discardableResult func moreOptions(for item: OCItem, at location: OCExtensionLocationIdentifier, context: ClientContext, sender: AnyObject?) -> Bool +} + +public protocol RevealItemAction : class { + @discardableResult func reveal(item: OCItem, context: ClientContext, sender: AnyObject?) -> Bool + func showReveal(at path: IndexPath) -> Bool +} + +public protocol InlineMessageCenter : class { + func hasInlineMessage(for item: OCItem) -> Bool + func showInlineMessageFor(item: OCItem) +} + +public class ClientContext: NSObject { + public weak var parent: ClientContext? + + // MARK: - Core + public weak var core: OCCore? + + // MARK: - Drive + public var drive: OCDrive? + public weak var query: OCQuery? + + // MARK: - UI objects + public weak var rootViewController: UIViewController? + public weak var navigationController: UINavigationController? // Navigation controller to push to + public weak var progressSummarizer: ProgressSummarizer? + + // MARK: - UI item handling + public weak var openItemHandler: OpenItemAction? + public weak var moreItemHandler: MoreItemAction? + public weak var revealItemHandler: RevealItemAction? + public weak var inlineMessageCenter: InlineMessageCenter? + + // MARK: - Post Initialization Modifier + // allows postponing of a client context passed into another object until the object it is passed into is initialized and can be referenced + public typealias PostInitializationModifier = (_ owner: Any?, _ context: ClientContext) -> Void + public var postInitializationModifier: PostInitializationModifier? + + public init(with inParent: ClientContext? = nil, core inCore: OCCore? = nil, drive inDrive: OCDrive? = nil, rootViewController inRootViewController : UIViewController? = nil, navigationController inNavigationController: UINavigationController? = nil, progressSummarizer inProgressSummarizer: ProgressSummarizer? = nil, modifier: ((_ context: ClientContext) -> Void)? = nil) { + super.init() + + parent = inParent + + core = inCore ?? inParent?.core + + drive = inDrive ?? inParent?.drive + query = inParent?.query + + rootViewController = inRootViewController ?? inParent?.rootViewController + navigationController = inNavigationController ?? inParent?.navigationController + progressSummarizer = inProgressSummarizer ?? inParent?.progressSummarizer + + openItemHandler = inParent?.openItemHandler + moreItemHandler = inParent?.moreItemHandler + revealItemHandler = inParent?.revealItemHandler + inlineMessageCenter = inParent?.inlineMessageCenter + + modifier?(self) + } + + public func postInitialize(owner: Any?) { + postInitializationModifier?(owner, self) + postInitializationModifier = nil + } +} diff --git a/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift b/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift index 3ddcad51d..0dd2937d5 100644 --- a/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift +++ b/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift @@ -20,6 +20,7 @@ import UIKit import ownCloudSDK import ownCloudApp import CoreServices +import UniformTypeIdentifiers public typealias ClientActionVieDidAppearHandler = () -> Void public typealias ClientActionCompletionHandler = (_ actionPerformed: Bool) -> Void @@ -704,21 +705,21 @@ extension ClientQueryViewController: UITableViewDropDelegate { // Import Items from outside let typeIdentifiers = item.dragItem.itemProvider.registeredTypeIdentifiers let preferredUTIs = [ - kUTTypeImage, - kUTTypeMovie, - kUTTypePDF, - kUTTypeText, - kUTTypeRTF, - kUTTypeHTML, - kUTTypePlainText + UTType.image, + UTType.movie, + UTType.pdf, + UTType.text, + UTType.rtf, + UTType.html, + UTType.plainText ] var useUTI : String? var useIndex : Int = Int.max for typeIdentifier in typeIdentifiers { - if typeIdentifier != ItemDataUTI, !typeIdentifier.hasPrefix("dyn.") { + if typeIdentifier != ItemDataUTI, !typeIdentifier.hasPrefix("dyn."), let typeIdentifierUTI = UTType(typeIdentifier) { for preferredUTI in preferredUTIs { - let conforms = UTTypeConformsTo(typeIdentifier as CFString, preferredUTI) + let conforms = typeIdentifierUTI.conforms(to: preferredUTI) // Log.log("\(preferredUTI) vs \(typeIdentifier) -> \(conforms)") @@ -737,7 +738,7 @@ extension ClientQueryViewController: UITableViewDropDelegate { } if useUTI == nil { - useUTI = kUTTypeData as String + useUTI = UTType.data.identifier } var fileName: String? @@ -747,11 +748,11 @@ extension ClientQueryViewController: UITableViewDropDelegate { let fileNameMaxLength = 16 - if useUTI == kUTTypeUTF8PlainText as String { + if useUTI == UTType.utf8PlainText.identifier { fileName = try? String(String(contentsOf: url, encoding: .utf8).prefix(fileNameMaxLength) + ".txt") } - if useUTI == kUTTypeRTF as String { + if useUTI == UTType.rtf.identifier { let options = [NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.rtf] fileName = try? String(NSAttributedString(url: url, options: options, documentAttributes: nil).string.prefix(fileNameMaxLength) + ".rtf") } @@ -866,15 +867,13 @@ extension ClientQueryViewController: UITableViewDragDelegate { case .file: guard let itemMimeType = item.mimeType else { return nil } - - let mimeTypeCF = itemMimeType as CFString - guard let rawUti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeTypeCF, nil)?.takeRetainedValue() as String? else { return nil } + guard let itemUTI = UTType(mimeType: itemMimeType)?.identifier else { return nil } let itemProvider = NSItemProvider() itemProvider.suggestedName = item.name - itemProvider.registerFileRepresentation(forTypeIdentifier: rawUti, fileOptions: [], visibility: .all, loadHandler: { [weak core] (completionHandler) -> Progress? in + itemProvider.registerFileRepresentation(forTypeIdentifier: itemUTI, fileOptions: [], visibility: .all, loadHandler: { [weak core] (completionHandler) -> Progress? in var progress : Progress? guard let core = core else { diff --git a/ownCloudAppShared/Client/File Lists/FileListTableViewController.swift b/ownCloudAppShared/Client/File Lists/FileListTableViewController.swift index a06bc4b99..14611fde8 100644 --- a/ownCloudAppShared/Client/File Lists/FileListTableViewController.swift +++ b/ownCloudAppShared/Client/File Lists/FileListTableViewController.swift @@ -32,11 +32,6 @@ public protocol RevealItemHandling : class { func showReveal(at path: IndexPath) -> Bool } -public protocol InlineMessageSupport : class { - func hasInlineMessage(for item: OCItem) -> Bool - func showInlineMessageFor(item: OCItem) -} - open class FileListTableViewController: UITableViewController, ClientItemCellDelegate, Themeable { open weak var core : OCCore? @@ -106,7 +101,7 @@ open class FileListTableViewController: UITableViewController, ClientItemCellDel // MARK: - Inline message support open func hasMessage(for item: OCItem) -> Bool { - if let inlineMessageSupport = self as? InlineMessageSupport { + if let inlineMessageSupport = self as? InlineMessageCenter { return inlineMessageSupport.hasInlineMessage(for: item) } @@ -115,7 +110,7 @@ open class FileListTableViewController: UITableViewController, ClientItemCellDel open func messageButtonTapped(cell: ClientItemCell) { if let item = cell.item { - if let inlineMessageSupport = self as? InlineMessageSupport { + if let inlineMessageSupport = self as? InlineMessageCenter { inlineMessageSupport.showInlineMessageFor(item: item) } } diff --git a/ownCloudAppShared/Client/Sharing/PublicLinkTableViewController.swift b/ownCloudAppShared/Client/Sharing/PublicLinkTableViewController.swift index bad0ba49e..074796b1a 100644 --- a/ownCloudAppShared/Client/Sharing/PublicLinkTableViewController.swift +++ b/ownCloudAppShared/Client/Sharing/PublicLinkTableViewController.swift @@ -19,6 +19,7 @@ import UIKit import ownCloudSDK import CoreServices +import UniformTypeIdentifiers open class PublicLinkTableViewController: SharingTableViewController { @@ -343,7 +344,7 @@ extension PublicLinkTableViewController: UITableViewDragDelegate { public func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { if let share = share(at: indexPath), let url = share.url { - let itemProvider = NSItemProvider(item: url as URL as NSSecureCoding, typeIdentifier: kUTTypeURL as String) + let itemProvider = NSItemProvider(item: url as URL as NSSecureCoding, typeIdentifier: UTType.url.identifier) let dragItem = UIDragItem(itemProvider: itemProvider) return [dragItem] diff --git a/ownCloudAppShared/Client/User Interface/ClientItemCell.swift b/ownCloudAppShared/Client/User Interface/ClientItemCell.swift index 02e205834..abd767c16 100644 --- a/ownCloudAppShared/Client/User Interface/ClientItemCell.swift +++ b/ownCloudAppShared/Client/User Interface/ClientItemCell.swift @@ -186,9 +186,9 @@ open class ClientItemCell: ThemeTableViewCell { messageButton.isPointerInteractionEnabled = true messageButton.isHidden = true - moreButton.addTarget(self, action: #selector(moreButtonTapped), for: .touchUpInside) - revealButton.addTarget(self, action: #selector(revealButtonTapped), for: .touchUpInside) - messageButton.addTarget(self, action: #selector(messageButtonTapped), for: .touchUpInside) + moreButton.addTarget(self, action: #selector(moreButtonTapped), for: .primaryActionTriggered) + revealButton.addTarget(self, action: #selector(revealButtonTapped), for: .primaryActionTriggered) + messageButton.addTarget(self, action: #selector(messageButtonTapped), for: .primaryActionTriggered) sharedStatusIconView.setContentHuggingPriority(.required, for: .vertical) sharedStatusIconView.setContentHuggingPriority(.required, for: .horizontal) diff --git a/ownCloudAppShared/Client/User Interface/GradientView.swift b/ownCloudAppShared/Client/User Interface/GradientView.swift new file mode 100644 index 000000000..4dcd6a4e4 --- /dev/null +++ b/ownCloudAppShared/Client/User Interface/GradientView.swift @@ -0,0 +1,87 @@ +// +// GradientView.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 21.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit + +public class GradientView : UIView { + public var colors: [CGColor] { + didSet { + gradientLayer?.colors = colors + } + } + public var locations: [NSNumber] { + didSet { + gradientLayer?.locations = locations + } + } + + var gradientLayer : CAGradientLayer? + + public init(with colors: [CGColor], locations: [NSNumber]) { + self.colors = colors + self.locations = locations + + super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 20)) + + backgroundColor = .clear + + gradientLayer = CAGradientLayer() + gradientLayer?.colors = colors + gradientLayer?.locations = locations + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public var bounds: CGRect { + didSet { + gradientLayer?.frame = CGRect(x: 0, y: 0, width: bounds.size.width, height: bounds.size.height) + + if let gradientLayer = gradientLayer, gradientLayer.superlayer == nil { + self.layer.addSublayer(gradientLayer) + } + } + } + + public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + // Be transparant to touch events + return nil + } +} + +public class ShadowBarView : GradientView, Themeable { + public func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { + if let tableTintColor = collection.tableRowColors.tintColor { + self.colors = [tableTintColor.withAlphaComponent(0).cgColor, tableTintColor.withAlphaComponent(0.1).cgColor, tableTintColor.withAlphaComponent(0.25).cgColor] + } + } + + public init() { + super.init(with: [UIColor(hex: 0, alpha: 0).cgColor, UIColor(hex: 0, alpha: 0.10).cgColor, UIColor(hex: 0, alpha: 0.25).cgColor], locations: [0.0, 0.9, 1.0]) + Theme.shared.register(client: self, applyImmediately: true) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + Theme.shared.unregister(client: self) + } +} diff --git a/ownCloudAppShared/SDK Extensions/OCItem+Extension.swift b/ownCloudAppShared/SDK Extensions/OCItem+Extension.swift index 744a1ef73..c8074ac31 100644 --- a/ownCloudAppShared/SDK Extensions/OCItem+Extension.swift +++ b/ownCloudAppShared/SDK Extensions/OCItem+Extension.swift @@ -19,17 +19,18 @@ import UIKit import ownCloudSDK import CoreServices +import UniformTypeIdentifiers extension OCItem { public var isPlayable: Bool { guard let mime = self.mimeType else { return false } - guard let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mime as CFString, nil) else { + guard let uti = UTType(mimeType: mime) else { return false } - return UTTypeConformsTo(uti.takeUnretainedValue(), kUTTypeAudiovisualContent) + return uti.conforms(to: .audiovisualContent) } static private let iconNamesByMIMEType : [String:String] = { diff --git a/ownCloudAppShared/User Interface/Theme/Resources/ThemeImage.swift b/ownCloudAppShared/User Interface/Theme/Resources/ThemeImage.swift index e28019fb5..5b6a58268 100644 --- a/ownCloudAppShared/User Interface/Theme/Resources/ThemeImage.swift +++ b/ownCloudAppShared/User Interface/Theme/Resources/ThemeImage.swift @@ -25,7 +25,7 @@ enum ThemeImageType { typealias ThemeImageRenderer = (_ image: ThemeImage, _ theme : Theme, _ ThemeCollection: ThemeCollection) -> UIImage? -class ThemeImage : Themeable { +class ThemeImage : NSObject, Themeable { var type : ThemeImageType = .image var renderer : ThemeImageRenderer? diff --git a/ownCloudAppShared/User Interface/Theme/Theme.swift b/ownCloudAppShared/User Interface/Theme/Theme.swift index e77e46de2..272c58f25 100644 --- a/ownCloudAppShared/User Interface/Theme/Theme.swift +++ b/ownCloudAppShared/User Interface/Theme/Theme.swift @@ -24,23 +24,15 @@ public enum ThemeEvent { case update } -public protocol Themeable : class { +public protocol Themeable : NSObject { func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) } public typealias ThemeApplier = (_ theme : Theme, _ ThemeCollection: ThemeCollection, _ event: ThemeEvent) -> Void public typealias ThemeApplierToken = Int -final class WeakThemeable { - weak var weakClient : Themeable? - - init(_ client: Themeable) { - weakClient = client - } -} - public class Theme: NSObject { - private var weakClients : [WeakThemeable] = [] + private var weakClients : NSHashTable = NSHashTable.weakObjects() private var appliers : [ThemeApplierToken : ThemeApplier] = [:] private var applierSerial : ThemeApplierToken = 0 @@ -72,7 +64,7 @@ public class Theme: NSObject { // MARK: - Client register / unregister public func register(client: Themeable, applyImmediately: Bool = true) { OCSynchronized(self) { - weakClients.append(WeakThemeable(client)) + weakClients.add(client) } if applyImmediately { @@ -82,14 +74,7 @@ public class Theme: NSObject { public func unregister(client : Themeable) { OCSynchronized(self) { - if let clientIndex = weakClients.index(where: { (themable) -> Bool in - if themable.weakClient != nil { - return themable.weakClient === client - } - return false - }) { - weakClients.remove(at: clientIndex) - } + weakClients.remove(client) } } @@ -104,7 +89,7 @@ public class Theme: NSObject { public func add(resource: ThemeResource) { OCSynchronized(self) { - weakClients.insert(WeakThemeable(resource), at: 0) + weakClients.add(resource) if resource.identifier != nil { resourcesByIdentifier[resource.identifier!] = resource @@ -218,9 +203,9 @@ public class Theme: NSObject { public func applyThemeCollection(_ collection: ThemeCollection) { OCSynchronized(self) { // Apply theme to clients - for client in weakClients { - if client.weakClient != nil { - client.weakClient?.applyThemeCollection(theme: self, collection: collection, event: .update) + for client in weakClients.allObjects { + if let client = client as? Themeable { + client.applyThemeCollection(theme: self, collection: collection, event: .update) } } diff --git a/ownCloudAppShared/User Interface/Theme/UI/ThemeTableViewCell.swift b/ownCloudAppShared/User Interface/Theme/UI/ThemeTableViewCell.swift index 1211992e0..959547d77 100644 --- a/ownCloudAppShared/User Interface/Theme/UI/ThemeTableViewCell.swift +++ b/ownCloudAppShared/User Interface/Theme/UI/ThemeTableViewCell.swift @@ -185,10 +185,6 @@ open class ThemeTableViewCell: UITableViewCell, Themeable { textColor = collection.tableRowColors.labelColor backgroundColor = collection.tableRowColors.backgroundColor - case .text: - textColor = collection.tableRowColors.labelColor - backgroundColor = collection.tableRowColors.backgroundColor - case .confirmation: textColor = collection.approvalColors.normal.foreground backgroundColor = collection.approvalColors.normal.background diff --git a/ownCloudAppShared/View Providers/OCResourceText+ViewProvider.swift b/ownCloudAppShared/View Providers/OCResourceText+ViewProvider.swift index 1a92619ba..1ca54403c 100644 --- a/ownCloudAppShared/View Providers/OCResourceText+ViewProvider.swift +++ b/ownCloudAppShared/View Providers/OCResourceText+ViewProvider.swift @@ -20,6 +20,47 @@ import UIKit import ownCloudSDK import Down +class ThemeableTextView : UITextView, Themeable { + init() { + registeredThemeable = false + super.init(frame: .zero, textContainer: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + Theme.shared.unregister(client: self) + } + + private var registeredThemeable : Bool + + func registerThemeable() { + if !registeredThemeable { + registeredThemeable = true + Theme.shared.register(client: self, applyImmediately: true) + } + } + + override var attributedText: NSAttributedString! { + didSet { + registerThemeable() + } + } + + override var text: String! { + didSet { + registerThemeable() + } + } + + func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { + backgroundColor = collection.tableBackgroundColor + textColor = collection.tableRowColors.labelColor + } +} + extension OCResourceText : OCViewProvider { public func provideView(for size: CGSize, in context: OCViewProviderContext?, completion completionHandler: @escaping (UIView?) -> Void) { var attributedText : NSAttributedString? @@ -41,7 +82,7 @@ extension OCResourceText : OCViewProvider { } } - let textView = UITextView() + let textView = ThemeableTextView() textView.translatesAutoresizingMaskIntoConstraints = false From dd3239bea1a342a7ab173ca45225f09a14adad55 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Mon, 25 Apr 2022 23:11:58 +0200 Subject: [PATCH 024/328] - ownCloudTests: fix compilation issues due to API changes --- ownCloudTests/File List/Subclasses/MockOCCore.swift | 2 +- ownCloudTests/File List/Subclasses/MockOCQuery.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ownCloudTests/File List/Subclasses/MockOCCore.swift b/ownCloudTests/File List/Subclasses/MockOCCore.swift index 95af77c76..7e4cd33d4 100644 --- a/ownCloudTests/File List/Subclasses/MockOCCore.swift +++ b/ownCloudTests/File List/Subclasses/MockOCCore.swift @@ -30,7 +30,7 @@ class MockOCCore: OCCore { return nil } - override func suggestUnusedNameBased(on name: String, atPath path: String, isDirectory: Bool, using nameStyle: OCCoreDuplicateNameStyle, filteredBy filter: OCCoreUnusedNameSuggestionFilter?, resultHandler: @escaping OCCoreUnusedNameSuggestionResultHandler) { + override func suggestUnusedNameBased(on name: String, at location: OCLocation, isDirectory: Bool, using nameStyle: OCCoreDuplicateNameStyle, filteredBy filter: OCCoreUnusedNameSuggestionFilter?, resultHandler: @escaping OCCoreUnusedNameSuggestionResultHandler) { resultHandler(name, nil) } diff --git a/ownCloudTests/File List/Subclasses/MockOCQuery.swift b/ownCloudTests/File List/Subclasses/MockOCQuery.swift index da0c54256..7552313da 100644 --- a/ownCloudTests/File List/Subclasses/MockOCQuery.swift +++ b/ownCloudTests/File List/Subclasses/MockOCQuery.swift @@ -12,7 +12,7 @@ import ownCloudSDK class MockOCQuery: OCQuery { convenience init(path: String) { - self.init(forPath: path) + self.init(for: OCLocation.legacyRootPath(path)) let rootItem = OCItem() rootItem.permissions = [.createFile, .createFolder, .delete, .move, .rename, .writable] rootItem.path = "/" From 01edb55063c4231ecf26ddccd72a3407224dd876 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Tue, 26 Apr 2022 11:28:35 +0200 Subject: [PATCH 025/328] - migrate app to Swift 5 - fix indentation in several places --- ios-sdk | 2 +- .../ShareViewController.swift | 4 +- ownCloud.xcodeproj/project.pbxproj | 20 +-------- .../ImportPasteboardAction.swift | 2 +- ownCloud/Client/ClientActivityCell.swift | 2 +- .../Client/ClientRootViewController.swift | 2 +- ownCloud/Client/ClientSessionManager.swift | 2 +- .../Client/Viewer/DisplayViewController.swift | 2 +- .../Viewer/PDF/PDFSearchResultsView.swift | 2 +- .../FileProviderInterfaceManager.swift | 2 +- ownCloud/Key Commands/KeyCommands.swift | 4 +- ownCloud/Messages/MessageGroupCell.swift | 2 +- ownCloud/Migration/Migration.swift | 4 +- .../OCCertificate+Extension.swift | 44 +++++++++---------- .../ServerListTableViewController.swift | 4 +- ownCloud/Settings/DataSettingsSection.swift | 2 +- .../StaticLoginSetupViewController.swift | 23 +++++----- .../Licensing/OCLicenseTypes.h | 4 +- .../Licensing/Offer/OCLicenseOffer.h | 2 +- .../CollectionViewSection.swift | 2 +- .../ClientItemViewController.swift | 2 +- .../Client/Context/ClientContext.swift | 8 ++-- .../ClientQueryViewController.swift | 2 +- .../FileListTableViewController.swift | 6 +-- .../QueryFileListTableViewController.swift | 2 +- .../GroupSharingTableViewController.swift | 2 +- .../User Interface/ClientItemCell.swift | 18 ++++---- .../Client/User Interface/MessageView.swift | 2 +- .../Client/User Interface/SortBar.swift | 10 ++--- .../UIKit Extension/LAContext+Extension.swift | 43 +++++++++--------- .../Cursor Support/PointerEffect.swift | 2 +- .../Progress/ProgressSummarizer.swift | 10 ++--- .../StaticTableViewController.swift | 12 ++--- .../StaticTableView/StaticTableViewRow.swift | 2 +- .../StaticTableViewSection.swift | 4 +- .../Theme/NSObject+ThemeApplication.swift | 4 +- .../Theme/UI/ThemeNavigationController.swift | 2 +- .../User Interface/UserInterfaceContext.swift | 2 +- 38 files changed, 125 insertions(+), 139 deletions(-) diff --git a/ios-sdk b/ios-sdk index a6a3eb149..b75bb08f8 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit a6a3eb1496dff2b3e56e5a2f1ca8422b7b66169f +Subproject commit b75bb08f8f265e3c524ebcb03ebfcee4cc4b9c3a diff --git a/ownCloud Share Extension/ShareViewController.swift b/ownCloud Share Extension/ShareViewController.swift index ab55532e7..d31799d76 100644 --- a/ownCloud Share Extension/ShareViewController.swift +++ b/ownCloud Share Extension/ShareViewController.swift @@ -106,7 +106,7 @@ class ShareViewController: MoreStaticTableViewController { OCCoreManager.shared.requestCore(for: bookmark, setup: nil, completionHandler: { (core, error) in if error != nil { // Remove only one entry, not all for that bookmark - if let index = self.requestedCoreBookmarks.index(of: bookmark) { + if let index = self.requestedCoreBookmarks.firstIndex(of: bookmark) { self.requestedCoreBookmarks.remove(at: index) } } @@ -122,7 +122,7 @@ class ShareViewController: MoreStaticTableViewController { func returnCore(for bookmark: OCBookmark, completionHandler: @escaping () -> Void) { OCCoreManager.shared.returnCore(for: bookmark, completionHandler: { // Remove only one entry, not all for that bookmark - if let index = self.requestedCoreBookmarks.index(of: bookmark) { + if let index = self.requestedCoreBookmarks.firstIndex(of: bookmark) { self.requestedCoreBookmarks.remove(at: index) } diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 9877fcde0..a5dcec60e 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -4939,6 +4939,7 @@ SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(APP_BUILD_FLAGS_SWIFT)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; VALIDATE_WORKSPACE = YES; }; name = Debug; @@ -5005,6 +5006,7 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(APP_BUILD_FLAGS_SWIFT)"; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; VALIDATE_PRODUCT = YES; VALIDATE_WORKSPACE = YES; }; @@ -5035,7 +5037,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; STRINGS_FILE_OUTPUT_ENCODING = "UTF-16"; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; @@ -5066,7 +5067,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.owncloud.ios-app"; STRINGS_FILE_OUTPUT_ENCODING = "UTF-16"; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; @@ -5088,7 +5088,6 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.owncloud.ownCloudTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ownCloud.app/ownCloud"; }; @@ -5110,7 +5109,6 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.owncloud.ownCloudTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ownCloud.app/ownCloud"; }; @@ -5149,7 +5147,6 @@ SKIP_INSTALL = YES; STRINGS_FILE_OUTPUT_ENCODING = "UTF-8"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -5183,7 +5180,6 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; STRINGS_FILE_OUTPUT_ENCODING = "UTF-8"; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -5215,7 +5211,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -5240,7 +5235,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.owncloud.ios-app.ownCloud-Intents"; SKIP_INSTALL = YES; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -5270,7 +5264,6 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.owncloud.ios-app.ownCloud-File-ProviderUI"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -5296,7 +5289,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.owncloud.ios-app.ownCloud-File-ProviderUI"; SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -5328,7 +5320,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.owncloud.ownCloudScreenshotsTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = ownCloud; }; @@ -5356,7 +5347,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.owncloud.ownCloudScreenshotsTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = ownCloud; }; @@ -5372,7 +5362,6 @@ MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -5386,7 +5375,6 @@ MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_VERSION = 4.0; }; name = Release; }; @@ -5424,7 +5412,6 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -5459,7 +5446,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.owncloud.ownCloudApp; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -5586,7 +5572,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; STRINGS_FILE_OUTPUT_ENCODING = "UTF-8"; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -5614,7 +5599,6 @@ PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.owncloud.ios-app.ownCloud-Share-Extension"; SKIP_INSTALL = YES; STRINGS_FILE_OUTPUT_ENCODING = "UTF-8"; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; diff --git a/ownCloud/Client/Actions/Actions+Extensions/ImportPasteboardAction.swift b/ownCloud/Client/Actions/Actions+Extensions/ImportPasteboardAction.swift index 4fba94a12..a29438918 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/ImportPasteboardAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/ImportPasteboardAction.swift @@ -177,7 +177,7 @@ class ImportPasteboardAction : Action { // Log.log("\(preferredUTI) vs \(typeIdentifier) -> \(conforms)") if conforms { - if let utiIndex = preferredUTIs.index(of: preferredUTI), utiIndex < useIndex { + if let utiIndex = preferredUTIs.firstIndex(of: preferredUTI), utiIndex < useIndex { useUTI = typeIdentifier useIndex = utiIndex } diff --git a/ownCloud/Client/ClientActivityCell.swift b/ownCloud/Client/ClientActivityCell.swift index 5d31fd0e4..ee7a90b3b 100644 --- a/ownCloud/Client/ClientActivityCell.swift +++ b/ownCloud/Client/ClientActivityCell.swift @@ -20,7 +20,7 @@ import UIKit import ownCloudSDK import ownCloudAppShared -protocol ClientActivityCellDelegate : class { +protocol ClientActivityCellDelegate : AnyObject { func showMessage(for activity: OCActivity) diff --git a/ownCloud/Client/ClientRootViewController.swift b/ownCloud/Client/ClientRootViewController.swift index 21ae4c15a..efc7bf1bb 100644 --- a/ownCloud/Client/ClientRootViewController.swift +++ b/ownCloud/Client/ClientRootViewController.swift @@ -21,7 +21,7 @@ import ownCloudSDK import ownCloudApp import ownCloudAppShared -protocol ClientRootViewControllerAuthenticationDelegate : class { +protocol ClientRootViewControllerAuthenticationDelegate : AnyObject { func handleAuthError(for clientViewController: ClientRootViewController, error: NSError, editBookmark: OCBookmark?, preferredAuthenticationMethods: [OCAuthenticationMethodIdentifier]?) } diff --git a/ownCloud/Client/ClientSessionManager.swift b/ownCloud/Client/ClientSessionManager.swift index 954ac16ec..68c17649d 100644 --- a/ownCloud/Client/ClientSessionManager.swift +++ b/ownCloud/Client/ClientSessionManager.swift @@ -21,7 +21,7 @@ import ownCloudSDK import ownCloudApp import ownCloudAppShared -protocol ClientSessionManagerDelegate : class { +protocol ClientSessionManagerDelegate : AnyObject { func canPresent(bookmark: OCBookmark, message: OCMessage?) -> OCMessagePresentationPriority func present(bookmark: OCBookmark, message: OCMessage?) } diff --git a/ownCloud/Client/Viewer/DisplayViewController.swift b/ownCloud/Client/Viewer/DisplayViewController.swift index 4f3062290..98ba47de2 100644 --- a/ownCloud/Client/Viewer/DisplayViewController.swift +++ b/ownCloud/Client/Viewer/DisplayViewController.swift @@ -32,7 +32,7 @@ enum DisplayViewState { case previewFailed } -protocol DisplayViewEditingDelegate: class { +protocol DisplayViewEditingDelegate: AnyObject { func save(item: OCItem, fileURL newVersion: URL) } diff --git a/ownCloud/Client/Viewer/PDF/PDFSearchResultsView.swift b/ownCloud/Client/Viewer/PDF/PDFSearchResultsView.swift index bab58ff5c..02bf744c2 100644 --- a/ownCloud/Client/Viewer/PDF/PDFSearchResultsView.swift +++ b/ownCloud/Client/Viewer/PDF/PDFSearchResultsView.swift @@ -40,7 +40,7 @@ class PDFSearchResultsView : UIView { var currentMatch: PDFSelection? { didSet { if let match = currentMatch, let matches = self.matches { - if let index = matches.index(of: match), let matchString = match.string { + if let index = matches.firstIndex(of: match), let matchString = match.string { currentIndex = index let searchResultsText = "\(matchString) (" + String(format: "%@ of %@".localized, "\(index + 1)", "\(matches.count)") + ")" searchTermButton.setTitle(searchResultsText, for: .normal) diff --git a/ownCloud/FileProvider Integration/FileProviderInterfaceManager.swift b/ownCloud/FileProvider Integration/FileProviderInterfaceManager.swift index 9956d4a4b..3d7dece40 100644 --- a/ownCloud/FileProvider Integration/FileProviderInterfaceManager.swift +++ b/ownCloud/FileProvider Integration/FileProviderInterfaceManager.swift @@ -85,7 +85,7 @@ class FileProviderInterfaceManager: NSObject { let domainIdentifierString = domain.identifier.rawValue var removeDomain : Bool = false - if let removeAtIndex = bookmarkUUIDStrings.index(of: domainIdentifierString) { + if let removeAtIndex = bookmarkUUIDStrings.firstIndex(of: domainIdentifierString) { // Domain is already registered for this bookmark -> check if name also still matches if displayNamesByUUIDString[domainIdentifierString] == domain.displayName { // Identical -> no changes needed for this bookmark diff --git a/ownCloud/Key Commands/KeyCommands.swift b/ownCloud/Key Commands/KeyCommands.swift index 4a1e6d71b..bcd27903c 100644 --- a/ownCloud/Key Commands/KeyCommands.swift +++ b/ownCloud/Key Commands/KeyCommands.swift @@ -335,7 +335,7 @@ extension ClientRootViewController { @objc func switchTheme(sender: UIKeyCommand) { if let availableStyles = ThemeStyle.availableStyles { - let currentIndex = availableStyles.index(of: ThemeStyle.preferredStyle) ?? 0 + let currentIndex = availableStyles.firstIndex(of: ThemeStyle.preferredStyle) ?? 0 var newStyle = ThemeStyle.preferredStyle if currentIndex + 1 < availableStyles.count { newStyle = availableStyles[currentIndex + 1] @@ -938,7 +938,7 @@ extension QueryFileListTableViewController { let firstItem = self.items.filter { (( $0.name?.uppercased().hasPrefix(title) ?? nil)! ) }.first if let firstItem = firstItem { - if let itemIndex = self.items.index(of: firstItem) { + if let itemIndex = self.items.firstIndex(of: firstItem) { let indexPath = IndexPath(row: itemIndex, section: 0) tableView.scrollToRow(at: indexPath, at: UITableView.ScrollPosition.middle, animated: false) tableView.selectRow(at: indexPath, animated: true, scrollPosition: .middle) diff --git a/ownCloud/Messages/MessageGroupCell.swift b/ownCloud/Messages/MessageGroupCell.swift index ee47026c9..f8000d8ac 100644 --- a/ownCloud/Messages/MessageGroupCell.swift +++ b/ownCloud/Messages/MessageGroupCell.swift @@ -20,7 +20,7 @@ import UIKit import ownCloudSDK import ownCloudAppShared -protocol MessageGroupCellDelegate : class { +protocol MessageGroupCellDelegate : AnyObject { func cell(_ cell: MessageGroupCell, showMessagesLike: OCMessage) } diff --git a/ownCloud/Migration/Migration.swift b/ownCloud/Migration/Migration.swift index fad6e662a..355e1bce6 100644 --- a/ownCloud/Migration/Migration.swift +++ b/ownCloud/Migration/Migration.swift @@ -180,7 +180,7 @@ class Migration { self.migrationQueue.async { // Check if the passcode is set let passcodeQuery = OCSQLiteQuery(selectingColumns: ["passcode", "is_touch_id"], fromTable: "passcode", where: nil, orderBy: "id DESC", limit: "1") { (_, _, _, resultSet) in - if let dict = try? resultSet?.nextRowDictionary(), let passcode = dict?["passcode"] as? String { + if let dict = try? resultSet?.nextRowDictionary(), let passcode = dict["passcode"] as? String { let activityName = "App Passcode".localized self.postAccountMigrationNotification(activity: activityName, state: .initiated, type: .passcode) @@ -191,7 +191,7 @@ class Migration { AppLockManager.shared.passcode = passcode AppLockSettings.shared.lockEnabled = true - if let biometricalIdEnabled = dict?["is_touch_id"] as? Bool { + if let biometricalIdEnabled = dict["is_touch_id"] as? Bool { AppLockSettings.shared.biometricalSecurityEnabled = biometricalIdEnabled } diff --git a/ownCloud/SDK Extensions/OCCertificate+Extension.swift b/ownCloud/SDK Extensions/OCCertificate+Extension.swift index 9441d0c76..3ae4bfc04 100644 --- a/ownCloud/SDK Extensions/OCCertificate+Extension.swift +++ b/ownCloud/SDK Extensions/OCCertificate+Extension.swift @@ -31,28 +31,28 @@ extension OCCertificate { switch status { - case .none: - break - case .error: - color = Theme.shared.activeCollection.errorColor - shortDescription = "Error".localized - longDescription = "\("Validation Error".localized) \(error.localizedDescription)" - case .reject: - color = Theme.shared.activeCollection.errorColor - shortDescription = "Rejected".localized - longDescription = "Certificate was rejected by user.".localized - case .promptUser: - color = Theme.shared.activeCollection.warningColor - shortDescription = "Warning".localized - longDescription = "Certificate has issues.\nOpen 'Certificate Details' for more informations.".localized - case .passed: - color = Theme.shared.activeCollection.successColor - shortDescription = "Passed".localized - longDescription = "No issues found. Certificate passed validation.".localized - case .userAccepted: - color = Theme.shared.activeCollection.warningColor - shortDescription = "Accepted".localized - longDescription = "Certificate may have issues, but was accepted by user.\nOpen 'Certificate Details' for more informations.".localized + case .none: + break + case .error: + color = Theme.shared.activeCollection.errorColor + shortDescription = "Error".localized + longDescription = "\("Validation Error".localized) \(error.localizedDescription)" + case .reject: + color = Theme.shared.activeCollection.errorColor + shortDescription = "Rejected".localized + longDescription = "Certificate was rejected by user.".localized + case .promptUser: + color = Theme.shared.activeCollection.warningColor + shortDescription = "Warning".localized + longDescription = "Certificate has issues.\nOpen 'Certificate Details' for more informations.".localized + case .passed: + color = Theme.shared.activeCollection.successColor + shortDescription = "Passed".localized + longDescription = "No issues found. Certificate passed validation.".localized + case .userAccepted: + color = Theme.shared.activeCollection.warningColor + shortDescription = "Accepted".localized + longDescription = "Certificate may have issues, but was accepted by user.\nOpen 'Certificate Details' for more informations.".localized } completionHandler(status, shortDescription, longDescription, color, error) }) diff --git a/ownCloud/Server List/ServerListTableViewController.swift b/ownCloud/Server List/ServerListTableViewController.swift index 84ed8c78b..86cb874b4 100644 --- a/ownCloud/Server List/ServerListTableViewController.swift +++ b/ownCloud/Server List/ServerListTableViewController.swift @@ -22,7 +22,7 @@ import ownCloudApp import ownCloudAppShared import PocketSVG -public protocol StateRestorationConnectProtocol : class { +public protocol StateRestorationConnectProtocol : AnyObject { func connect(to bookmark: OCBookmark, lastVisibleItemId: String?, animated: Bool, present message: OCMessage?) } @@ -935,7 +935,7 @@ extension OCBookmarkManager { static func unlock(bookmark: OCBookmark) { OCSynchronized(self) { - if let removeIndex = self.lockedBookmarks.index(of: bookmark) { + if let removeIndex = self.lockedBookmarks.firstIndex(of: bookmark) { self.lockedBookmarks.remove(at: removeIndex) } } diff --git a/ownCloud/Settings/DataSettingsSection.swift b/ownCloud/Settings/DataSettingsSection.swift index 063c0c9c4..5aa557cb9 100644 --- a/ownCloud/Settings/DataSettingsSection.swift +++ b/ownCloud/Settings/DataSettingsSection.swift @@ -146,7 +146,7 @@ class DataSettingsSection: SettingsSection { } let offsetForTimeInterval : (Int) -> Int = { (timeInterval) in - if let offset = timeIntervals.index(of: timeInterval) { + if let offset = timeIntervals.firstIndex(of: timeInterval) { return offset } diff --git a/ownCloud/Static Login/Interface/StaticLoginSetupViewController.swift b/ownCloud/Static Login/Interface/StaticLoginSetupViewController.swift index bd906b622..c68e653a4 100644 --- a/ownCloud/Static Login/Interface/StaticLoginSetupViewController.swift +++ b/ownCloud/Static Login/Interface/StaticLoginSetupViewController.swift @@ -589,18 +589,19 @@ class StaticLoginSetupViewController : StaticLoginStepViewController { let authMethodType = authenticationMethodClass.type as OCAuthenticationMethodType switch authMethodType { - case .passphrase: - if self.sectionForIdentifier("loginMaskSection") == nil { - self.addSection(self.loginMaskSection()) - } - case .token: - if self.sectionForIdentifier("tokenMaskSection") == nil { - self.addSection(self.tokenMaskSection()) - } + case .passphrase: + if self.sectionForIdentifier("loginMaskSection") == nil { + self.addSection(self.loginMaskSection()) + } - if self.username != nil { - self.startAuthentication(nil) - } + case .token: + if self.sectionForIdentifier("tokenMaskSection") == nil { + self.addSection(self.tokenMaskSection()) + } + + if self.username != nil { + self.startAuthentication(nil) + } } if self.profile.isOnboardingEnabled, self.sectionForIdentifier("onboardingSection") == nil { diff --git a/ownCloudAppFramework/Licensing/OCLicenseTypes.h b/ownCloudAppFramework/Licensing/OCLicenseTypes.h index 744b762ad..9473bc9c2 100644 --- a/ownCloudAppFramework/Licensing/OCLicenseTypes.h +++ b/ownCloudAppFramework/Licensing/OCLicenseTypes.h @@ -37,7 +37,7 @@ typedef NS_ENUM(NSUInteger, OCLicenseType) OCLicenseTypeTrial, //!< Trial OCLicenseTypeSubscription, //!< Subscription OCLicenseTypePurchase //!< Regular purchase -}; +} __attribute__((enum_extensibility(closed))); typedef NS_ENUM(NSUInteger, OCLicenseAuthorizationStatus) { @@ -45,7 +45,7 @@ typedef NS_ENUM(NSUInteger, OCLicenseAuthorizationStatus) OCLicenseAuthorizationStatusDenied, //!< Authorization denied OCLicenseAuthorizationStatusExpired, //!< Authorization expired, existed at some point in the past OCLicenseAuthorizationStatusGranted //!< Authorization granted -}; +} __attribute__((enum_extensibility(closed))); @class OCLicenseEnvironment; @class OCLicenseObserver; diff --git a/ownCloudAppFramework/Licensing/Offer/OCLicenseOffer.h b/ownCloudAppFramework/Licensing/Offer/OCLicenseOffer.h index 4b6c283bc..35e7d04c7 100644 --- a/ownCloudAppFramework/Licensing/Offer/OCLicenseOffer.h +++ b/ownCloudAppFramework/Licensing/Offer/OCLicenseOffer.h @@ -39,7 +39,7 @@ typedef NS_ENUM(NSUInteger, OCLicenseOfferState) OCLicenseOfferStateCommitted, //!< The user has committed to (bought) the offer. OCLicenseOfferStateExpired //!< The user has committed to the offer, but it has expired (f.ex. for subscriptions that have ended). -}; +} __attribute__((enum_extensibility(closed))); @interface OCLicenseOffer : NSObject diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift index 4bb12fb33..e0a3a98c0 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift @@ -87,7 +87,7 @@ public class CollectionViewSection: NSObject { var cell: UICollectionViewCell? if let (dataItemRef, _) = collectionViewController?.unwrap(collectionItemRef) { - if let itemRecord = try? dataSource?.record(forItemRef: dataItemRef), let itemRecord = itemRecord { + if let itemRecord = try? dataSource?.record(forItemRef: dataItemRef) { var cellProvider = CollectionViewCellProvider.providerFor(itemRecord) if cellProvider == nil { diff --git a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift index d4af456d5..e3c794351 100644 --- a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift @@ -148,7 +148,7 @@ public class ClientItemViewController: CollectionViewController { if let core = clientContext?.core, let firstItemRef = snapshot.items.first, let itemRecord = try? subscription.source?.record(forItemRef: firstItemRef), - let drive = itemRecord?.item as? OCDrive, + let drive = itemRecord.item as? OCDrive, let driveRepresentation = OCDataRenderer.default.renderItem(drive, asType: .presentable, error: nil) as? OCDataItemPresentable, let descriptionResourceRequest = try? driveRepresentation.provideResourceRequest(.coverDescription) { descriptionResourceRequest.lifetime = .singleRun diff --git a/ownCloudAppShared/Client/Context/ClientContext.swift b/ownCloudAppShared/Client/Context/ClientContext.swift index 99792ef58..b247cafc7 100644 --- a/ownCloudAppShared/Client/Context/ClientContext.swift +++ b/ownCloudAppShared/Client/Context/ClientContext.swift @@ -24,20 +24,20 @@ import ownCloudSDK // - allow customization for specific purposes through primitive "inheritance" // - can be passed around "strongly" while storing OCCore reference "weakly" (structural barrier to accidential retains) -public protocol OpenItemAction : class { +public protocol OpenItemAction : AnyObject { @discardableResult func open(item: OCItem, context: ClientContext, animated: Bool, pushViewController: Bool) -> UIViewController? } -public protocol MoreItemAction : class { +public protocol MoreItemAction : AnyObject { @discardableResult func moreOptions(for item: OCItem, at location: OCExtensionLocationIdentifier, context: ClientContext, sender: AnyObject?) -> Bool } -public protocol RevealItemAction : class { +public protocol RevealItemAction : AnyObject { @discardableResult func reveal(item: OCItem, context: ClientContext, sender: AnyObject?) -> Bool func showReveal(at path: IndexPath) -> Bool } -public protocol InlineMessageCenter : class { +public protocol InlineMessageCenter : AnyObject { func hasInlineMessage(for item: OCItem) -> Bool func showInlineMessageFor(item: OCItem) } diff --git a/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift b/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift index 0dd2937d5..e3baf76f6 100644 --- a/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift +++ b/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift @@ -724,7 +724,7 @@ extension ClientQueryViewController: UITableViewDropDelegate { // Log.log("\(preferredUTI) vs \(typeIdentifier) -> \(conforms)") if conforms { - if let utiIndex = preferredUTIs.index(of: preferredUTI), utiIndex < useIndex { + if let utiIndex = preferredUTIs.firstIndex(of: preferredUTI), utiIndex < useIndex { useUTI = typeIdentifier useIndex = utiIndex } diff --git a/ownCloudAppShared/Client/File Lists/FileListTableViewController.swift b/ownCloudAppShared/Client/File Lists/FileListTableViewController.swift index 14611fde8..8a611a9db 100644 --- a/ownCloudAppShared/Client/File Lists/FileListTableViewController.swift +++ b/ownCloudAppShared/Client/File Lists/FileListTableViewController.swift @@ -19,15 +19,15 @@ import UIKit import ownCloudSDK -public protocol OpenItemHandling : class { +public protocol OpenItemHandling : AnyObject { @discardableResult func open(item: OCItem, animated: Bool, pushViewController: Bool) -> UIViewController? } -public protocol MoreItemHandling : class { +public protocol MoreItemHandling : AnyObject { @discardableResult func moreOptions(for item: OCItem, at location: OCExtensionLocationIdentifier, core: OCCore, query: OCQuery?, sender: AnyObject?) -> Bool } -public protocol RevealItemHandling : class { +public protocol RevealItemHandling : AnyObject { @discardableResult func reveal(item: OCItem, core: OCCore, sender: AnyObject?) -> Bool func showReveal(at path: IndexPath) -> Bool } diff --git a/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift b/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift index 53daf68ec..ca0d6feb4 100644 --- a/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift +++ b/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift @@ -503,7 +503,7 @@ open class QueryFileListTableViewController: FileListTableViewController, SortBa override open func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int { let firstItem = self.items.filter { (( $0.name?.uppercased().hasPrefix(title) ?? nil)! ) }.first - if let firstItem = firstItem, let itemIndex = self.items.index(of: firstItem) { + if let firstItem = firstItem, let itemIndex = self.items.firstIndex(of: firstItem) { OnMainThread { let section = tableView.numberOfSections - 1 // in directory picker there could be more than one section, if favorites exists tableView.scrollToRow(at: IndexPath(row: itemIndex, section: section), at: UITableView.ScrollPosition.top, animated: false) diff --git a/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift b/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift index f1193a8b4..3f5d28639 100644 --- a/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift +++ b/ownCloudAppShared/Client/Sharing/GroupSharingTableViewController.swift @@ -460,7 +460,7 @@ open class GroupSharingTableViewController: SharingTableViewController, UISearch } } - func searchController(_ searchController: OCRecipientSearchController, isWaitingForResults isSearching: Bool) { + public func searchController(_ searchController: OCRecipientSearchController, isWaitingForResults isSearching: Bool) { OnMainThread { if isSearching { diff --git a/ownCloudAppShared/Client/User Interface/ClientItemCell.swift b/ownCloudAppShared/Client/User Interface/ClientItemCell.swift index abd767c16..8b28d7ac3 100644 --- a/ownCloudAppShared/Client/User Interface/ClientItemCell.swift +++ b/ownCloudAppShared/Client/User Interface/ClientItemCell.swift @@ -20,7 +20,7 @@ import UIKit import ownCloudSDK import ownCloudApp -public protocol ClientItemCellDelegate: class { +public protocol ClientItemCellDelegate: AnyObject { func moreButtonTapped(cell: ClientItemCell) func messageButtonTapped(cell: ClientItemCell) @@ -382,16 +382,16 @@ open class ClientItemCell: ThemeTableViewCell { if item.type == .file { switch item.cloudStatus { - case .cloudOnly: - cloudStatusIcon = UIImage(named: "cloud-only") - cloudStatusIconAlpha = 1.0 + case .cloudOnly: + cloudStatusIcon = UIImage(named: "cloud-only") + cloudStatusIconAlpha = 1.0 - case .localCopy: - cloudStatusIcon = (item.downloadTriggerIdentifier == OCItemDownloadTriggerID.availableOffline) ? UIImage(named: "cloud-available-offline") : nil + case .localCopy: + cloudStatusIcon = (item.downloadTriggerIdentifier == OCItemDownloadTriggerID.availableOffline) ? UIImage(named: "cloud-available-offline") : nil - case .locallyModified, .localOnly: - cloudStatusIcon = UIImage(named: "cloud-local-only") - cloudStatusIconAlpha = 1.0 + case .locallyModified, .localOnly: + cloudStatusIcon = UIImage(named: "cloud-local-only") + cloudStatusIconAlpha = 1.0 } } else { if availableOfflineCoverage == .none { diff --git a/ownCloudAppShared/Client/User Interface/MessageView.swift b/ownCloudAppShared/Client/User Interface/MessageView.swift index 13778d943..df372d048 100644 --- a/ownCloudAppShared/Client/User Interface/MessageView.swift +++ b/ownCloudAppShared/Client/User Interface/MessageView.swift @@ -220,7 +220,7 @@ open class MessageView: UIView { } @objc func keyboardWillShow(notification: Notification) { - let keyboardSize = (notification.userInfo? [UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue + let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue keyboardHeight = keyboardSize?.height ?? 0 if self.composeViewBottomConstraint != nil { diff --git a/ownCloudAppShared/Client/User Interface/SortBar.swift b/ownCloudAppShared/Client/User Interface/SortBar.swift index 3a8b9d82f..06143743a 100644 --- a/ownCloudAppShared/Client/User Interface/SortBar.swift +++ b/ownCloudAppShared/Client/User Interface/SortBar.swift @@ -51,7 +51,7 @@ public enum SearchScope : Int, CaseIterable { } } -public protocol SortBarDelegate: class { +public protocol SortBarDelegate: AnyObject { var sortDirection: SortDirection { get set } var sortMethod: SortMethod { get set } @@ -153,10 +153,10 @@ public class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate sortButton?.sizeToFit() if let sortSegmentedControl = sortSegmentedControl, sortSegmentedControl.numberOfSegments > 0 { - if let oldSementIndex = SortMethod.all.index(of: oldValue) { + if let oldSementIndex = SortMethod.all.firstIndex(of: oldValue) { sortSegmentedControl.setTitle(oldValue.localizedName, forSegmentAt: oldSementIndex) } - if let segmentIndex = SortMethod.all.index(of: sortMethod) { + if let segmentIndex = SortMethod.all.firstIndex(of: sortMethod) { sortSegmentedControl.selectedSegmentIndex = segmentIndex sortSegmentedControl.setTitle(sortDirectionTitle(sortMethod.localizedName), forSegmentAt: segmentIndex) } @@ -322,7 +322,7 @@ public class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate sortSegmentedControl.removeAllSegments() var longestTitleWidth : CGFloat = 0.0 for method in SortMethod.all { - sortSegmentedControl.insertSegment(withTitle: method.localizedName, at: SortMethod.all.index(of: method)!, animated: false) + sortSegmentedControl.insertSegment(withTitle: method.localizedName, at: SortMethod.all.firstIndex(of: method)!, animated: false) let titleWidth = method.localizedName.appending(" ↓").width(withConstrainedHeight: sortSegmentedControl.frame.size.height, font: UIFont.systemFont(ofSize: 16.0)) if titleWidth > longestTitleWidth { longestTitleWidth = titleWidth @@ -335,7 +335,7 @@ public class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate sortSegmentedControl.setWidth(longestTitleWidth, forSegmentAt: currentIndex) currentIndex += 1 } - if let segmentIndex = SortMethod.all.index(of: sortMethod) { + if let segmentIndex = SortMethod.all.firstIndex(of: sortMethod) { sortSegmentedControl.selectedSegmentIndex = segmentIndex sortSegmentedControl.setTitle(sortDirectionTitle(sortMethod.localizedName), forSegmentAt: segmentIndex) } diff --git a/ownCloudAppShared/UIKit Extension/LAContext+Extension.swift b/ownCloudAppShared/UIKit Extension/LAContext+Extension.swift index 8233889af..44254f894 100644 --- a/ownCloudAppShared/UIKit Extension/LAContext+Extension.swift +++ b/ownCloudAppShared/UIKit Extension/LAContext+Extension.swift @@ -20,32 +20,33 @@ import LocalAuthentication import UIKit extension LAContext { - - public func supportedBiometricsAuthenticationName() -> String? { - if canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) { - switch self.biometryType { - case .faceID : return "Face ID".localized - case .touchID: return "Touch ID".localized - case .none: return nil - } - } - return nil + public func supportedBiometricsAuthenticationName() -> String? { + if canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) { + switch self.biometryType { + case .faceID : return "Face ID".localized + case .touchID: return "Touch ID".localized + case .none: return nil + @unknown default: return nil + } + } + return nil } public func biometricsAuthenticationImage() -> UIImage? { if canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) { switch self.biometryType { - case .faceID : if #available(iOSApplicationExtension 13.0, *) { - return UIImage(systemName: "faceid") - } else { - return UIImage(named: "biometrical-faceid") - } - case .touchID: if #available(iOSApplicationExtension 13.0, *) { - return UIImage(systemName: "touchid") - } else { - return UIImage(named: "biometrical-touchid") - } - case .none: return nil + case .faceID : if #available(iOSApplicationExtension 13.0, *) { + return UIImage(systemName: "faceid") + } else { + return UIImage(named: "biometrical-faceid") + } + case .touchID: if #available(iOSApplicationExtension 13.0, *) { + return UIImage(systemName: "touchid") + } else { + return UIImage(named: "biometrical-touchid") + } + case .none: return nil + @unknown default: return nil } } return nil diff --git a/ownCloudAppShared/User Interface/Cursor Support/PointerEffect.swift b/ownCloudAppShared/User Interface/Cursor Support/PointerEffect.swift index a2e580f1e..a20ec0c74 100644 --- a/ownCloudAppShared/User Interface/Cursor Support/PointerEffect.swift +++ b/ownCloudAppShared/User Interface/Cursor Support/PointerEffect.swift @@ -47,7 +47,7 @@ public class PointerEffect : NSObject, UIPointerInteractionDelegate { view.addInteraction(pointerInteraction) } - private func pointerInteraction(_ interaction: UIPointerInteraction, styleFor region: UIPointerRegion) -> UIPointerStyle? { + public func pointerInteraction(_ interaction: UIPointerInteraction, styleFor region: UIPointerRegion) -> UIPointerStyle? { var pointerStyle: UIPointerStyle? if let interactionView = interaction.view { diff --git a/ownCloudAppShared/User Interface/Progress/ProgressSummarizer.swift b/ownCloudAppShared/User Interface/Progress/ProgressSummarizer.swift index e78d2e0ad..4712cbf08 100644 --- a/ownCloudAppShared/User Interface/Progress/ProgressSummarizer.swift +++ b/ownCloudAppShared/User Interface/Progress/ProgressSummarizer.swift @@ -138,7 +138,7 @@ public class ProgressSummarizer: NSObject { OCSynchronized(self) { Log.debug("Stop tracking progress \(String(describing: progress)) (remove=\(remove))") - if let trackedProgressIndex = trackedProgress.index(of: progress) { + if let trackedProgressIndex = trackedProgress.firstIndex(of: progress) { progress.removeObserver(self, forKeyPath: "fractionCompleted", context: observerContext) progress.removeObserver(self, forKeyPath: "isFinished", context: observerContext) progress.removeObserver(self, forKeyPath: "isCancelled", context: observerContext) @@ -149,7 +149,7 @@ public class ProgressSummarizer: NSObject { trackedProgress.remove(at: trackedProgressIndex) if progress.eventType != .none { - if let progressByTypeIndex = trackedProgressByType[progress.eventType]?.index(of: progress) { + if let progressByTypeIndex = trackedProgressByType[progress.eventType]?.firstIndex(of: progress) { trackedProgressByType[progress.eventType]?.remove(at: progressByTypeIndex) let remainingProgressByType = (trackedProgressByType[progress.eventType]?.count ?? 0) @@ -272,7 +272,7 @@ public class ProgressSummarizer: NSObject { OCSynchronized(self) { Log.debug("Pop fallback summary \(String(describing: summary))") - if let index = fallbackSummaries.index(of: summary) { + if let index = fallbackSummaries.firstIndex(of: summary) { fallbackSummaries.remove(at: index) if index == 0 { @@ -318,7 +318,7 @@ public class ProgressSummarizer: NSObject { OCSynchronized(self) { Log.debug("Pop priority summary \(String(describing: summary))") - if let index = prioritySummaries.index(of: summary) { + if let index = prioritySummaries.firstIndex(of: summary) { prioritySummaries.remove(at: index) if prioritySummaries.count == 0 { @@ -340,7 +340,7 @@ public class ProgressSummarizer: NSObject { public func removeObserver(_ observer: AnyObject) { OCSynchronized(self) { - if let removeIndex : Int = observers.index(where: { (observerRecord) -> Bool in + if let removeIndex : Int = observers.firstIndex(where: { (observerRecord) -> Bool in return observerRecord.observer === observer }) { observers.remove(at: removeIndex) diff --git a/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewController.swift b/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewController.swift index fba0763b6..65e62ef12 100644 --- a/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewController.swift +++ b/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewController.swift @@ -58,7 +58,7 @@ open class StaticTableViewController: UITableViewController, Themeable { open func removeSection(_ section: StaticTableViewSection, animated: Bool = false) { if animated { tableView.performBatchUpdates({ - if let index = sections.index(of: section) { + if let index = sections.firstIndex(of: section) { sections.remove(at: index) tableView.deleteSections(IndexSet(integer: index), with: .fade) } @@ -66,7 +66,7 @@ open class StaticTableViewController: UITableViewController, Themeable { section.viewController = nil }) } else { - if let sectionIndex = sections.index(of: section) { + if let sectionIndex = sections.firstIndex(of: section) { sections.remove(at: sectionIndex) section.viewController = nil @@ -101,13 +101,13 @@ open class StaticTableViewController: UITableViewController, Themeable { var removalIndexes : IndexSet = IndexSet() for section in removeSections { - if let index : Int = sections.index(of: section) { + if let index : Int = sections.firstIndex(of: section) { removalIndexes.insert(index) } } for section in removeSections { - if let index : Int = sections.index(of: section) { + if let index : Int = sections.firstIndex(of: section) { sections.remove(at: index) } } @@ -120,7 +120,7 @@ open class StaticTableViewController: UITableViewController, Themeable { }) } else { for section in removeSections { - sections.remove(at: sections.index(of: section)!) + sections.remove(at: sections.firstIndex(of: section)!) section.viewController = nil } @@ -154,7 +154,7 @@ open class StaticTableViewController: UITableViewController, Themeable { } open func indexForSection(_ inSection: StaticTableViewSection) -> Int? { - return sections.index(of: inSection) + return sections.firstIndex(of: inSection) } // MARK: - View Controller diff --git a/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift b/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift index d9314c558..72e02d3a1 100644 --- a/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift +++ b/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift @@ -105,7 +105,7 @@ open class StaticTableViewRow : NSObject, UITextFieldDelegate { private var themeApplierToken : ThemeApplierToken? public var index : Int? { - return section?.rows.index(of: self) + return section?.rows.firstIndex(of: self) } public var indexPath : IndexPath? { diff --git a/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewSection.swift b/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewSection.swift index bc0a28a1b..44ee8243c 100644 --- a/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewSection.swift +++ b/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewSection.swift @@ -32,7 +32,7 @@ open class StaticTableViewSection: NSObject { public var footerView : UIView? public var index : Int? { - return self.viewController?.sections.index(of: self) + return self.viewController?.sections.firstIndex(of: self) } public var attached : Bool { @@ -130,7 +130,7 @@ open class StaticTableViewSection: NSObject { // Finds rows to remove for row in rowsToRemove { - if let index = rows.index(of: row) { + if let index = rows.firstIndex(of: row) { // Save indexes and index paths indexes.insert(index) if sectionIndex != nil { diff --git a/ownCloudAppShared/User Interface/Theme/NSObject+ThemeApplication.swift b/ownCloudAppShared/User Interface/Theme/NSObject+ThemeApplication.swift index 4b7d743d0..f8db5a38f 100644 --- a/ownCloudAppShared/User Interface/Theme/NSObject+ThemeApplication.swift +++ b/ownCloudAppShared/User Interface/Theme/NSObject+ThemeApplication.swift @@ -59,11 +59,11 @@ public enum ThemeItemState { } } -public protocol ThemeableSectionHeader : class { +public protocol ThemeableSectionHeader : AnyObject { var sectionHeaderColor : UIColor? { get set } } -public protocol ThemeableSectionFooter : class { +public protocol ThemeableSectionFooter : AnyObject { var sectionFooterColor : UIColor? { get set } } diff --git a/ownCloudAppShared/User Interface/Theme/UI/ThemeNavigationController.swift b/ownCloudAppShared/User Interface/Theme/UI/ThemeNavigationController.swift index aaa129251..2c5841fa2 100644 --- a/ownCloudAppShared/User Interface/Theme/UI/ThemeNavigationController.swift +++ b/ownCloudAppShared/User Interface/Theme/UI/ThemeNavigationController.swift @@ -18,7 +18,7 @@ import UIKit -public protocol CustomStatusBarViewControllerProtocol : class { +public protocol CustomStatusBarViewControllerProtocol : AnyObject { func statusBarStyle() -> UIStatusBarStyle } diff --git a/ownCloudAppShared/User Interface/UserInterfaceContext.swift b/ownCloudAppShared/User Interface/UserInterfaceContext.swift index 52808a7f1..22665e663 100644 --- a/ownCloudAppShared/User Interface/UserInterfaceContext.swift +++ b/ownCloudAppShared/User Interface/UserInterfaceContext.swift @@ -18,7 +18,7 @@ import UIKit -public protocol UserInterfaceContextProvider: class { +public protocol UserInterfaceContextProvider: AnyObject { func provideRootView() -> UIView? /// provide "root-most" view for app func provideCurrentWindow() -> UIWindow? // provide front-most window of the app From f36e18e59da2ebbd0050799e65c914cd252a94c9 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Tue, 26 Apr 2022 12:36:58 +0200 Subject: [PATCH 026/328] - update SDK - squash more warnings --- ios-sdk | 2 +- .../Viewer/WebView/WebViewDisplayViewController.swift | 2 -- .../UIKit Extension/UIAlertController+OCIssue.swift | 10 ++++++++++ .../Progress/ProgressIndicatorViewController.swift | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/ios-sdk b/ios-sdk index b75bb08f8..65ce6bff5 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit b75bb08f8f265e3c524ebcb03ebfcee4cc4b9c3a +Subproject commit 65ce6bff589d844ec53011f482e69eb6da763f60 diff --git a/ownCloud/Client/Viewer/WebView/WebViewDisplayViewController.swift b/ownCloud/Client/Viewer/WebView/WebViewDisplayViewController.swift index 25e5f308e..86a78ea54 100644 --- a/ownCloud/Client/Viewer/WebView/WebViewDisplayViewController.swift +++ b/ownCloud/Client/Viewer/WebView/WebViewDisplayViewController.swift @@ -43,8 +43,6 @@ class WebViewDisplayViewController: DisplayViewController { if self.webView == nil { let configuration: WKWebViewConfiguration = WKWebViewConfiguration() - configuration.preferences.javaScriptEnabled = true - if blockList != nil { configuration.userContentController.add(blockList!) diff --git a/ownCloudAppShared/UIKit Extension/UIAlertController+OCIssue.swift b/ownCloudAppShared/UIKit Extension/UIAlertController+OCIssue.swift index 76063d4be..420d322cc 100644 --- a/ownCloudAppShared/UIKit Extension/UIAlertController+OCIssue.swift +++ b/ownCloudAppShared/UIKit Extension/UIAlertController+OCIssue.swift @@ -6,6 +6,16 @@ // Copyright © 2018 ownCloud GmbH. All rights reserved. // +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + import UIKit import ownCloudSDK diff --git a/ownCloudAppShared/User Interface/Progress/ProgressIndicatorViewController.swift b/ownCloudAppShared/User Interface/Progress/ProgressIndicatorViewController.swift index 1ebe6c794..27ad43ccd 100644 --- a/ownCloudAppShared/User Interface/Progress/ProgressIndicatorViewController.swift +++ b/ownCloudAppShared/User Interface/Progress/ProgressIndicatorViewController.swift @@ -174,7 +174,7 @@ open class ProgressIndicatorViewController: UIViewController, Themeable { ]) } else { constraints.append(contentsOf: [ - label.topAnchor.constraint(equalTo: centerView.topAnchor, constant: outerSpacing), + label.topAnchor.constraint(equalTo: centerView.topAnchor, constant: outerSpacing) ]) } From 5b86651ab9b259fbe4c33d5776702adb6766e7af Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Tue, 26 Apr 2022 16:53:35 +0200 Subject: [PATCH 027/328] - ThemeWindow: add static property .frontmostThemeWindow - OCResourceText+ViewProvider.swift: move from ownCloudAppShared to app to avoid Down linker warning - AppStatistics: switch to new API for requesting a review --- ownCloud.xcodeproj/project.pbxproj | 24 ++++++++++--------- .../OCResourceText+ViewProvider.swift | 3 ++- ownCloudAppShared/Tools/AppStatistics.swift | 6 +++-- .../User Interface/Theme/UI/ThemeWindow.swift | 16 +++++++++++++ 4 files changed, 35 insertions(+), 14 deletions(-) rename {ownCloudAppShared => ownCloud}/View Providers/OCResourceText+ViewProvider.swift (98%) diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index a5dcec60e..a436693db 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -311,7 +311,6 @@ DC3AB24428104AA500789435 /* UIFont+Weight.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3AB24328104AA500789435 /* UIFont+Weight.swift */; }; DC3AB2462810602500789435 /* UILabel+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3AB2452810602500789435 /* UILabel+Extension.swift */; }; DC3AB2482810A10300789435 /* ExpandableResourceCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3AB2472810A10300789435 /* ExpandableResourceCell.swift */; }; - DC3AB24B2810A69600789435 /* OCResourceText+ViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3AB24A2810A69600789435 /* OCResourceText+ViewProvider.swift */; }; DC3BE0D82077BC5D002A0AC0 /* ownCloudSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 239369782076110900BCE21A /* ownCloudSDK.framework */; }; DC3BE0DA2077BC6B002A0AC0 /* ownCloudSDK.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 239369782076110900BCE21A /* ownCloudSDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DC3BE0DF2077CC14002A0AC0 /* ClientRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3BE0DD2077CC13002A0AC0 /* ClientRootViewController.swift */; }; @@ -361,6 +360,7 @@ DC7C101224B5FD6500227085 /* OCBookmark+AppExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = DC7C100F24B5F81E00227085 /* OCBookmark+AppExtensions.m */; }; DC7DBA37207F84BF00E7337D /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC7DBA36207F84BF00E7337D /* main.swift */; }; DC82663C28168D2800F91F7D /* ClientContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC82663B28168D2800F91F7D /* ClientContext.swift */; }; + DC82665028183CFD00F91F7D /* OCResourceText+ViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3AB24A2810A69600789435 /* OCResourceText+ViewProvider.swift */; }; DC82D6FA23171339001551C5 /* ScanAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC82D6F923171339001551C5 /* ScanAction.swift */; }; DC854936218331CF00782BA8 /* UserInterfaceSettingsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC854935218331CF00782BA8 /* UserInterfaceSettingsSection.swift */; }; DC85572C20513B8C00189B9A /* ServerListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC85572A20513B8C00189B9A /* ServerListTableViewController.swift */; }; @@ -510,7 +510,6 @@ DCEAF06D280767CF00980B6D /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = DCEAF06C280767CF00980B6D /* OpenSSL */; }; DCEAF08A2808254800980B6D /* DriveListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCEAF0892808254800980B6D /* DriveListCell.swift */; }; DCEAF08D28084B3800980B6D /* Down in Frameworks */ = {isa = PBXBuildFile; productRef = DCEAF08C28084B3800980B6D /* Down */; }; - DCEAF08F28084B5600980B6D /* Down in Frameworks */ = {isa = PBXBuildFile; productRef = DCEAF08E28084B5600980B6D /* Down */; }; DCEE1C9C23A0EADD00FE8D98 /* LicenseOfferView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCEE1C9B23A0EADD00FE8D98 /* LicenseOfferView.swift */; }; DCF072D72798558B00E0B01D /* OCCircularImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF072D5279850CC00E0B01D /* OCCircularImageView.m */; }; DCF072D82798559900E0B01D /* OCCircularImageView.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF072D4279850CC00E0B01D /* OCCircularImageView.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1575,7 +1574,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DCEAF08F28084B5600980B6D /* Down in Frameworks */, DC04920B258CB06A00DEDC27 /* PocketSVG in Frameworks */, DCDC0ACF23CD186400DFE36D /* ownCloudApp.framework in Frameworks */, 394A0B0A22EEFCF500603813 /* ownCloudSDK.framework in Frameworks */, @@ -1744,6 +1742,7 @@ DCA35D6724CF7B6E00DBE2B0 /* Diagnostic */, DC44343721ABF9A200376B16 /* Static Login */, DC7DF17C205140F400189B9A /* Server List */, + DC3AB2492810A67F00789435 /* View Providers */, DCF4F1802051A91500189B9A /* Settings */, 39E42D152315286300B82AC3 /* Key Commands */, DC422448207CAED60006A2A6 /* Theming */, @@ -1878,7 +1877,6 @@ DCCE54092080175B00067D1D /* TVGs */, DCE4E43824C19A950051722F /* Licensing */, DC0A354F24C0E18800FB58FC /* AppLock */, - DC3AB2492810A67F00789435 /* View Providers */, DC0A354C24C0E09300FB58FC /* User Interface */, 3912208023436E9B0026C290 /* Client */, DC0A355524C0E2DD00FB58FC /* Foundation Extensions */, @@ -3392,6 +3390,7 @@ buildRules = ( ); dependencies = ( + DC82664F2818056C00F91F7D /* PBXTargetDependency */, DC63207D21FCA71B007EC0A8 /* PBXTargetDependency */, DC3BE0CF2077BC52002A0AC0 /* PBXTargetDependency */, DC3BE0D12077BC52002A0AC0 /* PBXTargetDependency */, @@ -3457,7 +3456,6 @@ name = ownCloudAppShared; packageProductDependencies = ( DC04920A258CB06A00DEDC27 /* PocketSVG */, - DCEAF08E28084B5600980B6D /* Down */, ); productName = ownCloudAppShared; productReference = 394A0AF922EEFC2C00603813 /* ownCloudAppShared.framework */; @@ -4124,6 +4122,7 @@ 025FC745247EF0F1009307A7 /* BackgroundUploadsSettingsSection.swift in Sources */, DC63208321FCAC1E007EC0A8 /* ClientActivityViewController.swift in Sources */, DC9C1AEC247C76470067895A /* MessageGroupCell.swift in Sources */, + DC82665028183CFD00F91F7D /* OCResourceText+ViewProvider.swift in Sources */, 4C464BF62187AF1500D30602 /* PDFTocItem.swift in Sources */, 6E3A103E219D5BBA00F90C96 /* RenameAction.swift in Sources */, DCC832E2242C0EAC00153F8C /* MessageSelector.swift in Sources */, @@ -4364,7 +4363,6 @@ DCD864122811FC5700CA6631 /* GradientView.swift in Sources */, DCFC9ED528002F33005D9144 /* CollectionViewCellConfiguration.swift in Sources */, DC0A355624C0E33A00FB58FC /* Log.swift in Sources */, - DC3AB24B2810A69600789435 /* OCResourceText+ViewProvider.swift in Sources */, DC0A359624C0E61500FB58FC /* UIView+Extension.swift in Sources */, DCE4E43924C19AB20051722F /* MoreStaticTableViewController.swift in Sources */, DC82663C28168D2800F91F7D /* ClientContext.swift in Sources */, @@ -4665,6 +4663,10 @@ name = libzip; targetProxy = DC774E6622F44F65000B11A1 /* PBXContainerItemProxy */; }; + DC82664F2818056C00F91F7D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = DC82664E2818056C00F91F7D /* Down */; + }; DCB2C059250C1C3F001083CA /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = DCC0855B2293F1FD008CC05C /* ownCloudApp */; @@ -5777,6 +5779,11 @@ package = DC049197258CAF8200DEDC27 /* XCRemoteSwiftPackageReference "PocketSVG" */; productName = PocketSVG; }; + DC82664E2818056C00F91F7D /* Down */ = { + isa = XCSwiftPackageProductDependency; + package = DCEAF08B28084B3800980B6D /* XCRemoteSwiftPackageReference "Down" */; + productName = Down; + }; DCD8640528115FD600CA6631 /* OpenSSL */ = { isa = XCSwiftPackageProductDependency; package = DCEAF066280767BC00980B6D /* XCRemoteSwiftPackageReference "OpenSSL" */; @@ -5792,11 +5799,6 @@ package = DCEAF08B28084B3800980B6D /* XCRemoteSwiftPackageReference "Down" */; productName = Down; }; - DCEAF08E28084B5600980B6D /* Down */ = { - isa = XCSwiftPackageProductDependency; - package = DCEAF08B28084B3800980B6D /* XCRemoteSwiftPackageReference "Down" */; - productName = Down; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 233BDE94204FEFE500C06732 /* Project object */; diff --git a/ownCloudAppShared/View Providers/OCResourceText+ViewProvider.swift b/ownCloud/View Providers/OCResourceText+ViewProvider.swift similarity index 98% rename from ownCloudAppShared/View Providers/OCResourceText+ViewProvider.swift rename to ownCloud/View Providers/OCResourceText+ViewProvider.swift index 1ca54403c..31bb14f43 100644 --- a/ownCloudAppShared/View Providers/OCResourceText+ViewProvider.swift +++ b/ownCloud/View Providers/OCResourceText+ViewProvider.swift @@ -1,6 +1,6 @@ // // OCResourceText+ViewProvider.swift -// ownCloudAppShared +// ownCloud // // Created by Felix Schwarz on 20.04.22. // Copyright © 2022 ownCloud GmbH. All rights reserved. @@ -18,6 +18,7 @@ import UIKit import ownCloudSDK +import ownCloudAppShared import Down class ThemeableTextView : UITextView, Themeable { diff --git a/ownCloudAppShared/Tools/AppStatistics.swift b/ownCloudAppShared/Tools/AppStatistics.swift index 5ef78240e..44dd18fe2 100644 --- a/ownCloudAppShared/Tools/AppStatistics.swift +++ b/ownCloudAppShared/Tools/AppStatistics.swift @@ -108,9 +108,11 @@ public class AppStatistics { } if shallRequest { - self.lastReviewPromptDate = Date() OnMainThread { - SKStoreReviewController.requestReview() + if let frontmostWindowScene = ThemeWindow.frontmostThemeWindow?.windowScene { + self.lastReviewPromptDate = Date() + SKStoreReviewController.requestReview(in: frontmostWindowScene) + } } } } diff --git a/ownCloudAppShared/User Interface/Theme/UI/ThemeWindow.swift b/ownCloudAppShared/User Interface/Theme/UI/ThemeWindow.swift index 9bd032b96..ee3cf6c55 100644 --- a/ownCloudAppShared/User Interface/Theme/UI/ThemeWindow.swift +++ b/ownCloudAppShared/User Interface/Theme/UI/ThemeWindow.swift @@ -49,6 +49,22 @@ public class ThemeWindow : UIWindow { return themeWindows } + static var frontmostThemeWindow : ThemeWindow? { + var themeWindow : ThemeWindow? + + OCSynchronized(self) { + let themeWindows = _themeWindows.allObjects + + for checkWindow in themeWindows { + if checkWindow.themeWindowInForeground { + themeWindow = checkWindow + } + } + } + + return themeWindow + } + // MARK: - Lifecycle override public init(frame: CGRect) { super.init(frame: frame) From 8aaf59bbc66cf42ddca8719df45a8561a5a58d90 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Thu, 5 May 2022 23:38:24 +0200 Subject: [PATCH 028/328] - add KNOWN_ISSUES.md to keep track of user-visible known issues alongside advances in the code - FileProvider: - FileProviderContentEnumerator: implement an NSFileProviderEnumerator driven by OCVFSContent - FileProviderExtension: adapt implementation to be based on OCVFS - OCVFSNode+FileProviderItem: make OCVFSNode comply to NSFileProviderItem - first basically functioning implementation with Spaces support - update SDK for improved VFS support --- KNOWN_ISSUES.md | 28 + ios-sdk | 2 +- .../FileProviderContentEnumerator.h | 42 ++ .../FileProviderContentEnumerator.m | 665 ++++++++++++++++++ .../FileProviderExtension.h | 3 +- .../FileProviderExtension.m | 166 +++-- .../OCItem+FileProviderItem.m | 46 +- .../OCVFSNode+FileProviderItem.h | 27 + .../OCVFSNode+FileProviderItem.m | 73 ++ ownCloud.xcodeproj/project.pbxproj | 18 +- 10 files changed, 984 insertions(+), 86 deletions(-) create mode 100644 KNOWN_ISSUES.md create mode 100644 ownCloud File Provider/FileProviderContentEnumerator.h create mode 100644 ownCloud File Provider/FileProviderContentEnumerator.m create mode 100644 ownCloud File Provider/OCVFSNode+FileProviderItem.h create mode 100644 ownCloud File Provider/OCVFSNode+FileProviderItem.m diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md new file mode 100644 index 000000000..47dd7318f --- /dev/null +++ b/KNOWN_ISSUES.md @@ -0,0 +1,28 @@ +# Known issues in version 12.0 alpha 1 + +## WARNING + +This release of version 12 is an alpha preview release and not yet ready for production or regular use. +It should only be used with dedicated test servers, test data - and test devices. + +## App +- in the new browsing experience, some features are not yet available: + - row actions + - context menus + - drag and drop + - folder actions (like "Create folder") + - search + - sorting + - a grid view + - the .space folder in the root of spaces is visible (but should not) +- spaces do not yet show a member count or provide access to a list of members +- support for sharing is widely untested + +## File Provider +- the list of spaces doesn't update dynamically +- the list of spaces may contain spaces of unsupported types +- not all actions are working correctly, especially in the root folder of spaces +- OCCores may not be managed correctly under all circumstances + +## SDK +- local storage consumed by spaces that are then deleted or inactivated is not reclaimed diff --git a/ios-sdk b/ios-sdk index 65ce6bff5..6cfd471d7 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit 65ce6bff589d844ec53011f482e69eb6da763f60 +Subproject commit 6cfd471d72aa01af71e6923e6597c5d6e67f20d5 diff --git a/ownCloud File Provider/FileProviderContentEnumerator.h b/ownCloud File Provider/FileProviderContentEnumerator.h new file mode 100644 index 000000000..f885e6fe4 --- /dev/null +++ b/ownCloud File Provider/FileProviderContentEnumerator.h @@ -0,0 +1,42 @@ +// +// FileProviderContentEnumerator.h +// ownCloud File Provider +// +// Created by Felix Schwarz on 05.05.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import +#import +#import "FileProviderEnumeratorObserver.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FileProviderContentEnumerator : NSObject +{ + NSMutableArray *_enumerationObservers; + NSMutableArray *_changeObservers; + + BOOL _contentRequested; +} + +@property(strong) OCVFSCore *vfsCore; +@property(strong) OCVFSNodeID containerItemIdentifier; + +@property(strong,nullable,nonatomic) OCVFSContent *content; + +- (instancetype)initWithVFSCore:(OCVFSCore *)vfsCore containerItemIdentifier:(NSFileProviderItemIdentifier)containerItemIdentifier; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloud File Provider/FileProviderContentEnumerator.m b/ownCloud File Provider/FileProviderContentEnumerator.m new file mode 100644 index 000000000..4b4fdc7ad --- /dev/null +++ b/ownCloud File Provider/FileProviderContentEnumerator.m @@ -0,0 +1,665 @@ +// +// FileProviderContentEnumerator.m +// ownCloud File Provider +// +// Created by Felix Schwarz on 05.05.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import + +#import "FileProviderContentEnumerator.h" +#import "FileProviderEnumeratorObserver.h" +#import "OCItem+FileProviderItem.h" +#import "OCVFSNode+FileProviderItem.h" +#import "NSNumber+OCSyncAnchorData.h" + +@interface OCVault (InternalSignal) +- (void)signalEnumeratorForContainerItemIdentifier:(NSFileProviderItemIdentifier)changedDirectoryLocalID; +@end + +@implementation FileProviderContentEnumerator + +- (instancetype)initWithVFSCore:(OCVFSCore *)vfsCore containerItemIdentifier:(NSFileProviderItemIdentifier)containerItemIdentifier; +{ + if ((self = [super init]) != nil) + { + _vfsCore = vfsCore; + _containerItemIdentifier = containerItemIdentifier; + + _enumerationObservers = [NSMutableArray new]; + _changeObservers = [NSMutableArray new]; + + if ([_containerItemIdentifier isEqual:NSFileProviderRootContainerItemIdentifier]) + { + _containerItemIdentifier = _vfsCore.rootNode.itemID; + } + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_displaySettingsChanged:) name:DisplaySettingsChanged object:nil]; + } + + return (self); +} + +- (void)invalidate +{ + OCLogDebug(@"##### INVALIDATE %@", _containerItemIdentifier); + + [[NSNotificationCenter defaultCenter] removeObserver:self name:DisplaySettingsChanged object:nil]; + + self.content.query.delegate = nil; + self.content = nil; +} + +- (void)_displaySettingsChanged:(NSNotification *)notification +{ + OCLogDebug(@"Received display settings update notification (enumerator for %@)", _containerItemIdentifier); + + if (_content.query != nil) + { + [DisplaySettings.sharedDisplaySettings updateQueryWithDisplaySettings:_content.query]; + } + + if (_containerItemIdentifier != nil) + { + [_content.core.vault signalEnumeratorForContainerItemIdentifier:_containerItemIdentifier]; + } +} + +- (void)enumerateItemsForObserver:(id)observer startingAtPage:(NSFileProviderPage)page +{ + FileProviderEnumeratorObserver *enumerationObserver = [FileProviderEnumeratorObserver new]; + + OCLogDebug(@"##### Enumerate ITEMS for observer: %@ fromPage: %@", observer, page); + + enumerationObserver.enumerationObserver = observer; + enumerationObserver.enumerationStartPage = page; + enumerationObserver.didProvideInitialItems = NO; + + @synchronized(self) + { + [self->_enumerationObservers addObject:enumerationObserver]; + } + + [self _startQuery]; +} + +//- (void)enumerateItemsForObserver:(id)observer startingAtPage:(NSFileProviderPage)page +//{ +// if ([_containerItemIdentifier isEqual:NSFileProviderRootContainerItemIdentifier]) +// { +// _containerItemIdentifier = _vfsCore.rootNode.itemID; +// } +// +// [_vfsCore provideContentForContainerItemID:_containerItemIdentifier changesFromSyncAnchor:nil completionHandler:^(NSError * _Nullable error, OCVFSContent * _Nullable content) { +// if (error != nil) +// { +// [observer finishEnumeratingWithError:error]; +// } +// else +// { +// self.content = content; +// +// // Send VFS children +// if (content.vfsChildNodes.count > 0) +// { +// [observer didEnumerateItems:content.vfsChildNodes]; +// } +// +// // End enumeration if no query returned +// if (content.query == nil) +// { +// [observer finishEnumeratingUpToPage:nil]; +// } +// +// // Continue enumeration if query returned +// if (content.query != nil) +// { +// FileProviderEnumeratorObserver *enumerationObserver = [FileProviderEnumeratorObserver new]; +// +// OCLogDebug(@"##### Enumerate ITEMS for observer: %@ fromPage: %@", observer, page); +// +// enumerationObserver.enumerationObserver = observer; +// enumerationObserver.enumerationStartPage = page; +// enumerationObserver.didProvideInitialItems = NO; +// +// @synchronized(self) +// { +// [self->_enumerationObservers addObject:enumerationObserver]; +// } +// +// [self _startQuery]; +// } +// } +// }]; +//} + +- (void)_finishAllEnumeratorsWithError:(NSError *)error +{ + @synchronized(self) + { + for (FileProviderEnumeratorObserver *observer in _enumerationObservers) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [observer.enumerationObserver finishEnumeratingWithError:error]; + }); + } + [_enumerationObservers removeAllObjects]; + + for (FileProviderEnumeratorObserver *observer in _changeObservers) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [observer.changeObserver finishEnumeratingWithError:error]; + }); + } + [_changeObservers removeAllObjects]; + } +} + +- (void)setContent:(OCVFSContent *)content +{ + if (content != nil) + { + if (content.query != nil) + { + [content.core stopQuery:content.query]; + } + } + + _content = content; + + if ((content.core != nil) && (content.query != nil)) + { + content.query.delegate = self; + + [DisplaySettings.sharedDisplaySettings updateQueryWithDisplaySettings:content.query]; + + @synchronized(self) + { + if ([self->_enumerationObservers.lastObject.enumerationStartPage isEqual:NSFileProviderInitialPageSortedByDate]) + { + content.query.sortComparator = ^NSComparisonResult(OCItem *item1, OCItem *item2) { + return ([item1.lastModified compare:item2.lastModified]); + }; + } + + if ([self->_enumerationObservers.lastObject.enumerationStartPage isEqual:NSFileProviderInitialPageSortedByName]) + { + content.query.sortComparator = ^NSComparisonResult(OCItem *item1, OCItem *item2) { + return ([item1.name compare:item2.name]); + }; + } + } + + [content.core startQuery:content.query]; + } + +//// OCMeasureEventBegin(self, @"fp.enumerator", coreRequestRef, @"Requesting core…"); +//// OCMeasureEventEnd(self, @"fp.enumerator", coreRequestRef, @"Received core…"); +//// if (self->_core != nil) +//// { +//// // Already has a core - balance duplicate requested core +//// [[OCCoreManager sharedCoreManager] returnCoreForBookmark:core.bookmark completionHandler:nil]; +//// } +//// else +//// { +//// self->_core = core; +//// } +// +//// if (error != nil) +//// { +//// // TODO: Report error as NSFileProviderErrorServerUnreachable or NSFileProviderErrorNotAuthenticated, depending on what the underlying error is +//// [self _finishAllEnumeratorsWithError:error]; +//// } +//// else +//// { +// // Create and add query +// __block OCPath queryPath = nil; +// +// OCMeasureEventBegin(self, @"db.resolve-item", resolveEventRef, @"Resolve item identifier"); +// +//// if ([self->_enumeratedItemIdentifier isEqualToString:NSFileProviderRootContainerItemIdentifier]) +//// { +//// queryPath = @"/"; +//// } +//// else +// { +// NSError *error = nil; +// OCItem *item; +// +// if ((item = [core synchronousRetrieveItemFromDatabaseForLocalID:self->_enumeratedItemIdentifier syncAnchor:NULL error:&error]) != nil) +// { +// if (item.type == OCItemTypeCollection) +// { +// queryPath = item.path; +// } +//// +//// if (item.type == OCItemTypeFile) +//// { +//// OCLogDebug(@"Observe item: %@", item); +//// +//// [observer didEnumerateItems:@[ item ]]; +//// [observer finishEnumeratingUpToPage:nil]; +//// return; +//// } +// } +// } +// +// OCMeasureEventEnd(self, @"db.resolve-item", resolveEventRef, @"Resolve item identifier"); +// +// if (queryPath == nil) +// { +// // Item not found or not a directory +// NSError *enumerationError = [NSError errorWithDomain:NSFileProviderErrorDomain code:NSFileProviderErrorNoSuchItem userInfo:nil]; +// +// [self _finishAllEnumeratorsWithError:enumerationError]; +// return; +// } +// else +// { +// // Start query +// self->_query = [OCQuery queryForLocation:[OCLocation legacyRootPath:queryPath]]; +// self->_query.includeRootItem = queryPath.isRootPath; // Include the root item only for the root folder. If it's not included, no folder can be created in the root directory. If a non-root folder is included in a query result for its content, the Files Duplicate action will loop infinitely. +// self->_query.delegate = self; +// +// [DisplaySettings.sharedDisplaySettings updateQueryWithDisplaySettings:self->_query]; +// +// @synchronized(self) +// { +// if ([self->_enumerationObservers.lastObject.enumerationStartPage isEqual:NSFileProviderInitialPageSortedByDate]) +// { +// self->_query.sortComparator = ^NSComparisonResult(OCItem *item1, OCItem *item2) { +// return ([item1.lastModified compare:item2.lastModified]); +// }; +// } +// +// if ([self->_enumerationObservers.lastObject.enumerationStartPage isEqual:NSFileProviderInitialPageSortedByName]) +// { +// self->_query.sortComparator = ^NSComparisonResult(OCItem *item1, OCItem *item2) { +// return ([item1.name compare:item2.name]); +// }; +// } +// } +// +// OCLogDebug(@"##### START QUERY FOR %@", self->_query.queryLocation); +// +// [self->_query attachMeasurement:self->_measurement]; +// +// [core startQuery:self->_query]; +// } +// } +// }]; +// } +// else +// { +// OCLogDebug(@"Query already running.."); +// +// if (_query != nil) +// { +// @synchronized(self) +// { +// if (_enumerationObservers.count!=0) +// { +// dispatch_async(dispatch_get_main_queue(), ^{ +// [self provideItemsForEnumerationObserverFromQuery:self->_query]; +// }); +// } +// +// if (_changeObservers.count!=0) +// { +// dispatch_async(dispatch_get_main_queue(), ^{ +// [self provideItemsForChangeObserverFromQuery:self->_query]; +// }); +// } +// } +// } +// } + + +// if (error != nil) +// { +// [observer finishEnumeratingWithError:error]; +// } +// else +// { +// self.content = content; +// +// // Send VFS children +// if (content.vfsChildNodes.count > 0) +// { +// [observer didEnumerateItems:content.vfsChildNodes]; +// } +// +// // End enumeration if no query returned +// if (content.query == nil) +// { +// [observer finishEnumeratingUpToPage:nil]; +// } +// +// // Continue enumeration if query returned +// if (content.query != nil) +// { +// } +// } +} + +- (void)_startQuery +{ + OCLogDebug(@"##### Starting query.."); + + OCMeasureEvent(self, @"fp.enumerator", @"Starting enumerator…"); + + BOOL contentRequested = NO; + + @synchronized(self) + { + contentRequested = _contentRequested; + + if (_contentRequested == NO) + { + _contentRequested = YES; + } + } + + if ((self.content == nil) && !contentRequested) + { + [self.vfsCore provideContentForContainerItemID:_containerItemIdentifier changesFromSyncAnchor:nil completionHandler:^(NSError * _Nullable error, OCVFSContent * _Nullable content) { + if (error != nil) + { + [self _finishAllEnumeratorsWithError:error]; + } + else + { + self.content = content; + + if (self.content.query == nil) + { + // No query that would call us back later, so just send the existing content now + [self sendExistingContent]; + } + } + }]; + } + else + { + OCLogDebug(@"Query already running.."); + [self sendExistingContent]; + } + + /* TODO: + - inspect the page to determine whether this is an initial or a follow-up request + + If this is an enumerator for a directory, the root container or all directories: + - perform a server request to fetch directory contents + If this is an enumerator for the active set: + - perform a server request to update your local database + - fetch the active set from your local database + + - inform the observer about the items returned by the server (possibly multiple times) + - inform the observer that you are finished with this page + */ +} + +- (void)sendExistingContent +{ + if (self.content != nil) + { + @synchronized(self) + { + if (_enumerationObservers.count!=0) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [self provideItemsForEnumerationObserver]; + }); + } + + if (_changeObservers.count!=0) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [self provideItemsForChangeObserver]; + }); + } + } + } +} + +- (void)provideItemsForEnumerationObserver +{ + if (((_content.query.state == OCQueryStateContentsFromCache) || ((_content.query.state == OCQueryStateWaitingForServerReply) && (_content.query.queryResults.count > 0)) || (_content.query.state == OCQueryStateIdle)) + || ((_content.query == nil) && (_content != nil))) + { + @synchronized(self) + { + NSMutableArray *removeObservers = [NSMutableArray new]; + + for (FileProviderEnumeratorObserver *observer in _enumerationObservers) + { + if (observer.enumerationObserver != nil) + { + if (!observer.didProvideInitialItems) + { + NSArray *queryResults = _content.query.queryResults; + OCBookmarkUUIDString bookmarkUUIDString = _content.core.bookmark.uuid.UUIDString; + + for (OCItem *item in queryResults) + { + item.customIdentifier1 = bookmarkUUIDString; + item.customIdentifier2 = _content.containerNode.itemIdentifier; + } + + OCLogDebug(@"##### PROVIDE ITEMS TO %ld --ENUMERATION-- OBSERVER %@ FOR %@: %@", _enumerationObservers.count, observer.enumerationObserver, _content.query.queryLocation.path, queryResults); + + observer.didProvideInitialItems = YES; + + if (_content.vfsChildNodes.count > 0) + { + [observer.enumerationObserver didEnumerateItems:_content.vfsChildNodes]; + } + + if (queryResults.count > 0) + { + // NSUInteger offset = 0, count = queryResults.count; + // + // while (offset < count) + // { + // NSUInteger sliceCount = 100; + // + // if (offset + sliceCount > count) + // { + // sliceCount = count - offset; + // } + // + // NSArray *partialResults = [queryResults subarrayWithRange:NSMakeRange(offset, sliceCount)]; + // + // [observer.enumerationObserver didEnumerateItems:partialResults]; + // + // offset += sliceCount; + // }; + + [observer.enumerationObserver didEnumerateItems:queryResults]; + } + + [observer.enumerationObserver finishEnumeratingUpToPage:nil]; + + [removeObservers addObject:observer]; + } + } + } + + [_enumerationObservers removeObjectsInArray:removeObservers]; + } + } +} + +- (void)provideItemsForChangeObserver +{ + @synchronized(self) + { + if (_changeObservers.count > 0) + { + OCLogDebug(@"##### PROVIDE ITEMS TO %lu --CHANGE-- OBSERVER FOR %@: %@", _changeObservers.count, _content.query.queryLocation.path, _content.query.queryResults); + + NSArray *queryResults = _content.query.queryResults; + OCBookmarkUUIDString bookmarkUUIDString = _content.core.bookmark.uuid.UUIDString; + + for (OCItem *item in queryResults) + { + item.customIdentifier1 = bookmarkUUIDString; + item.customIdentifier2 = _content.containerNode.itemIdentifier; + } + + NSFileProviderSyncAnchor syncAnchor = [_content.core.latestSyncAnchor syncAnchorData]; + + for (FileProviderEnumeratorObserver *observer in _changeObservers) + { + [observer.changeObserver didUpdateItems:queryResults]; + [observer.changeObserver finishEnumeratingChangesUpToSyncAnchor:syncAnchor moreComing:NO]; + } + + [_changeObservers removeAllObjects]; + } + } +} + +- (void)queryHasChangesAvailable:(OCQuery *)query +{ + OCLogDebug(@"##### Query for %@ has changes. Query state: %lu, SinceSyncAnchor: %@, Changes available: %d", query.queryLocation.path, (unsigned long)query.state, query.querySinceSyncAnchor, query.hasChangesAvailable); + + if ( (query.state == OCQueryStateContentsFromCache) || + ((query.state == OCQueryStateWaitingForServerReply) && (query.queryResults.count > 0)) || + (query.state == OCQueryStateIdle)) + { + dispatch_async(dispatch_get_main_queue(), ^{ + @synchronized(self) + { + if (self->_enumerationObservers.count > 0) + { + [self provideItemsForEnumerationObserver]; + } + + if (self->_changeObservers.count > 0) + { + [self provideItemsForChangeObserver]; + } + } + }); + } +} + +- (void)query:(OCQuery *)query failedWithError:(NSError *)error +{ + OCLogDebug(@"### Query failed with error: %@", error); +} + +- (void)enumerateChangesForObserver:(id)observer fromSyncAnchor:(NSFileProviderSyncAnchor)syncAnchor +{ + OCLogDebug(@"##### Enumerate CHANGES for observer: %@ fromSyncAnchor: %@", observer, syncAnchor); + + if (syncAnchor != nil) + { + /** Apple: + If the enumeration fails with NSFileProviderErrorSyncAnchorExpired, we will + drop all cached data and start the enumeration over starting with sync anchor + nil. + */ + dispatch_async(dispatch_get_main_queue(), ^{ +// if ([syncAnchor isEqual:[self->_core.latestSyncAnchor syncAnchorData]]) +// { +// OCLogDebug(@"##### END(LATEST) Enumerate CHANGES for observer: %@ fromSyncAnchor: %@", observer, syncAnchor); +// [observer finishEnumeratingChangesUpToSyncAnchor:syncAnchor moreComing:NO]; +// } +// else + { + OCLogDebug(@"##### END(EXPIRED) Enumerate CHANGES for observer: %@ fromSyncAnchor: %@", observer, syncAnchor); + [observer finishEnumeratingWithError:[NSError errorWithDomain:NSFileProviderErrorDomain code:NSFileProviderErrorSyncAnchorExpired userInfo:nil]]; + } + }); + } + else + { + /** Apple: + "If anchor is nil, then the system is enumerating from scratch: the system wants + to receives changes to reconstruct the list of items in this enumeration as if + starting from an empty list." + */ + +// [_vfsCore provideContentForContainerItemID:_containerItemIdentifier changesFromSyncAnchor:nil completionHandler:^(NSError * _Nullable error, OCVFSContent * _Nullable content) { +// if (error != nil) +// { +// [observer finishEnumeratingWithError:error]; +// } +// else +// { +//// [observer didEnumerateItems:content.vfsChildNodes]; +//// [observer finishEnumeratingUpToPage:nil]; +// } +// }]; + + FileProviderEnumeratorObserver *enumerationObserver = [FileProviderEnumeratorObserver new]; + + enumerationObserver.changeObserver = observer; + enumerationObserver.changesFromSyncAnchor = syncAnchor; + + @synchronized(self) + { + [_enumerationObservers addObject:enumerationObserver]; + } + + [self _startQuery]; + } +} + +//- (void)currentSyncAnchorWithCompletionHandler:(void (^)(NSFileProviderSyncAnchor _Nullable))completionHandler +//{ +// OCLogDebug(@"#### Request current sync anchor"); +// +// dispatch_async(dispatch_get_main_queue(), ^{ +// completionHandler([self->_core.latestSyncAnchor syncAnchorData]); +// }); +//} + +// - (void)enumerateChangesForObserver:(id)observer fromSyncAnchor:(NSFileProviderSyncAnchor)anchor +// { + /* TODO: + - query the server for updates since the passed-in sync anchor + + If this is an enumerator for the active set: + - note the changes in your local database + + - inform the observer about item deletions and updates (modifications + insertions) + - inform the observer when you have finished enumerating up to a subsequent sync anchor + */ + /** + If the enumeration fails with NSFileProviderErrorSyncAnchorExpired, we will + drop all cached data and start the enumeration over starting with sync anchor + nil. + */ + // - (void)finishEnumeratingWithError:(NSError *)error; +// } + +//- (OCMeasurement *)hostedMeasurement +//{ +// return (_measurement); +//} + ++ (NSArray *)logTags +{ + return (@[ @"FPEnum" ]); +} + +- (NSArray *)logTags +{ + return (@[ @"FPEnum", OCLogTagInstance(self)]); +} + +@end diff --git a/ownCloud File Provider/FileProviderExtension.h b/ownCloud File Provider/FileProviderExtension.h index c130ebac8..9a0888bd0 100644 --- a/ownCloud File Provider/FileProviderExtension.h +++ b/ownCloud File Provider/FileProviderExtension.h @@ -19,9 +19,10 @@ #import #import -@interface FileProviderExtension : NSFileProviderExtension +@interface FileProviderExtension : NSFileProviderExtension { __weak OCCore *_core; + NSUInteger _coreRetainCount; OCBookmark *_bookmark; } diff --git a/ownCloud File Provider/FileProviderExtension.m b/ownCloud File Provider/FileProviderExtension.m index 0f1cd4da1..23b1bccaa 100644 --- a/ownCloud File Provider/FileProviderExtension.m +++ b/ownCloud File Provider/FileProviderExtension.m @@ -25,6 +25,7 @@ #import "FileProviderExtensionThumbnailRequest.h" #import "NSError+MessageResolution.h" #import "FileProviderServiceSource.h" +#import "FileProviderContentEnumerator.h" #import @interface FileProviderExtension () @@ -32,6 +33,8 @@ @interface FileProviderExtension () NSFileCoordinator *_fileCoordinator; NotificationMessagePresenter *_messagePresenter; + OCVFSCore *_vfsCore; + BOOL _skipAuthorizationFailure; } @@ -81,6 +84,61 @@ - (instancetype)init // }); //} +- (OCVFSCore *)vfsCore +{ + if (_vfsCore == nil) + { + _vfsCore = [[OCVFSCore alloc] init]; + _vfsCore.delegate = self; +// +// [_vfsCore addNodes:@[ +// [OCVFSNode virtualFolderAtPath:@"/" location:nil], +// [OCVFSNode virtualFolderInPath:@"/" withName:@"Hi" location:nil], +// [OCVFSNode virtualFolderInPath:@"/" withName:@"Hallo" location:nil], +// [OCVFSNode virtualFolderInPath:@"/" withName:@"Hello" location:nil], +// [OCVFSNode virtualFolderInPath:@"/Hello/" withName:@"world" location:nil] +// ]]; + } + + if (_vfsCore.rootNode == nil) + { + OCCore *core; + + if ((core = self.core) != nil) + { + if (core.useDrives) + { + if (core.drives.count > 0) + { + NSMutableArray *nodes = [NSMutableArray new]; + + [nodes addObject:[OCVFSNode virtualFolderAtPath:@"/" location:nil]]; + + for (OCDrive *drive in core.drives) + { + OCLocation *driveRootLocation = drive.rootLocation; + driveRootLocation.bookmarkUUID = self.bookmark.uuid; + + [nodes addObject:[OCVFSNode virtualFolderAtPath:[@"/" stringByAppendingPathComponent:drive.name].normalizedDirectoryPath location:driveRootLocation]]; + } + + [_vfsCore addNodes:nodes]; + } + } + else + { + OCLocation *legacyRoot = [[OCLocation alloc] initWithBookmarkUUID:core.bookmark.uuid driveID:nil path:@"/"]; + + [_vfsCore addNodes:@[ + [OCVFSNode virtualFolderAtPath:@"/" location:legacyRoot] + ]]; + } + } + } + + return (_vfsCore); +} + - (void)setupCrashReporting { static dispatch_once_t token; @@ -173,65 +231,21 @@ - (NSFileProviderItem)itemForIdentifier:(NSFileProviderItemIdentifier)identifier __block NSFileProviderItem item = nil; __block NSError *returnError = nil; - OCSyncExec(itemRetrieval, { - // Resolve the given identifier to a record in the model - NSError *coreError = nil; - OCCore *core = [self coreWithError:&coreError]; - - if (core != nil) - { - if (coreError != nil) - { - returnError = coreError; - } - else - { - if ([identifier isEqual:NSFileProviderRootContainerItemIdentifier]) - { - // Root item - OCDatabase *database; - - if (((database = core.vault.database) != nil) && database.isOpened) - { - [database retrieveCacheItemsAtLocation:OCLocation.legacyRootLocation itemOnly:YES completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { - item = items.firstObject; - returnError = error; - - OCSyncExecDone(itemRetrieval); - }]; - } - else - { - // Database not available - OCSyncExecDone(itemRetrieval); - } - } - else - { - // Other item - [core retrieveItemFromDatabaseForLocalID:(OCLocalID)identifier completionHandler:^(NSError *error, OCSyncAnchor syncAnchor, OCItem *itemFromDatabase) { - item = itemFromDatabase; - returnError = error; - - OCSyncExecDone(itemRetrieval); - }]; - } - } - } - else - { - returnError = coreError; - - OCSyncExecDone(itemRetrieval); - } - }); + if ([identifier isEqual:NSFileProviderRootContainerItemIdentifier]) + { + item = (NSFileProviderItem)self.vfsCore.rootNode; + } + else + { + item = (NSFileProviderItem)[self.vfsCore itemForIdentifier:(OCVFSItemID)identifier error:&returnError]; + } if ((item == nil) && (returnError == nil)) { returnError = [NSError fileProviderErrorForNonExistentItemWithIdentifier:identifier]; } - // OCLogDebug(@"-itemForIdentifier:error: %@ => %@ / %@", identifier, item, returnError); + OCLogDebug(@"-itemForIdentifier:error: %@ => %@ / %@", identifier, item, returnError); if (outError != NULL) { @@ -243,13 +257,15 @@ - (NSFileProviderItem)itemForIdentifier:(NSFileProviderItemIdentifier)identifier - (NSURL *)URLForItemWithPersistentIdentifier:(NSFileProviderItemIdentifier)identifier { - OCItem *item; +// OCItem *item; NSURL *url = nil; - if ((item = (OCItem *)[self itemForIdentifier:identifier error:NULL]) != nil) - { - url = [self.core localURLForItem:item]; - } + url = [self.vfsCore urlForItemIdentifier:(OCVFSItemID)identifier]; + +// if ((item = (OCItem *)[self itemForIdentifier:identifier error:NULL]) != nil) +// { +// url = [self.core localURLForItem:item]; +// } // OCLogDebug(@"-URLForItemWithPersistentIdentifier: %@ => %@", identifier, url); @@ -269,20 +285,15 @@ - (NSURL *)URLForItemWithPersistentIdentifier:(NSFileProviderItemIdentifier)iden - (NSFileProviderItemIdentifier)persistentIdentifierForItemAtURL:(NSURL *)url { // resolve the given URL to a persistent identifier using a database - NSArray *pathComponents = [url pathComponents]; - - // exploit the fact that the path structure has been defined as - // /[Drives//]/ above - NSParameterAssert(pathComponents.count > 2); // OCLogDebug(@"-persistentIdentifierForItemAtURL: %@", (pathComponents[pathComponents.count - 2])); - if ([pathComponents.lastObject isEqual:self.bookmark.fpServicesURLComponentName]) + if ([url.lastPathComponent isEqual:self.bookmark.fpServicesURLComponentName]) { return (url.lastPathComponent); } - return pathComponents[pathComponents.count - 2]; + return ([self.vfsCore itemIdentifierForURL:url]); } - (void)providePlaceholderAtURL:(NSURL *)url completionHandler:(void (^)(NSError * _Nullable))completionHandler @@ -913,11 +924,13 @@ - (void)setLastUsedDate:(NSDate *)lastUsedDate forItemIdentifier:(NSFileProvider if (![containerItemIdentifier isEqualToString:NSFileProviderWorkingSetContainerItemIdentifier]) { - FileProviderEnumerator *enumerator = [[FileProviderEnumerator alloc] initWithBookmark:self.bookmark enumeratedItemIdentifier:containerItemIdentifier]; - - enumerator.fileProviderExtension = self; + return ([[FileProviderContentEnumerator alloc] initWithVFSCore:self.vfsCore containerItemIdentifier:containerItemIdentifier]); - return (enumerator); +// FileProviderEnumerator *enumerator = [[FileProviderEnumerator alloc] initWithBookmark:self.bookmark enumeratedItemIdentifier:containerItemIdentifier]; +// +// enumerator.fileProviderExtension = self; +// +// return (enumerator); } return (nil); @@ -1040,6 +1053,21 @@ - (OCCore *)core return ([self coreWithError:nil]); } +- (void)acquireCoreForBookmark:(OCBookmark *)bookmark completionHandler:(void (^)(NSError * _Nullable, OCCore * _Nullable))completionHandler +{ + NSError *error = nil; + OCCore *core = [self coreWithError:&error]; + + // TODO + + completionHandler(error, core); +} + +- (void)relinquishCoreForBookmark:(OCBookmark *)bookmark completionHandler:(void (^)(NSError * _Nullable))completionHandler +{ + // TODO +} + - (OCCore *)coreWithError:(NSError **)outError { OCLogDebug(@"FileProviderExtension[%p].core[enter]: _core=%p, bookmark=%@", self, _core, self.bookmark); diff --git a/ownCloud File Provider/OCItem+FileProviderItem.m b/ownCloud File Provider/OCItem+FileProviderItem.m index 400e85d35..97681c8a8 100644 --- a/ownCloud File Provider/OCItem+FileProviderItem.m +++ b/ownCloud File Provider/OCItem+FileProviderItem.m @@ -33,22 +33,31 @@ @implementation OCItem (FileProviderItem) - (NSFileProviderItemIdentifier)itemIdentifier { - if ([self.path isEqual:@"/"]) - { - return (NSFileProviderRootContainerItemIdentifier); - } +// if ([self.path isEqual:@"/"]) +// { +// return (NSFileProviderRootContainerItemIdentifier); +// } +// +// return (self.localID); - return (self.localID); + return ([OCVFSCore composeVFSItemIDForOCItemWithBookmarkUUID:self.customIdentifier1 driveID:self.driveID localID:self.localID]); } - (NSFileProviderItemIdentifier)parentItemIdentifier { - if ([[self.path stringByDeletingLastPathComponent] isEqualToString:@"/"]) + if (self.customIdentifier2 != nil) { - return (NSFileProviderRootContainerItemIdentifier); + return (self.customIdentifier2); } - return (self.parentLocalID); + return ([OCVFSCore composeVFSItemIDForOCItemWithBookmarkUUID:self.customIdentifier1 driveID:self.driveID localID:self.parentLocalID]); + +// if ([[self.path stringByDeletingLastPathComponent] isEqualToString:@"/"]) +// { +// return (NSFileProviderRootContainerItemIdentifier); +// } +// +// return (self.parentLocalID); } - (NSString *)filename @@ -173,9 +182,14 @@ - (UTType *)contentType // Override by MIMEType if (self.mimeType != nil) { - uti = [UTType typeWithIdentifier:OCItem.overriddenUTIByMIMEType[self.mimeType]]; + NSString *overrideType; + + if ((overrideType = OCItem.overriddenUTIByMIMEType[self.mimeType]) != nil) + { + uti = [UTType typeWithIdentifier:overrideType]; - OCLogVerbose(@"Mapped %@ MIMEType %@ to UTI %@", self.name, self.mimeType, uti); + OCLogVerbose(@"Mapped %@ MIMEType %@ to UTI %@", self.name, self.mimeType, uti); + } } } @@ -186,9 +200,14 @@ - (UTType *)contentType // Override by suffix if ((suffix = self.name.pathExtension.lowercaseString) != nil) { - uti = [UTType typeWithIdentifier:OCItem.overriddenUTIBySuffix[suffix]]; + NSString *overrideType; - OCLogVerbose(@"Mapped %@ suffix %@ to UTI %@", self.name, suffix, uti); + if ((overrideType = OCItem.overriddenUTIBySuffix[suffix]) != nil) + { + uti = [UTType typeWithIdentifier:overrideType]; + + OCLogVerbose(@"Mapped %@ suffix %@ to UTI %@", self.name, suffix, uti); + } } } @@ -199,7 +218,8 @@ - (UTType *)contentType { uti = [UTType typeWithMIMEType:self.mimeType]; } - else + + if (uti == nil) { uti = UTTypeData; } diff --git a/ownCloud File Provider/OCVFSNode+FileProviderItem.h b/ownCloud File Provider/OCVFSNode+FileProviderItem.h new file mode 100644 index 000000000..665d3f944 --- /dev/null +++ b/ownCloud File Provider/OCVFSNode+FileProviderItem.h @@ -0,0 +1,27 @@ +// +// OCVFSNode+FileProviderItem.h +// ownCloud File Provider +// +// Created by Felix Schwarz on 04.05.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface OCVFSNode (FileProviderItem) + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloud File Provider/OCVFSNode+FileProviderItem.m b/ownCloud File Provider/OCVFSNode+FileProviderItem.m new file mode 100644 index 000000000..47047546c --- /dev/null +++ b/ownCloud File Provider/OCVFSNode+FileProviderItem.m @@ -0,0 +1,73 @@ +// +// OCVFSNode+FileProviderItem.m +// ownCloud File Provider +// +// Created by Felix Schwarz on 04.05.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import "OCVFSNode+FileProviderItem.h" +#import "OCItem+FileProviderItem.h" +#import + +@implementation OCVFSNode (FileProviderItem) + +- (NSString *)filename +{ + return (self.name); +} + +- (NSFileProviderItemIdentifier)itemIdentifier +{ + if (self.isRootNode) + { + return (NSFileProviderRootContainerItemIdentifier); + } + + return (self.itemID); +} + +- (NSFileProviderItemIdentifier)parentItemIdentifier +{ + OCVFSNode *parentNode = self.parentNode; + + if (parentNode.isRootNode) + { + return (NSFileProviderRootContainerItemIdentifier); + } + + return (parentNode.itemID); +} + +- (UTType *)contentType +{ + return (UTTypeFolder); +} + +- (NSFileProviderItemCapabilities)capabilities +{ + if (self.location != nil) + { + // Return capabilities of item at .location + OCItem *locationItem; + + if ((locationItem = self.locationItem) != nil) + { + return (locationItem.capabilities); + } + } + + return (NSFileProviderItemCapabilitiesAllowsContentEnumerating); +} + +@end diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index a436693db..4241c412d 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -279,6 +279,8 @@ DC20DE5C21C01A3D0096000B /* ownCloudMocking.framework in EarlGrey Copy Files */ = {isa = PBXBuildFile; fileRef = DC0196A620F754CA00C41B78 /* ownCloudMocking.framework */; }; DC20DE6A21C01B210096000B /* ownCloudSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 239369782076110900BCE21A /* ownCloudSDK.framework */; }; DC20DE6B21C01B210096000B /* ownCloudUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2393697C2076110900BCE21A /* ownCloudUI.framework */; }; + DC2218C62822C5B900808BCE /* OCVFSNode+FileProviderItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DC2218C52822C5B900808BCE /* OCVFSNode+FileProviderItem.m */; }; + DC2218CC2823329100808BCE /* FileProviderContentEnumerator.m in Sources */ = {isa = PBXBuildFile; fileRef = DC2218CB2823329100808BCE /* FileProviderContentEnumerator.m */; }; DC23D1D9238F390A00423F62 /* OCLicenseAppStoreReceipt.m in Sources */ = {isa = PBXBuildFile; fileRef = DC23D1D7238F390200423F62 /* OCLicenseAppStoreReceipt.m */; }; DC23D1DA238F391200423F62 /* OCLicenseAppStoreReceipt.h in Headers */ = {isa = PBXBuildFile; fileRef = DC23D1D6238F390200423F62 /* OCLicenseAppStoreReceipt.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC24B28725BA2A2E005783E2 /* Branding.h in Headers */ = {isa = PBXBuildFile; fileRef = DC24B27125B9DF31005783E2 /* Branding.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1237,6 +1239,10 @@ DC136581208223F000FC0F60 /* OCBookmark+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCBookmark+Extension.swift"; sourceTree = ""; }; DC1AC7CF2319ADAE002B7892 /* ScanViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanViewController.swift; sourceTree = ""; }; DC1B270B209CF34B004715E1 /* BookmarkViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkViewController.swift; sourceTree = ""; }; + DC2218C42822C5B900808BCE /* OCVFSNode+FileProviderItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCVFSNode+FileProviderItem.h"; sourceTree = ""; }; + DC2218C52822C5B900808BCE /* OCVFSNode+FileProviderItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCVFSNode+FileProviderItem.m"; sourceTree = ""; }; + DC2218CA2823329100808BCE /* FileProviderContentEnumerator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileProviderContentEnumerator.h; sourceTree = ""; }; + DC2218CB2823329100808BCE /* FileProviderContentEnumerator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FileProviderContentEnumerator.m; sourceTree = ""; }; DC23D1D6238F390200423F62 /* OCLicenseAppStoreReceipt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCLicenseAppStoreReceipt.h; sourceTree = ""; }; DC23D1D7238F390200423F62 /* OCLicenseAppStoreReceipt.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCLicenseAppStoreReceipt.m; sourceTree = ""; }; DC243BF92317B446004FBB5C /* ThemeWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeWindow.swift; sourceTree = ""; }; @@ -1279,6 +1285,7 @@ DC3AB2452810602500789435 /* UILabel+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Extension.swift"; sourceTree = ""; }; DC3AB2472810A10300789435 /* ExpandableResourceCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandableResourceCell.swift; sourceTree = ""; }; DC3AB24A2810A69600789435 /* OCResourceText+ViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCResourceText+ViewProvider.swift"; sourceTree = ""; }; + DC3BAC63282472A80057FCD4 /* KNOWN_ISSUES.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = KNOWN_ISSUES.md; sourceTree = ""; }; DC3BE0DC2077CC13002A0AC0 /* ClientQueryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientQueryViewController.swift; sourceTree = ""; }; DC3BE0DD2077CC13002A0AC0 /* ClientRootViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientRootViewController.swift; sourceTree = ""; }; DC3BE0E02077CD4B002A0AC0 /* Synchronized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Synchronized.swift; sourceTree = ""; }; @@ -1687,6 +1694,7 @@ 233BDE93204FEFE500C06732 = { isa = PBXGroup; children = ( + DC3BAC63282472A80057FCD4 /* KNOWN_ISSUES.md */, DC9BFBB220A19AF3007064B5 /* doc */, 233BDEBF204FEFF300C06732 /* ownCloudSDK.xcodeproj */, 233BDE9E204FEFE500C06732 /* ownCloud */, @@ -2800,10 +2808,14 @@ children = ( DCC6564920C9B7E400110A97 /* FileProviderExtension.m */, DCC6564820C9B7E400110A97 /* FileProviderExtension.h */, + DC2218CB2823329100808BCE /* FileProviderContentEnumerator.m */, + DC2218CA2823329100808BCE /* FileProviderContentEnumerator.h */, DC27A1E820CC56B0008ACB6C /* FileProviderExtensionThumbnailRequest.m */, DC27A1E720CC56B0008ACB6C /* FileProviderExtensionThumbnailRequest.h */, DC27A18D20CA9F66008ACB6C /* OCItem+FileProviderItem.m */, DC27A18C20CA9F66008ACB6C /* OCItem+FileProviderItem.h */, + DC2218C52822C5B900808BCE /* OCVFSNode+FileProviderItem.m */, + DC2218C42822C5B900808BCE /* OCVFSNode+FileProviderItem.h */, DC27A1A420CBEF85008ACB6C /* OCBookmark+FileProvider.m */, DC27A1A320CBEF85008ACB6C /* OCBookmark+FileProvider.h */, DC27A1A720CC095C008ACB6C /* OCCore+FileProviderTools.m */, @@ -4534,7 +4546,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DC2218C62822C5B900808BCE /* OCVFSNode+FileProviderItem.m in Sources */, DC98BBD420FF824600F4ED3E /* FileProviderEnumeratorObserver.m in Sources */, + DC2218CC2823329100808BCE /* FileProviderContentEnumerator.m in Sources */, DCC6565020C9B7E400110A97 /* FileProviderEnumerator.m in Sources */, DC27A1E920CC56B0008ACB6C /* FileProviderExtensionThumbnailRequest.m in Sources */, DCF2DA7A24C82E480026D790 /* FileProviderServiceSource.m in Sources */, @@ -4883,7 +4897,7 @@ APP_BUILD_FLAGS = "$(inherited)"; APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; - APP_SHORT_VERSION = 11.10.0; + APP_SHORT_VERSION = 12.0; APP_VERSION = 214; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; @@ -4953,7 +4967,7 @@ APP_BUILD_FLAGS = "$(inherited)"; APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; - APP_SHORT_VERSION = 11.10.0; + APP_SHORT_VERSION = 12.0; APP_VERSION = 214; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; From 7a359a23ce74b5523ed20c598fcbc679e2079ae5 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Fri, 6 May 2022 00:05:48 +0200 Subject: [PATCH 029/328] - File Provider: make sure actions work with OCItems, not OCVFSNodes (and crash) --- KNOWN_ISSUES.md | 6 +- .../FileProviderExtension.m | 56 +++++++++++++++---- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md index 47dd7318f..1321442f1 100644 --- a/KNOWN_ISSUES.md +++ b/KNOWN_ISSUES.md @@ -16,13 +16,15 @@ It should only be used with dedicated test servers, test data - and test devices - a grid view - the .space folder in the root of spaces is visible (but should not) - spaces do not yet show a member count or provide access to a list of members -- support for sharing is widely untested +- subscription of spaces can't be turned on/off yet +- the root of spaces-based accounts is not yet shown as hierarchic sidebar +- support for sharing is widely untested and/or unavailable in the alpha ## File Provider - the list of spaces doesn't update dynamically - the list of spaces may contain spaces of unsupported types - not all actions are working correctly, especially in the root folder of spaces -- OCCores may not be managed correctly under all circumstances +- OCCores may not be managed correctly under all circumstances, causing undefined behaviour ## SDK - local storage consumed by spaces that are then deleted or inactivated is not reclaimed diff --git a/ownCloud File Provider/FileProviderExtension.m b/ownCloud File Provider/FileProviderExtension.m index 23b1bccaa..2b27a1b51 100644 --- a/ownCloud File Provider/FileProviderExtension.m +++ b/ownCloud File Provider/FileProviderExtension.m @@ -255,6 +255,38 @@ - (NSFileProviderItem)itemForIdentifier:(NSFileProviderItemIdentifier)identifier return item; } +- (OCItem *)ocItemForIdentifier:(NSFileProviderItemIdentifier)identifier error:(NSError *__autoreleasing _Nullable *)outError +{ + id item; + + if ((item = [self itemForIdentifier:identifier error:outError]) != nil) + { + if ([item isKindOfClass:OCItem.class]) + { + return (item); + } + + if ([item isKindOfClass:OCVFSNode.class]) + { + OCVFSNode *vfsNode = (OCVFSNode *)item; + + if (vfsNode.location != nil) + { + return (vfsNode.locationItem); + } + } + } + + OCLogDebug(@"-ocItemForIdentifier:%@ could not find/return OCItem", identifier); + + if (outError != NULL) + { + *outError = [[NSError fileProviderErrorForNonExistentItemWithIdentifier:identifier] translatedError]; + } + + return (nil); +} + - (NSURL *)URLForItemWithPersistentIdentifier:(NSFileProviderItemIdentifier)identifier { // OCItem *item; @@ -299,14 +331,16 @@ - (NSFileProviderItemIdentifier)persistentIdentifierForItemAtURL:(NSURL *)url - (void)providePlaceholderAtURL:(NSURL *)url completionHandler:(void (^)(NSError * _Nullable))completionHandler { NSFileProviderItemIdentifier identifier = [self persistentIdentifierForItemAtURL:url]; - if (!identifier) { + if (identifier == nil) + { completionHandler([NSError errorWithDomain:NSFileProviderErrorDomain code:NSFileProviderErrorNoSuchItem userInfo:nil]); return; } NSError *error = nil; NSFileProviderItem fileProviderItem = [self itemForIdentifier:identifier error:&error]; - if (!fileProviderItem) { + if (fileProviderItem == nil) + { completionHandler(error); return; } @@ -488,7 +522,7 @@ - (void)createDirectoryWithName:(NSString *)directoryName inParentItemIdentifier FPLogCmdBegin(@"CreateDir", @"Start of createDirectoryWithName=%@, inParentItemIdentifier=%@", directoryName, parentItemIdentifier); - if ((parentItem = (OCItem *)[self itemForIdentifier:parentItemIdentifier error:&error]) != nil) + if ((parentItem = [self ocItemForIdentifier:parentItemIdentifier error:&error]) != nil) { // Detect collission with existing items FPLogCmd(@"Creating folder %@ inside %@", directoryName, parentItem.path); @@ -578,8 +612,8 @@ - (void)reparentItemWithIdentifier:(NSFileProviderItemIdentifier)itemIdentifier FPLogCmdBegin(@"Reparent", @"Start of reparentItemWithIdentifier=%@, toParentItemWithIdentifier=%@, newName=%@", itemIdentifier, parentItemIdentifier, newName); - if (((item = (OCItem *)[self itemForIdentifier:itemIdentifier error:&error]) != nil) && - ((parentItem = (OCItem *)[self itemForIdentifier:parentItemIdentifier error:&error]) != nil)) + if (((item = [self ocItemForIdentifier:itemIdentifier error:&error]) != nil) && + ((parentItem = [self ocItemForIdentifier:parentItemIdentifier error:&error]) != nil)) { FPLogCmd(@"Moving %@ to %@ as %@", item, parentItem, ((newName != nil) ? newName : item.name)); @@ -614,7 +648,7 @@ - (void)renameItemWithIdentifier:(NSFileProviderItemIdentifier)itemIdentifier to FPLogCmdBegin(@"Rename", @"Start of renameItemWithIdentifier=%@, toName=%@", itemIdentifier, itemName); - if ((item = (OCItem *)[self itemForIdentifier:itemIdentifier error:&error]) != nil) + if ((item = [self ocItemForIdentifier:itemIdentifier error:&error]) != nil) { FPLogCmd(@"Renaming %@ in %@ to %@", item, item.path.parentPath, itemName); @@ -637,7 +671,7 @@ - (void)deleteItemWithIdentifier:(NSFileProviderItemIdentifier)itemIdentifier co FPLogCmdBegin(@"Delete", @"Start of deleteItemWithIdentifier=%@", itemIdentifier); - if ((item = (OCItem *)[self itemForIdentifier:itemIdentifier error:&error]) != nil) + if ((item = [self ocItemForIdentifier:itemIdentifier error:&error]) != nil) { FPLogCmd(@"Deleting %@", item); @@ -696,7 +730,7 @@ - (void)importDocumentAtURL:(NSURL *)fileURL toParentItemIdentifier:(NSFileProvi } } - if ((parentItem = (OCItem *)[self itemForIdentifier:parentItemIdentifier error:&error]) != nil) + if ((parentItem = [self ocItemForIdentifier:parentItemIdentifier error:&error]) != nil) { // Detect name collissions OCItem *existingItem; @@ -770,7 +804,7 @@ - (void)setFavoriteRank:(NSNumber *)favoriteRank forItemIdentifier:(NSFileProvid FPLogCmdBegin(@"FavoriteRank", @"Start of setFavoriteRank=%@, forItemIdentifier=%@", favoriteRank, itemIdentifier); - if ((item = (OCItem *)[self itemForIdentifier:itemIdentifier error:&error]) != nil) + if ((item = [self ocItemForIdentifier:itemIdentifier error:&error]) != nil) { // item.isFavorite = @(favoriteRank != nil); // Stored on server @@ -807,7 +841,7 @@ - (void)setTagData:(NSData *)tagData forItemIdentifier:(NSFileProviderItemIdenti FPLogCmdBegin(@"TagData", @"Start of setTagData=%@, forItemIdentifier=%@", tagData, itemIdentifier); - if ((item = (OCItem *)[self itemForIdentifier:itemIdentifier error:&error]) != nil) + if ((item = [self ocItemForIdentifier:itemIdentifier error:&error]) != nil) { [item setLocalTagData:tagData]; // Stored in local attributes @@ -843,7 +877,7 @@ - (void)trashItemWithIdentifier:(NSFileProviderItemIdentifier)itemIdentifier com FPLogCmdBegin(@"Trash", @"Start of trashItemWithIdentifier=%@", itemIdentifier); - if ((item = (OCItem *)[self itemForIdentifier:itemIdentifier error:&error]) != nil) + if ((item = [self ocItemForIdentifier:itemIdentifier error:&error]) != nil) { FPLogCmd(@"Deleting %@", item); From ccd16710fe19071e3abee6fdfcd84a072d789880 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Fri, 6 May 2022 13:02:05 +0200 Subject: [PATCH 030/328] - ClientContext - add makeActionProgressHandler() part of the MoreItemAction protocol - add new ContextMenuProvider protocol and property - ClientItemViewController - add plus button and folder actions to navigation bar - add support for DisplaySettings (filtering out .space folder by default) - add support for item context menus - ClientRootViewController - implement support for ContextMenuProvider - CollectionViewController - add method to standardize resolution of indexPath -> OCDataItem - add provideContextMenuConfiguration method for subclassing - update known issues --- KNOWN_ISSUES.md | 6 +- ...ClientRootViewController+ItemActions.swift | 46 ++++++++++++- .../Client/ClientRootViewController.swift | 1 + .../CollectionViewController.swift | 58 +++++++++++++--- .../ClientItemViewController.swift | 69 ++++++++++++++++++- .../Client/Context/ClientContext.swift | 8 +++ 6 files changed, 174 insertions(+), 14 deletions(-) diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md index 1321442f1..e6704f8bb 100644 --- a/KNOWN_ISSUES.md +++ b/KNOWN_ISSUES.md @@ -8,17 +8,17 @@ It should only be used with dedicated test servers, test data - and test devices ## App - in the new browsing experience, some features are not yet available: - row actions - - context menus - drag and drop - - folder actions (like "Create folder") - search - sorting - a grid view - - the .space folder in the root of spaces is visible (but should not) + - full themeing support + - breadcrumb title - spaces do not yet show a member count or provide access to a list of members - subscription of spaces can't be turned on/off yet - the root of spaces-based accounts is not yet shown as hierarchic sidebar - support for sharing is widely untested and/or unavailable in the alpha +- photo/media uploads are broken ## File Provider - the list of spaces doesn't update dynamically diff --git a/ownCloud/Client/ClientRootViewController+ItemActions.swift b/ownCloud/Client/ClientRootViewController+ItemActions.swift index 71d356b3b..a6a96db14 100644 --- a/ownCloud/Client/ClientRootViewController+ItemActions.swift +++ b/ownCloud/Client/ClientRootViewController+ItemActions.swift @@ -19,6 +19,7 @@ import UIKit import ownCloudSDK import ownCloudAppShared +import ownCloudApp extension ClientRootViewController : MoreItemAction { func makeActionProgressHandler() -> ActionProgressHandler { @@ -57,7 +58,10 @@ extension ClientRootViewController : OpenItemAction { switch item.type { case .collection: if let location = item.location { - let queryViewController = ClientItemViewController(context: context, query: OCQuery(for: location)) + let query = OCQuery(for: location) + DisplaySettings.shared.updateQuery(withDisplaySettings: query) + + let queryViewController = ClientItemViewController(context: context, query: query) if pushViewController { context.navigationController?.pushViewController(queryViewController, animated: animated) } @@ -80,6 +84,46 @@ extension ClientRootViewController : OpenItemAction { } } +extension ClientRootViewController : ContextMenuProvider { + func composeContextMenuElements(for viewController: UIViewController, item: OCItem, location: OCExtensionLocationIdentifier, context: ClientContext, sender: AnyObject?) -> [UIMenuElement]? { + guard let core = context.core else { + return nil + } + + let actionsLocation = OCExtensionLocation(ofType: .action, identifier: location) // .contextMenuItem) + let actionContext = ActionContext(viewController: viewController, core: core, items: [item], location: actionsLocation, sender: sender) + let actions = Action.sortedApplicableActions(for: actionContext) + var actionMenuActions : [UIAction] = [] + for action in actions { + action.progressHandler = makeActionProgressHandler() + + if let menuAction = action.provideUIMenuAction() { + actionMenuActions.append(menuAction) + } + } + + if core.connectionStatus == .online, core.connection.capabilities?.sharingAPIEnabled == 1, location == .contextMenuItem { + // Actions menu + let actionsMenu = UIMenu(title: "", identifier: UIMenu.Identifier("context"), options: .displayInline, children: actionMenuActions) + + // Share Items + let sharingActionsLocation = OCExtensionLocation(ofType: .action, identifier: .contextMenuSharingItem) + let sharingActionContext = ActionContext(viewController: viewController, core: core, items: [item], location: sharingActionsLocation, sender: sender) + let sharingActions = Action.sortedApplicableActions(for: sharingActionContext) + for action in sharingActions { + action.progressHandler = makeActionProgressHandler() + } + + let sharingItems = sharingActions.compactMap({$0.provideUIMenuAction()}) + let shareMenu = UIMenu(title: "", identifier: UIMenu.Identifier("sharing"), options: .displayInline, children: sharingItems) + + return [shareMenu, actionsMenu] + } + + return actionMenuActions + } +} + extension ClientRootViewController : InlineMessageCenter { public func hasInlineMessage(for item: OCItem) -> Bool { guard let activeSyncRecordIDs = item.activeSyncRecordIDs, let syncRecordIDsWithMessages = self.syncRecordIDsWithMessages else { diff --git a/ownCloud/Client/ClientRootViewController.swift b/ownCloud/Client/ClientRootViewController.swift index efc7bf1bb..2c4a0d8fa 100644 --- a/ownCloud/Client/ClientRootViewController.swift +++ b/ownCloud/Client/ClientRootViewController.swift @@ -381,6 +381,7 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa context.inlineMessageCenter = self context.openItemHandler = self context.moreItemHandler = self + context.contextMenuProvider = self }) core.vault.resourceManager?.add(ResourceSourceItemIcons(core: core)) diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift index 947feed43..0dfba35e7 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift @@ -17,6 +17,7 @@ */ import UIKit +import ownCloudApp import ownCloudSDK public class CollectionViewController: UIViewController, UICollectionViewDelegate { @@ -184,10 +185,9 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat return (collectionItemRef, nil) } - // MARK: - Collection View Delegate - public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + public func retrieveItem(at indexPath: IndexPath, synchronous: Bool = false, action: @escaping ((_ record: OCDataItemRecord, _ indexPath: IndexPath) -> Void), handleError: ((_ error: Error?) -> Void)? = nil) { guard let collectionItemRef = collectionViewDataSource.itemIdentifier(for: indexPath) else { - collectionView.deselectItem(at: indexPath, animated: true) + handleError?(nil) return } @@ -196,20 +196,56 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat let dataSource = section.dataSource { let (itemRef, _) = unwrap(collectionItemRef) - dataSource.retrieveItem(forRef: itemRef, reusing: nil, completionHandler: { [weak self] (error, record) in - guard let record = record else { return } - - _ = self?.handleSelection(of: record, at: indexPath) - }) + if synchronous { + do { + let record = try dataSource.record(forItemRef: itemRef) + action(record, indexPath) + } catch { + handleError?(error) + } + } else { + dataSource.retrieveItem(forRef: itemRef, reusing: nil, completionHandler: { (error, record) in + guard let record = record else { + handleError?(error) + return + } + + action(record, indexPath) + }) + } + } else { + handleError?(nil) } } - public func handleSelection(of record: OCDataItemRecord, at indexPath: IndexPath) -> Bool { + // MARK: - Collection View Delegate + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + retrieveItem(at: indexPath, action: { [weak self] record, indexPath in + self?.handleSelection(of: record, at: indexPath) + }, handleError: { error in + collectionView.deselectItem(at: indexPath, animated: true) + }) + } + + public func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + var contextMenuConfiguration : UIContextMenuConfiguration? + + retrieveItem(at: indexPath, synchronous: true, action: { [weak self] record, indexPath in + contextMenuConfiguration = self?.provideContextMenuConfiguration(for: record, at: indexPath, point: point) + }, handleError: { error in + collectionView.deselectItem(at: indexPath, animated: true) + }) + + return contextMenuConfiguration + } + + @discardableResult public func handleSelection(of record: OCDataItemRecord, at indexPath: IndexPath) -> Bool { if let drive = record.item as? OCDrive { let driveContext = ClientContext(with: clientContext, modifier: { context in context.drive = drive }) let query = OCQuery(for: drive.rootLocation) + DisplaySettings.shared.updateQuery(withDisplaySettings: query) let rootFolderViewController = ClientItemViewController(context: driveContext, query: query) @@ -222,6 +258,10 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat return false } + + @discardableResult public func provideContextMenuConfiguration(for record: OCDataItemRecord, at indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + return nil + } } public extension CollectionViewController { diff --git a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift index e3c794351..1ec360ea3 100644 --- a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift @@ -104,6 +104,45 @@ public class ClientItemViewController: CollectionViewController { singleDriveDatasourceSubscription?.terminate() } + public override func viewDidLoad() { + super.viewDidLoad() + + var rightInset : CGFloat = 2 + var leftInset : CGFloat = 0 + if self.view.effectiveUserInterfaceLayoutDirection == .rightToLeft { + rightInset = 0 + leftInset = 2 + } + + var viewActionButtons : [UIBarButtonItem] = [] + + if query?.queryLocation != nil { + if clientContext?.moreItemHandler != nil { + let folderActionBarButton = UIBarButtonItem(image: UIImage(named: "more-dots")?.withInset(UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: rightInset)), style: .plain, target: self, action: #selector(moreBarButtonPressed)) + folderActionBarButton.accessibilityIdentifier = "client.folder-action" + folderActionBarButton.accessibilityLabel = "Actions".localized + + viewActionButtons.append(folderActionBarButton) + } + + let plusBarButton = UIBarButtonItem(barButtonSystemItem: .add, target: nil, action: nil) + plusBarButton.menu = UIMenu(title: "", children: [ + UIDeferredMenuElement.uncached({ [weak self] completion in + if let self = self, let contextMenuProvider = self.clientContext?.contextMenuProvider, let rootItem = self.query?.rootItem, let clientContext = self.clientContext { + if let contextMenuElements = contextMenuProvider.composeContextMenuElements(for: self, item: rootItem, location: .folderAction, context: clientContext, sender: nil) { + completion(contextMenuElements) + } + } + }) + ]) + plusBarButton.accessibilityIdentifier = "client.file-add" + + viewActionButtons.append(plusBarButton) + } + + self.navigationItem.rightBarButtonItems = viewActionButtons + } + public override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) @@ -127,7 +166,10 @@ public class ClientItemViewController: CollectionViewController { if let openHandler = clientContext.openItemHandler { openHandler.open(item: item, context: clientContext, animated: true, pushViewController: true) } else { - let rootFolderViewController = ClientItemViewController(context: clientContext, query: OCQuery(for: location)) + let query = OCQuery(for: location) + DisplaySettings.shared.updateQuery(withDisplaySettings: query) + + let rootFolderViewController = ClientItemViewController(context: clientContext, query: query) self.navigationController?.pushViewController(rootFolderViewController, animated: true) } @@ -163,5 +205,30 @@ public class ClientItemViewController: CollectionViewController { } } + @discardableResult public override func provideContextMenuConfiguration(for record: OCDataItemRecord, at indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { [weak self] _ in + guard let item = record.item as? OCItem, let clientContext = self?.clientContext, let self = self else { + return nil + } + + if let menuItems = clientContext.contextMenuProvider?.composeContextMenuElements(for: self, item: item, location: .contextMenuItem, context: clientContext, sender: nil) { + return UIMenu(title: "", children: menuItems) + } + + return nil + }) + } + var _actionProgressHandler : ActionProgressHandler? + + // MARK: - Navigation Bar Actions + @objc open func moreBarButtonPressed(_ sender: UIBarButtonItem) { + guard let rootItem = query?.rootItem else { + return + } + + if let moreItemHandler = clientContext?.moreItemHandler, let clientContext = clientContext { + moreItemHandler.moreOptions(for: rootItem, at: .moreFolder, context: clientContext, sender: sender) + } + } } diff --git a/ownCloudAppShared/Client/Context/ClientContext.swift b/ownCloudAppShared/Client/Context/ClientContext.swift index b247cafc7..34f4ece01 100644 --- a/ownCloudAppShared/Client/Context/ClientContext.swift +++ b/ownCloudAppShared/Client/Context/ClientContext.swift @@ -30,6 +30,7 @@ public protocol OpenItemAction : AnyObject { public protocol MoreItemAction : AnyObject { @discardableResult func moreOptions(for item: OCItem, at location: OCExtensionLocationIdentifier, context: ClientContext, sender: AnyObject?) -> Bool + func makeActionProgressHandler() -> ActionProgressHandler } public protocol RevealItemAction : AnyObject { @@ -37,6 +38,11 @@ public protocol RevealItemAction : AnyObject { func showReveal(at path: IndexPath) -> Bool } +public protocol ContextMenuProvider : AnyObject { + @available(iOS 13.0, *) + func composeContextMenuElements(for viewController: UIViewController, item: OCItem, location: OCExtensionLocationIdentifier, context: ClientContext, sender: AnyObject?) -> [UIMenuElement]? +} + public protocol InlineMessageCenter : AnyObject { func hasInlineMessage(for item: OCItem) -> Bool func showInlineMessageFor(item: OCItem) @@ -61,6 +67,7 @@ public class ClientContext: NSObject { public weak var openItemHandler: OpenItemAction? public weak var moreItemHandler: MoreItemAction? public weak var revealItemHandler: RevealItemAction? + public weak var contextMenuProvider: ContextMenuProvider? public weak var inlineMessageCenter: InlineMessageCenter? // MARK: - Post Initialization Modifier @@ -85,6 +92,7 @@ public class ClientContext: NSObject { openItemHandler = inParent?.openItemHandler moreItemHandler = inParent?.moreItemHandler revealItemHandler = inParent?.revealItemHandler + contextMenuProvider = inParent?.contextMenuProvider inlineMessageCenter = inParent?.inlineMessageCenter modifier?(self) From 3bb34841b779154fc48091275a31b3ac114b37d9 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Mon, 9 May 2022 11:15:21 +0200 Subject: [PATCH 031/328] - ClientContext: add .originatingViewController property to pass the view controller from which f.ex. an action originates - ClientItemViewController: add support for .originatingViewController - ClientRootViewController+ItemActions: use .originatingViewController instead of (wrong) self for actions (fixes lack of feedback when Copying) - update known issues --- KNOWN_ISSUES.md | 5 ++++- ownCloud.xcodeproj/project.pbxproj | 4 ++-- ownCloud/Client/ClientRootViewController+ItemActions.swift | 5 +++-- .../View Controllers/ClientItemViewController.swift | 2 ++ ownCloudAppShared/Client/Context/ClientContext.swift | 4 +++- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md index e6704f8bb..0d6ffa0da 100644 --- a/KNOWN_ISSUES.md +++ b/KNOWN_ISSUES.md @@ -18,13 +18,16 @@ It should only be used with dedicated test servers, test data - and test devices - subscription of spaces can't be turned on/off yet - the root of spaces-based accounts is not yet shown as hierarchic sidebar - support for sharing is widely untested and/or unavailable in the alpha -- photo/media uploads are broken +- photo/media uploads from the app are broken +- inactived state of spaces is not yet represented in the UI +- "Empty folder" not shown for empty folders ## File Provider - the list of spaces doesn't update dynamically - the list of spaces may contain spaces of unsupported types - not all actions are working correctly, especially in the root folder of spaces - OCCores may not be managed correctly under all circumstances, causing undefined behaviour +- file uploads are broken ## SDK - local storage consumed by spaces that are then deleted or inactivated is not reclaimed diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 4241c412d..e1336224c 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -4898,7 +4898,7 @@ APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; APP_SHORT_VERSION = 12.0; - APP_VERSION = 214; + APP_VERSION = 216; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -4968,7 +4968,7 @@ APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; APP_SHORT_VERSION = 12.0; - APP_VERSION = 214; + APP_VERSION = 216; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; diff --git a/ownCloud/Client/ClientRootViewController+ItemActions.swift b/ownCloud/Client/ClientRootViewController+ItemActions.swift index a6a96db14..ea5490a95 100644 --- a/ownCloud/Client/ClientRootViewController+ItemActions.swift +++ b/ownCloud/Client/ClientRootViewController+ItemActions.swift @@ -36,11 +36,12 @@ extension ClientRootViewController : MoreItemAction { guard let sender = sender, let core = context.core else { return false } + let originatingViewController : UIViewController = context.originatingViewController ?? self let actionsLocation = OCExtensionLocation(ofType: .action, identifier: locationIdentifier) - let actionContext = ActionContext(viewController: self, core: core, query: context.query, items: [item], location: actionsLocation, sender: sender) + let actionContext = ActionContext(viewController: originatingViewController, core: core, query: context.query, items: [item], location: actionsLocation, sender: sender) if let moreViewController = Action.cardViewController(for: item, with: actionContext, progressHandler: makeActionProgressHandler(), completionHandler: nil) { - self.present(asCard: moreViewController, animated: true) + originatingViewController.present(asCard: moreViewController, animated: true) } return true diff --git a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift index 1ec360ea3..e292033e7 100644 --- a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift @@ -47,6 +47,8 @@ public class ClientItemViewController: CollectionViewController { } context.query = (owner as? ClientItemViewController)?.query + + context.originatingViewController = owner as? UIViewController } if let queryDatasource = query?.queryResultsDataSource, let core = itemControllerContext.core { diff --git a/ownCloudAppShared/Client/Context/ClientContext.swift b/ownCloudAppShared/Client/Context/ClientContext.swift index 34f4ece01..bbf053c11 100644 --- a/ownCloudAppShared/Client/Context/ClientContext.swift +++ b/ownCloudAppShared/Client/Context/ClientContext.swift @@ -61,6 +61,7 @@ public class ClientContext: NSObject { // MARK: - UI objects public weak var rootViewController: UIViewController? public weak var navigationController: UINavigationController? // Navigation controller to push to + public weak var originatingViewController: UIViewController? // Originating view controller for f.ex. actions public weak var progressSummarizer: ProgressSummarizer? // MARK: - UI item handling @@ -75,7 +76,7 @@ public class ClientContext: NSObject { public typealias PostInitializationModifier = (_ owner: Any?, _ context: ClientContext) -> Void public var postInitializationModifier: PostInitializationModifier? - public init(with inParent: ClientContext? = nil, core inCore: OCCore? = nil, drive inDrive: OCDrive? = nil, rootViewController inRootViewController : UIViewController? = nil, navigationController inNavigationController: UINavigationController? = nil, progressSummarizer inProgressSummarizer: ProgressSummarizer? = nil, modifier: ((_ context: ClientContext) -> Void)? = nil) { + public init(with inParent: ClientContext? = nil, core inCore: OCCore? = nil, drive inDrive: OCDrive? = nil, rootViewController inRootViewController : UIViewController? = nil, originatingViewController inOriginatingViewController: UIViewController? = nil, navigationController inNavigationController: UINavigationController? = nil, progressSummarizer inProgressSummarizer: ProgressSummarizer? = nil, modifier: ((_ context: ClientContext) -> Void)? = nil) { super.init() parent = inParent @@ -87,6 +88,7 @@ public class ClientContext: NSObject { rootViewController = inRootViewController ?? inParent?.rootViewController navigationController = inNavigationController ?? inParent?.navigationController + originatingViewController = inOriginatingViewController ?? inParent?.originatingViewController progressSummarizer = inProgressSummarizer ?? inParent?.progressSummarizer openItemHandler = inParent?.openItemHandler From ccee1614c78a71e445ec3140f39f19561ca9954e Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Tue, 24 May 2022 10:39:31 +0200 Subject: [PATCH 032/328] - ownCloudAppFramework: - implement VFSManager to manage and update OCVFSCores for use throughout the app and file provider - implement OCVault categories to perform ID conversion using VFSManager - File Provider: - update OCVFSNode and OCItem NSFileProviderItem conformance to build on the classes' OCVFSItem conformance and support OCVFSItemIDRoot - adopt VFSManager and support OCVFSItemIDRoot - fix ID mapping bug for "existing items" retrieved from cache individually by adding the bookmarkUUID to the retrieved OCItem - FileProviderEnumeratorObserver: add support for enumeration completion - FileProviderContentEnumerator: modernized rebuild around OCVFSContent and async sequential queues - update SDK --- KNOWN_ISSUES.md | 2 + ios-sdk | 2 +- .../FileProviderContentEnumerator.h | 8 +- .../FileProviderContentEnumerator.m | 669 ++++++++++-------- .../FileProviderEnumeratorObserver.h | 4 + .../FileProviderEnumeratorObserver.m | 16 + .../FileProviderExtension.h | 2 +- .../FileProviderExtension.m | 110 +-- .../OCItem+FileProviderItem.m | 25 +- .../OCVFSNode+FileProviderItem.m | 12 +- ownCloud.xcodeproj/project.pbxproj | 28 +- .../xcshareddata/xcschemes/ownCloud.xcscheme | 1 - ownCloudAppFramework/VFS/OCVault+VFSManager.h | 27 + ownCloudAppFramework/VFS/OCVault+VFSManager.m | 139 ++++ ownCloudAppFramework/VFS/VFSManager.h | 33 + ownCloudAppFramework/VFS/VFSManager.m | 182 +++++ ownCloudAppFramework/ownCloudApp.h | 2 + 17 files changed, 862 insertions(+), 400 deletions(-) create mode 100644 ownCloudAppFramework/VFS/OCVault+VFSManager.h create mode 100644 ownCloudAppFramework/VFS/OCVault+VFSManager.m create mode 100644 ownCloudAppFramework/VFS/VFSManager.h create mode 100644 ownCloudAppFramework/VFS/VFSManager.m diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md index 0d6ffa0da..2594c2b84 100644 --- a/KNOWN_ISSUES.md +++ b/KNOWN_ISSUES.md @@ -21,6 +21,8 @@ It should only be used with dedicated test servers, test data - and test devices - photo/media uploads from the app are broken - inactived state of spaces is not yet represented in the UI - "Empty folder" not shown for empty folders +- Copy & Paste allows copying a folder into a subfolder of its own / itself, leading to an infinite cycle +- handling of detached drives with user data in them (see OCVault.detachedDrives) ## File Provider - the list of spaces doesn't update dynamically diff --git a/ios-sdk b/ios-sdk index 6cfd471d7..ed7980e30 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit 6cfd471d72aa01af71e6923e6597c5d6e67f20d5 +Subproject commit ed7980e307d9e598e8b5abfd96c5a91cab462462 diff --git a/ownCloud File Provider/FileProviderContentEnumerator.h b/ownCloud File Provider/FileProviderContentEnumerator.h index f885e6fe4..08f0e8ede 100644 --- a/ownCloud File Provider/FileProviderContentEnumerator.h +++ b/ownCloud File Provider/FileProviderContentEnumerator.h @@ -26,16 +26,16 @@ NS_ASSUME_NONNULL_BEGIN { NSMutableArray *_enumerationObservers; NSMutableArray *_changeObservers; - - BOOL _contentRequested; } +@property(nonatomic,readonly,class) OCAsyncSequentialQueue *queue; + @property(strong) OCVFSCore *vfsCore; -@property(strong) OCVFSNodeID containerItemIdentifier; +@property(strong) OCVFSItemID containerItemIdentifier; @property(strong,nullable,nonatomic) OCVFSContent *content; -- (instancetype)initWithVFSCore:(OCVFSCore *)vfsCore containerItemIdentifier:(NSFileProviderItemIdentifier)containerItemIdentifier; +- (instancetype)initWithVFSCore:(OCVFSCore *)vfsCore containerItemIdentifier:(OCVFSItemID)containerItemIdentifier; @end diff --git a/ownCloud File Provider/FileProviderContentEnumerator.m b/ownCloud File Provider/FileProviderContentEnumerator.m index 4b4fdc7ad..2f494a6d1 100644 --- a/ownCloud File Provider/FileProviderContentEnumerator.m +++ b/ownCloud File Provider/FileProviderContentEnumerator.m @@ -30,7 +30,39 @@ - (void)signalEnumeratorForContainerItemIdentifier:(NSFileProviderItemIdentifier @implementation FileProviderContentEnumerator -- (instancetype)initWithVFSCore:(OCVFSCore *)vfsCore containerItemIdentifier:(NSFileProviderItemIdentifier)containerItemIdentifier; +#pragma mark - Queues ++ (dispatch_queue_t)dispatchQueue +{ + static dispatch_once_t onceToken; + static dispatch_queue_t dispatchQueue; + dispatch_once(&onceToken, ^{ + dispatchQueue = dispatch_queue_create("Enumeration queue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); + }); + + return (dispatchQueue); +} + ++ (OCAsyncSequentialQueue *)queue +{ + static dispatch_once_t onceToken; + static OCAsyncSequentialQueue *queue; + + dispatch_once(&onceToken, ^{ + dispatch_queue_t dispatchQueue = FileProviderContentEnumerator.dispatchQueue; + + queue = [[OCAsyncSequentialQueue alloc] init]; + queue.executor = ^(OCAsyncSequentialQueueJob _Nonnull job, dispatch_block_t _Nonnull completionHandler) { + dispatch_async(dispatchQueue, ^{ + job(completionHandler); + }); + }; + }); + + return (queue); +} + +#pragma mark - Initialization +- (instancetype)initWithVFSCore:(OCVFSCore *)vfsCore containerItemIdentifier:(OCVFSItemID)containerItemIdentifier; { if ((self = [super init]) != nil) { @@ -40,27 +72,13 @@ - (instancetype)initWithVFSCore:(OCVFSCore *)vfsCore containerItemIdentifier:(NS _enumerationObservers = [NSMutableArray new]; _changeObservers = [NSMutableArray new]; - if ([_containerItemIdentifier isEqual:NSFileProviderRootContainerItemIdentifier]) - { - _containerItemIdentifier = _vfsCore.rootNode.itemID; - } - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_displaySettingsChanged:) name:DisplaySettingsChanged object:nil]; } return (self); } -- (void)invalidate -{ - OCLogDebug(@"##### INVALIDATE %@", _containerItemIdentifier); - - [[NSNotificationCenter defaultCenter] removeObserver:self name:DisplaySettingsChanged object:nil]; - - self.content.query.delegate = nil; - self.content = nil; -} - +#pragma mark - Display settings change tracking - (void)_displaySettingsChanged:(NSNotification *)notification { OCLogDebug(@"Received display settings update notification (enumerator for %@)", _containerItemIdentifier); @@ -76,96 +94,244 @@ - (void)_displaySettingsChanged:(NSNotification *)notification } } +#pragma mark - FileProvider API - (void)enumerateItemsForObserver:(id)observer startingAtPage:(NSFileProviderPage)page { - FileProviderEnumeratorObserver *enumerationObserver = [FileProviderEnumeratorObserver new]; - OCLogDebug(@"##### Enumerate ITEMS for observer: %@ fromPage: %@", observer, page); - enumerationObserver.enumerationObserver = observer; - enumerationObserver.enumerationStartPage = page; - enumerationObserver.didProvideInitialItems = NO; + __weak FileProviderContentEnumerator *weakSelf = self; - @synchronized(self) - { - [self->_enumerationObservers addObject:enumerationObserver]; - } + [self requestContentWithErrorHandler:^(NSError *error) { + dispatch_async(dispatch_get_main_queue(), ^{ + [observer finishEnumeratingWithError:error]; + }); + } contentConsumer:^(OCVFSContent *content) { + dispatch_async(dispatch_get_main_queue(), ^{ + [weakSelf provideItemsToEnumerationObserver:observer fromContent:content]; + }); + } observerQueuer:^BOOL(dispatch_block_t completionHandler) { + FileProviderContentEnumerator *strongSelf = weakSelf; - [self _startQuery]; -} + if (strongSelf == nil) { + return(NO); + } -//- (void)enumerateItemsForObserver:(id)observer startingAtPage:(NSFileProviderPage)page -//{ -// if ([_containerItemIdentifier isEqual:NSFileProviderRootContainerItemIdentifier]) -// { -// _containerItemIdentifier = _vfsCore.rootNode.itemID; -// } + FileProviderEnumeratorObserver *enumerationObserver = [FileProviderEnumeratorObserver new]; + + enumerationObserver.enumerationObserver = observer; + enumerationObserver.enumerationStartPage = page; + enumerationObserver.enumerationCompletionHandler = completionHandler; + + [strongSelf->_enumerationObservers addObject:enumerationObserver]; + + return (YES); + }]; // -// [_vfsCore provideContentForContainerItemID:_containerItemIdentifier changesFromSyncAnchor:nil completionHandler:^(NSError * _Nullable error, OCVFSContent * _Nullable content) { -// if (error != nil) -// { -// [observer finishEnumeratingWithError:error]; +// [FileProviderContentEnumerator.queue async:^(dispatch_block_t _Nonnull completionHandler) { +// FileProviderContentEnumerator *strongSelf = weakSelf; +// +// if (strongSelf == nil) { +// completionHandler(); +// return; // } -// else -// { -// self.content = content; // -// // Send VFS children -// if (content.vfsChildNodes.count > 0) -// { -// [observer didEnumerateItems:content.vfsChildNodes]; -// } +// [strongSelf.vfsCore provideContentForContainerItemID:strongSelf->_containerItemIdentifier changesFromSyncAnchor:nil completionHandler:^(NSError * _Nullable error, OCVFSContent * _Nullable content) { +// dispatch_async(FileProviderContentEnumerator.dispatchQueue, ^{ +// FileProviderContentEnumerator *strongSelf = weakSelf; // -// // End enumeration if no query returned -// if (content.query == nil) -// { -// [observer finishEnumeratingUpToPage:nil]; -// } +// if (strongSelf == nil) { +// completionHandler(); +// return; +// } // -// // Continue enumeration if query returned -// if (content.query != nil) -// { -// FileProviderEnumeratorObserver *enumerationObserver = [FileProviderEnumeratorObserver new]; +// if (error != nil) +// { +// [observer finishEnumeratingWithError:error]; +// completionHandler(); +// } +// else +// { +// if (content.isSnapshot) +// { +// // Content is a snapshot, so there's no need to keep the content around - it can be sent now +// [strongSelf provideItemsToEnumerationObserver:observer fromContent:content]; +// completionHandler(); +// } +// else +// { +// // Content is self-updating, so we can send it +// if (strongSelf.content != nil) +// { +// [strongSelf provideItemsToEnumerationObserver:observer fromContent:self.content]; +// completionHandler(); +// } +// else +// { +// FileProviderEnumeratorObserver *enumerationObserver = [FileProviderEnumeratorObserver new]; // -// OCLogDebug(@"##### Enumerate ITEMS for observer: %@ fromPage: %@", observer, page); +// enumerationObserver.enumerationObserver = observer; +// enumerationObserver.enumerationStartPage = page; +// enumerationObserver.didProvideInitialItems = NO; +// enumerationObserver.enumerationCompletionHandler = completionHandler; // -// enumerationObserver.enumerationObserver = observer; -// enumerationObserver.enumerationStartPage = page; -// enumerationObserver.didProvideInitialItems = NO; +// [strongSelf->_enumerationObservers addObject:enumerationObserver]; // -// @synchronized(self) -// { -// [self->_enumerationObservers addObject:enumerationObserver]; +// strongSelf.content = content; +// } +// } // } -// -// [self _startQuery]; -// } -// } +// }); +// }]; // }]; -//} -- (void)_finishAllEnumeratorsWithError:(NSError *)error + /* TODO: + - inspect the page to determine whether this is an initial or a follow-up request + + If this is an enumerator for a directory, the root container or all directories: + - perform a server request to fetch directory contents + If this is an enumerator for the active set: + - perform a server request to update your local database + - fetch the active set from your local database + + - inform the observer about the items returned by the server (possibly multiple times) + - inform the observer that you are finished with this page + */ +} + +- (void)enumerateChangesForObserver:(id)observer fromSyncAnchor:(NSFileProviderSyncAnchor)syncAnchor { - @synchronized(self) + OCLogDebug(@"##### Enumerate CHANGES for observer: %@ fromSyncAnchor: %@", observer, syncAnchor); + + if (syncAnchor != nil) { - for (FileProviderEnumeratorObserver *observer in _enumerationObservers) - { + /** Apple: + If the enumeration fails with NSFileProviderErrorSyncAnchorExpired, we will + drop all cached data and start the enumeration over starting with sync anchor + nil. + */ + dispatch_async(dispatch_get_main_queue(), ^{ +// if ([syncAnchor isEqual:[self->_core.latestSyncAnchor syncAnchorData]]) +// { +// OCLogDebug(@"##### END(LATEST) Enumerate CHANGES for observer: %@ fromSyncAnchor: %@", observer, syncAnchor); +// [observer finishEnumeratingChangesUpToSyncAnchor:syncAnchor moreComing:NO]; +// } +// else + { + OCLogDebug(@"##### END(EXPIRED) Enumerate CHANGES for observer: %@ fromSyncAnchor: %@", observer, syncAnchor); + [observer finishEnumeratingWithError:[NSError errorWithDomain:NSFileProviderErrorDomain code:NSFileProviderErrorSyncAnchorExpired userInfo:nil]]; + } + }); + } + else + { + /** Apple: + "If anchor is nil, then the system is enumerating from scratch: the system wants + to receives changes to reconstruct the list of items in this enumeration as if + starting from an empty list." + */ + + __weak FileProviderContentEnumerator *weakSelf = self; + + [self requestContentWithErrorHandler:^(NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ - [observer.enumerationObserver finishEnumeratingWithError:error]; + [observer finishEnumeratingWithError:error]; }); - } - [_enumerationObservers removeAllObjects]; - - for (FileProviderEnumeratorObserver *observer in _changeObservers) - { + } contentConsumer:^(OCVFSContent *content) { dispatch_async(dispatch_get_main_queue(), ^{ - [observer.changeObserver finishEnumeratingWithError:error]; + [weakSelf provideItemsForChangeObserver:observer fromContent:content]; }); - } - [_changeObservers removeAllObjects]; + } observerQueuer:^BOOL(dispatch_block_t completionHandler) { + FileProviderContentEnumerator *strongSelf = weakSelf; + + if (strongSelf == nil) { + return(NO); + } + + FileProviderEnumeratorObserver *enumerationObserver = [FileProviderEnumeratorObserver new]; + + enumerationObserver.changeObserver = observer; + enumerationObserver.changesFromSyncAnchor = syncAnchor; + enumerationObserver.enumerationCompletionHandler = completionHandler; + + [strongSelf->_changeObservers addObject:enumerationObserver]; + + return (YES); + }]; } } +- (void)invalidate +{ + OCLogDebug(@"##### INVALIDATE %@", _containerItemIdentifier); + + [[NSNotificationCenter defaultCenter] removeObserver:self name:DisplaySettingsChanged object:nil]; + + self.content.query.delegate = nil; + self.content = nil; +} + +#pragma mark - Content retrieval +- (void)requestContentWithErrorHandler:(void(^)(NSError *))errorHandler contentConsumer:(void(^)(OCVFSContent *))contentConsumer observerQueuer:(BOOL(^)(dispatch_block_t completionHandler))observerQueuer +{ + __weak FileProviderContentEnumerator *weakSelf = self; + + [FileProviderContentEnumerator.queue async:^(dispatch_block_t _Nonnull completionHandler) { + FileProviderContentEnumerator *strongSelf = weakSelf; + + if (strongSelf == nil) { + completionHandler(); + return; + } + + [strongSelf.vfsCore provideContentForContainerItemID:strongSelf->_containerItemIdentifier changesFromSyncAnchor:nil completionHandler:^(NSError * _Nullable error, OCVFSContent * _Nullable content) { + dispatch_async(FileProviderContentEnumerator.dispatchQueue, ^{ + FileProviderContentEnumerator *strongSelf = weakSelf; + + if (strongSelf == nil) { + completionHandler(); + return; + } + + if (error != nil) + { + errorHandler(error); + completionHandler(); + } + else + { + if (content.isSnapshot) + { + // Content is a snapshot, so there's no need to keep the content around - it can be sent now + contentConsumer(content); + completionHandler(); + } + else + { + // Content is self-updating, so we can send it + if (strongSelf.content != nil) + { + contentConsumer(strongSelf.content); + completionHandler(); + } + else + { + if (observerQueuer(completionHandler)) + { + strongSelf.content = content; + } + else + { + strongSelf.content = content; + completionHandler(); + } + } + } + } + }); + }]; + }]; +} + - (void)setContent:(OCVFSContent *)content { if (content != nil) @@ -173,6 +339,7 @@ - (void)setContent:(OCVFSContent *)content if (content.query != nil) { [content.core stopQuery:content.query]; + content.query.delegate = nil; } } @@ -353,184 +520,141 @@ - (void)setContent:(OCVFSContent *)content // } } -- (void)_startQuery -{ - OCLogDebug(@"##### Starting query.."); - - OCMeasureEvent(self, @"fp.enumerator", @"Starting enumerator…"); +//- (void)_startQuery +//{ +// OCLogDebug(@"##### Starting query.."); +// +// OCMeasureEvent(self, @"fp.enumerator", @"Starting enumerator…"); +// +// BOOL contentRequested = NO; +// +// @synchronized(self) +// { +// contentRequested = _contentRequested; +// +// if (_contentRequested == NO) +// { +// _contentRequested = YES; +// } +// } +// +// if ((self.content == nil) && !contentRequested) +// { +// [self.vfsCore provideContentForContainerItemID:_containerItemIdentifier changesFromSyncAnchor:nil completionHandler:^(NSError * _Nullable error, OCVFSContent * _Nullable content) { +// if (error != nil) +// { +// [self _finishAllEnumeratorsWithError:error]; +// } +// else +// { +// if (content.isSnapshot) +// { +// // No query that would call us back later, so just send the existing content now +// [self sendContent:content]; +// +// @synchronized(self) +// { +// self->_contentRequested = NO; +// } +// } +// else +// { +// // There will be callbacks from the query, no need to send content now already +// self.content = content; +// } +// } +// }]; +// } +// else +// { +// OCLogDebug(@"Query already running.."); +// [self sendContent:self.content]; +// } +// +//} - BOOL contentRequested = NO; +//- (void)sendContent:(OCVFSContent *)content +//{ +// if (content != nil) +// { +// @synchronized(self) +// { +// if (_enumerationObservers.count!=0) +// { +// dispatch_async(dispatch_get_main_queue(), ^{ +// [self provideItemsForEnumerationObserverFromContent:content]; +// }); +// } +// +// if (_changeObservers.count!=0) +// { +// dispatch_async(dispatch_get_main_queue(), ^{ +// [self provideItemsForChangeObserverFromConten:content]; +// }); +// } +// } +// } +//} - @synchronized(self) +#pragma mark - Content distribution +- (BOOL)provideItemsToEnumerationObserver:(id)enumerationObserver fromContent:(OCVFSContent *)content +{ + if (((content.query.state == OCQueryStateContentsFromCache) || ((content.query.state == OCQueryStateWaitingForServerReply) && (content.query.queryResults.count > 0)) || (content.query.state == OCQueryStateIdle)) + || ((content.query == nil) && (content != nil))) { - contentRequested = _contentRequested; + NSArray *queryResults = content.query.queryResults; + OCBookmarkUUIDString bookmarkUUIDString = content.core.bookmark.uuid.UUIDString; - if (_contentRequested == NO) + for (OCItem *item in queryResults) { - _contentRequested = YES; + item.bookmarkUUID = bookmarkUUIDString; } - } - - if ((self.content == nil) && !contentRequested) - { - [self.vfsCore provideContentForContainerItemID:_containerItemIdentifier changesFromSyncAnchor:nil completionHandler:^(NSError * _Nullable error, OCVFSContent * _Nullable content) { - if (error != nil) - { - [self _finishAllEnumeratorsWithError:error]; - } - else - { - self.content = content; - - if (self.content.query == nil) - { - // No query that would call us back later, so just send the existing content now - [self sendExistingContent]; - } - } - }]; - } - else - { - OCLogDebug(@"Query already running.."); - [self sendExistingContent]; - } - /* TODO: - - inspect the page to determine whether this is an initial or a follow-up request - - If this is an enumerator for a directory, the root container or all directories: - - perform a server request to fetch directory contents - If this is an enumerator for the active set: - - perform a server request to update your local database - - fetch the active set from your local database - - - inform the observer about the items returned by the server (possibly multiple times) - - inform the observer that you are finished with this page - */ -} + OCLogDebug(@"##### PROVIDE ITEMS TO %ld --ENUMERATION-- OBSERVER %@ FOR %@: %@", _enumerationObservers.count, enumerationObserver, content.query.queryLocation.path, queryResults); -- (void)sendExistingContent -{ - if (self.content != nil) - { - @synchronized(self) - { - if (_enumerationObservers.count!=0) + dispatch_async(dispatch_get_main_queue(), ^{ + if (content.vfsChildNodes.count > 0) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self provideItemsForEnumerationObserver]; - }); + [enumerationObserver didEnumerateItems:content.vfsChildNodes]; } - if (_changeObservers.count!=0) + if (queryResults.count > 0) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self provideItemsForChangeObserver]; - }); + [enumerationObserver didEnumerateItems:queryResults]; } - } - } -} - -- (void)provideItemsForEnumerationObserver -{ - if (((_content.query.state == OCQueryStateContentsFromCache) || ((_content.query.state == OCQueryStateWaitingForServerReply) && (_content.query.queryResults.count > 0)) || (_content.query.state == OCQueryStateIdle)) - || ((_content.query == nil) && (_content != nil))) - { - @synchronized(self) - { - NSMutableArray *removeObservers = [NSMutableArray new]; - - for (FileProviderEnumeratorObserver *observer in _enumerationObservers) - { - if (observer.enumerationObserver != nil) - { - if (!observer.didProvideInitialItems) - { - NSArray *queryResults = _content.query.queryResults; - OCBookmarkUUIDString bookmarkUUIDString = _content.core.bookmark.uuid.UUIDString; - - for (OCItem *item in queryResults) - { - item.customIdentifier1 = bookmarkUUIDString; - item.customIdentifier2 = _content.containerNode.itemIdentifier; - } - - OCLogDebug(@"##### PROVIDE ITEMS TO %ld --ENUMERATION-- OBSERVER %@ FOR %@: %@", _enumerationObservers.count, observer.enumerationObserver, _content.query.queryLocation.path, queryResults); - - observer.didProvideInitialItems = YES; - - if (_content.vfsChildNodes.count > 0) - { - [observer.enumerationObserver didEnumerateItems:_content.vfsChildNodes]; - } - - if (queryResults.count > 0) - { - // NSUInteger offset = 0, count = queryResults.count; - // - // while (offset < count) - // { - // NSUInteger sliceCount = 100; - // - // if (offset + sliceCount > count) - // { - // sliceCount = count - offset; - // } - // - // NSArray *partialResults = [queryResults subarrayWithRange:NSMakeRange(offset, sliceCount)]; - // - // [observer.enumerationObserver didEnumerateItems:partialResults]; - // - // offset += sliceCount; - // }; - - [observer.enumerationObserver didEnumerateItems:queryResults]; - } - - [observer.enumerationObserver finishEnumeratingUpToPage:nil]; - [removeObservers addObject:observer]; - } - } - } + [enumerationObserver finishEnumeratingUpToPage:nil]; + }); - [_enumerationObservers removeObjectsInArray:removeObservers]; - } + return (YES); } + + return (NO); } -- (void)provideItemsForChangeObserver +- (BOOL)provideItemsForChangeObserver:(id)changeObserver fromContent:(OCVFSContent *)content { - @synchronized(self) - { - if (_changeObservers.count > 0) - { - OCLogDebug(@"##### PROVIDE ITEMS TO %lu --CHANGE-- OBSERVER FOR %@: %@", _changeObservers.count, _content.query.queryLocation.path, _content.query.queryResults); + OCLogDebug(@"##### PROVIDE ITEMS TO %lu --CHANGE-- OBSERVER FOR %@: %@", _changeObservers.count, content.query.queryLocation.path, content.query.queryResults); - NSArray *queryResults = _content.query.queryResults; - OCBookmarkUUIDString bookmarkUUIDString = _content.core.bookmark.uuid.UUIDString; + NSArray *queryResults = content.query.queryResults; + OCBookmarkUUIDString bookmarkUUIDString = content.core.bookmark.uuid.UUIDString; - for (OCItem *item in queryResults) - { - item.customIdentifier1 = bookmarkUUIDString; - item.customIdentifier2 = _content.containerNode.itemIdentifier; - } + for (OCItem *item in queryResults) + { + item.bookmarkUUID = bookmarkUUIDString; + } - NSFileProviderSyncAnchor syncAnchor = [_content.core.latestSyncAnchor syncAnchorData]; + NSFileProviderSyncAnchor syncAnchor = [content.core.latestSyncAnchor syncAnchorData]; - for (FileProviderEnumeratorObserver *observer in _changeObservers) - { - [observer.changeObserver didUpdateItems:queryResults]; - [observer.changeObserver finishEnumeratingChangesUpToSyncAnchor:syncAnchor moreComing:NO]; - } + dispatch_async(dispatch_get_main_queue(), ^{ + [changeObserver didUpdateItems:queryResults]; + [changeObserver finishEnumeratingChangesUpToSyncAnchor:syncAnchor moreComing:NO]; + }); - [_changeObservers removeAllObjects]; - } - } + return (YES); } +#pragma mark - OCQuery delegate - (void)queryHasChangesAvailable:(OCQuery *)query { OCLogDebug(@"##### Query for %@ has changes. Query state: %lu, SinceSyncAnchor: %@, Changes available: %d", query.queryLocation.path, (unsigned long)query.state, query.querySinceSyncAnchor, query.hasChangesAvailable); @@ -539,17 +663,29 @@ - (void)queryHasChangesAvailable:(OCQuery *)query ((query.state == OCQueryStateWaitingForServerReply) && (query.queryResults.count > 0)) || (query.state == OCQueryStateIdle)) { - dispatch_async(dispatch_get_main_queue(), ^{ - @synchronized(self) + dispatch_async(FileProviderContentEnumerator.dispatchQueue, ^{ + // Send content to enumeration observers + NSArray *enumerationObservers = [self->_enumerationObservers copy]; + + for (FileProviderEnumeratorObserver *observer in enumerationObservers) { - if (self->_enumerationObservers.count > 0) + if ([self provideItemsToEnumerationObserver:observer.enumerationObserver fromContent:self.content]) { - [self provideItemsForEnumerationObserver]; + [observer completeEnumeration]; + [self->_enumerationObservers removeObject:observer]; } + } + + + // Send content to change observers + NSArray *changeObservers = [self->_changeObservers copy]; - if (self->_changeObservers.count > 0) + for (FileProviderEnumeratorObserver *observer in changeObservers) + { + if ([self provideItemsForChangeObserver:observer.changeObserver fromContent:self.content]) { - [self provideItemsForChangeObserver]; + [observer completeEnumeration]; + [self->_changeObservers removeObject:observer]; } } }); @@ -561,64 +697,6 @@ - (void)query:(OCQuery *)query failedWithError:(NSError *)error OCLogDebug(@"### Query failed with error: %@", error); } -- (void)enumerateChangesForObserver:(id)observer fromSyncAnchor:(NSFileProviderSyncAnchor)syncAnchor -{ - OCLogDebug(@"##### Enumerate CHANGES for observer: %@ fromSyncAnchor: %@", observer, syncAnchor); - - if (syncAnchor != nil) - { - /** Apple: - If the enumeration fails with NSFileProviderErrorSyncAnchorExpired, we will - drop all cached data and start the enumeration over starting with sync anchor - nil. - */ - dispatch_async(dispatch_get_main_queue(), ^{ -// if ([syncAnchor isEqual:[self->_core.latestSyncAnchor syncAnchorData]]) -// { -// OCLogDebug(@"##### END(LATEST) Enumerate CHANGES for observer: %@ fromSyncAnchor: %@", observer, syncAnchor); -// [observer finishEnumeratingChangesUpToSyncAnchor:syncAnchor moreComing:NO]; -// } -// else - { - OCLogDebug(@"##### END(EXPIRED) Enumerate CHANGES for observer: %@ fromSyncAnchor: %@", observer, syncAnchor); - [observer finishEnumeratingWithError:[NSError errorWithDomain:NSFileProviderErrorDomain code:NSFileProviderErrorSyncAnchorExpired userInfo:nil]]; - } - }); - } - else - { - /** Apple: - "If anchor is nil, then the system is enumerating from scratch: the system wants - to receives changes to reconstruct the list of items in this enumeration as if - starting from an empty list." - */ - -// [_vfsCore provideContentForContainerItemID:_containerItemIdentifier changesFromSyncAnchor:nil completionHandler:^(NSError * _Nullable error, OCVFSContent * _Nullable content) { -// if (error != nil) -// { -// [observer finishEnumeratingWithError:error]; -// } -// else -// { -//// [observer didEnumerateItems:content.vfsChildNodes]; -//// [observer finishEnumeratingUpToPage:nil]; -// } -// }]; - - FileProviderEnumeratorObserver *enumerationObserver = [FileProviderEnumeratorObserver new]; - - enumerationObserver.changeObserver = observer; - enumerationObserver.changesFromSyncAnchor = syncAnchor; - - @synchronized(self) - { - [_enumerationObservers addObject:enumerationObserver]; - } - - [self _startQuery]; - } -} - //- (void)currentSyncAnchorWithCompletionHandler:(void (^)(NSFileProviderSyncAnchor _Nullable))completionHandler //{ // OCLogDebug(@"#### Request current sync anchor"); @@ -652,6 +730,7 @@ - (void)enumerateChangesForObserver:(id)observer f // return (_measurement); //} +#pragma mark - Log tags + (NSArray *)logTags { return (@[ @"FPEnum" ]); diff --git a/ownCloud File Provider/FileProviderEnumeratorObserver.h b/ownCloud File Provider/FileProviderEnumeratorObserver.h index baefa3e91..bdcaab76e 100644 --- a/ownCloud File Provider/FileProviderEnumeratorObserver.h +++ b/ownCloud File Provider/FileProviderEnumeratorObserver.h @@ -30,4 +30,8 @@ @property(strong) NSFileProviderSyncAnchor changesFromSyncAnchor; @property(strong) OCQuery *changeQuery; +@property(copy) dispatch_block_t enumerationCompletionHandler; + +- (void)completeEnumeration; + @end diff --git a/ownCloud File Provider/FileProviderEnumeratorObserver.m b/ownCloud File Provider/FileProviderEnumeratorObserver.m index 10de87fc7..427bdbff4 100644 --- a/ownCloud File Provider/FileProviderEnumeratorObserver.m +++ b/ownCloud File Provider/FileProviderEnumeratorObserver.m @@ -20,4 +20,20 @@ @implementation FileProviderEnumeratorObserver +- (void)completeEnumeration +{ + dispatch_block_t enumerationCompletionHandler; + + @synchronized(self) + { + enumerationCompletionHandler = _enumerationCompletionHandler; + _enumerationCompletionHandler = nil; + } + + if (enumerationCompletionHandler != nil) + { + enumerationCompletionHandler(); + } +} + @end diff --git a/ownCloud File Provider/FileProviderExtension.h b/ownCloud File Provider/FileProviderExtension.h index 9a0888bd0..dc271abc8 100644 --- a/ownCloud File Provider/FileProviderExtension.h +++ b/ownCloud File Provider/FileProviderExtension.h @@ -19,7 +19,7 @@ #import #import -@interface FileProviderExtension : NSFileProviderExtension +@interface FileProviderExtension : NSFileProviderExtension { __weak OCCore *_core; NSUInteger _coreRetainCount; diff --git a/ownCloud File Provider/FileProviderExtension.m b/ownCloud File Provider/FileProviderExtension.m index 2b27a1b51..0f4be3b71 100644 --- a/ownCloud File Provider/FileProviderExtension.m +++ b/ownCloud File Provider/FileProviderExtension.m @@ -88,52 +88,7 @@ - (OCVFSCore *)vfsCore { if (_vfsCore == nil) { - _vfsCore = [[OCVFSCore alloc] init]; - _vfsCore.delegate = self; -// -// [_vfsCore addNodes:@[ -// [OCVFSNode virtualFolderAtPath:@"/" location:nil], -// [OCVFSNode virtualFolderInPath:@"/" withName:@"Hi" location:nil], -// [OCVFSNode virtualFolderInPath:@"/" withName:@"Hallo" location:nil], -// [OCVFSNode virtualFolderInPath:@"/" withName:@"Hello" location:nil], -// [OCVFSNode virtualFolderInPath:@"/Hello/" withName:@"world" location:nil] -// ]]; - } - - if (_vfsCore.rootNode == nil) - { - OCCore *core; - - if ((core = self.core) != nil) - { - if (core.useDrives) - { - if (core.drives.count > 0) - { - NSMutableArray *nodes = [NSMutableArray new]; - - [nodes addObject:[OCVFSNode virtualFolderAtPath:@"/" location:nil]]; - - for (OCDrive *drive in core.drives) - { - OCLocation *driveRootLocation = drive.rootLocation; - driveRootLocation.bookmarkUUID = self.bookmark.uuid; - - [nodes addObject:[OCVFSNode virtualFolderAtPath:[@"/" stringByAppendingPathComponent:drive.name].normalizedDirectoryPath location:driveRootLocation]]; - } - - [_vfsCore addNodes:nodes]; - } - } - else - { - OCLocation *legacyRoot = [[OCLocation alloc] initWithBookmarkUUID:core.bookmark.uuid driveID:nil path:@"/"]; - - [_vfsCore addNodes:@[ - [OCVFSNode virtualFolderAtPath:@"/" location:legacyRoot] - ]]; - } - } + _vfsCore = [VFSManager.sharedManager vfsForBookmark:self.bookmark]; } return (_vfsCore); @@ -231,7 +186,7 @@ - (NSFileProviderItem)itemForIdentifier:(NSFileProviderItemIdentifier)identifier __block NSFileProviderItem item = nil; __block NSError *returnError = nil; - if ([identifier isEqual:NSFileProviderRootContainerItemIdentifier]) + if ([identifier isEqual:NSFileProviderRootContainerItemIdentifier] || [identifier isEqual:OCVFSItemIDRoot]) { item = (NSFileProviderItem)self.vfsCore.rootNode; } @@ -328,6 +283,18 @@ - (NSFileProviderItemIdentifier)persistentIdentifierForItemAtURL:(NSURL *)url return ([self.vfsCore itemIdentifierForURL:url]); } +- (nullable OCItem *)cachedItemInParent:(OCItem *)parentItem withName:(NSString *)name isDirectory:(BOOL)isDirectory error:(__autoreleasing NSError * _Nullable * _Nullable)outError +{ + OCItem *item; + + if ((item = [self.core cachedItemInParent:parentItem withName:name isDirectory:isDirectory error:outError]) != nil) + { + item.bookmarkUUID = self.core.bookmark.uuid.UUIDString; + } + + return (item); +} + - (void)providePlaceholderAtURL:(NSURL *)url completionHandler:(void (^)(NSError * _Nullable))completionHandler { NSFileProviderItemIdentifier identifier = [self persistentIdentifierForItemAtURL:url]; @@ -531,7 +498,7 @@ - (void)createDirectoryWithName:(NSString *)directoryName inParentItemIdentifier { OCItem *existingItem; - if ((existingItem = [self.core cachedItemInParent:parentItem withName:directoryName isDirectory:YES error:NULL]) != nil) + if ((existingItem = [self cachedItemInParent:parentItem withName:directoryName isDirectory:YES error:NULL]) != nil) { FPLogCmd(@"Completed with collission with existingItem=%@ (locally detected)", existingItem); if (@available(iOS 13.3, *)) @@ -564,7 +531,7 @@ - (void)createDirectoryWithName:(NSString *)directoryName inParentItemIdentifier // Folder already exists on the server OCItem *existingItem; - if ((existingItem = [self.core cachedItemInParent:parentItem withName:directoryName isDirectory:YES error:NULL]) != nil) + if ((existingItem = [self cachedItemInParent:parentItem withName:directoryName isDirectory:YES error:NULL]) != nil) { FPLogCmd(@"Completed with collission with existingItem=%@ (server response)", existingItem); if (@available(iOS 13.3, *)) @@ -735,7 +702,7 @@ - (void)importDocumentAtURL:(NSURL *)fileURL toParentItemIdentifier:(NSFileProvi // Detect name collissions OCItem *existingItem; - if ((existingItem = [self.core cachedItemInParent:parentItem withName:importFileName isDirectory:NO error:NULL]) != nil) + if ((existingItem = [self cachedItemInParent:parentItem withName:importFileName isDirectory:NO error:NULL]) != nil) { // Return collission error FPLogCmd(@"Completed with collission with existingItem=%@ (local)", existingItem); @@ -958,13 +925,29 @@ - (void)setLastUsedDate:(NSDate *)lastUsedDate forItemIdentifier:(NSFileProvider if (![containerItemIdentifier isEqualToString:NSFileProviderWorkingSetContainerItemIdentifier]) { - return ([[FileProviderContentEnumerator alloc] initWithVFSCore:self.vfsCore containerItemIdentifier:containerItemIdentifier]); + if ([containerItemIdentifier isEqual:NSFileProviderRootContainerItemIdentifier]) + { + // Request core to enable continuous checks for new/changed spaces if app is not running in parallel + // Also shorten time to core availability when users decide to descend into an actual drive + OCCore *core = nil; + NSError *coreError = nil; -// FileProviderEnumerator *enumerator = [[FileProviderEnumerator alloc] initWithBookmark:self.bookmark enumeratedItemIdentifier:containerItemIdentifier]; -// -// enumerator.fileProviderExtension = self; -// -// return (enumerator); + if ((core = [self coreWithError:&coreError]) == nil) + { + // Error requesting core + if (error != NULL) + { + *error = coreError; + } + + return (nil); + } + + // Change identifier to VFS RootItem ID + containerItemIdentifier = OCVFSItemIDRoot; + } + + return ([[FileProviderContentEnumerator alloc] initWithVFSCore:self.vfsCore containerItemIdentifier:containerItemIdentifier]); } return (nil); @@ -1087,21 +1070,6 @@ - (OCCore *)core return ([self coreWithError:nil]); } -- (void)acquireCoreForBookmark:(OCBookmark *)bookmark completionHandler:(void (^)(NSError * _Nullable, OCCore * _Nullable))completionHandler -{ - NSError *error = nil; - OCCore *core = [self coreWithError:&error]; - - // TODO - - completionHandler(error, core); -} - -- (void)relinquishCoreForBookmark:(OCBookmark *)bookmark completionHandler:(void (^)(NSError * _Nullable))completionHandler -{ - // TODO -} - - (OCCore *)coreWithError:(NSError **)outError { OCLogDebug(@"FileProviderExtension[%p].core[enter]: _core=%p, bookmark=%@", self, _core, self.bookmark); diff --git a/ownCloud File Provider/OCItem+FileProviderItem.m b/ownCloud File Provider/OCItem+FileProviderItem.m index 97681c8a8..5d725affb 100644 --- a/ownCloud File Provider/OCItem+FileProviderItem.m +++ b/ownCloud File Provider/OCItem+FileProviderItem.m @@ -33,36 +33,21 @@ @implementation OCItem (FileProviderItem) - (NSFileProviderItemIdentifier)itemIdentifier { -// if ([self.path isEqual:@"/"]) -// { -// return (NSFileProviderRootContainerItemIdentifier); -// } -// -// return (self.localID); + OCVFSItemID vfsItemID = self.vfsItemID; - return ([OCVFSCore composeVFSItemIDForOCItemWithBookmarkUUID:self.customIdentifier1 driveID:self.driveID localID:self.localID]); + return ([vfsItemID isEqual:OCVFSItemIDRoot] ? NSFileProviderRootContainerItemIdentifier : vfsItemID); } - (NSFileProviderItemIdentifier)parentItemIdentifier { - if (self.customIdentifier2 != nil) - { - return (self.customIdentifier2); - } + OCVFSItemID vfsParentItemID = self.vfsParentItemID; - return ([OCVFSCore composeVFSItemIDForOCItemWithBookmarkUUID:self.customIdentifier1 driveID:self.driveID localID:self.parentLocalID]); - -// if ([[self.path stringByDeletingLastPathComponent] isEqualToString:@"/"]) -// { -// return (NSFileProviderRootContainerItemIdentifier); -// } -// -// return (self.parentLocalID); + return ([vfsParentItemID isEqual:OCVFSItemIDRoot] ? NSFileProviderRootContainerItemIdentifier : vfsParentItemID); } - (NSString *)filename { - return (self.name); + return (self.vfsItemName); } + (NSDictionary *)overriddenUTIByMIMEType diff --git a/ownCloud File Provider/OCVFSNode+FileProviderItem.m b/ownCloud File Provider/OCVFSNode+FileProviderItem.m index 47047546c..95f9d21f1 100644 --- a/ownCloud File Provider/OCVFSNode+FileProviderItem.m +++ b/ownCloud File Provider/OCVFSNode+FileProviderItem.m @@ -29,24 +29,26 @@ - (NSString *)filename - (NSFileProviderItemIdentifier)itemIdentifier { - if (self.isRootNode) + OCVFSItemID vfsItemID = self.vfsItemID; + + if ([vfsItemID isEqual:OCVFSItemIDRoot]) { return (NSFileProviderRootContainerItemIdentifier); } - return (self.itemID); + return (vfsItemID); } - (NSFileProviderItemIdentifier)parentItemIdentifier { - OCVFSNode *parentNode = self.parentNode; + OCVFSItemID vfsParentItemID = self.vfsParentItemID; - if (parentNode.isRootNode) + if ([vfsParentItemID isEqual:OCVFSItemIDRoot]) { return (NSFileProviderRootContainerItemIdentifier); } - return (parentNode.itemID); + return (vfsParentItemID); } - (UTType *)contentType diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index e1336224c..6a2fcae91 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -321,6 +321,8 @@ DC4332002472E1B4002DC0E5 /* OCLicenseEMMProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = DC4331FE2472E1B4002DC0E5 /* OCLicenseEMMProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC4332012472E1B4002DC0E5 /* OCLicenseEMMProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DC4331FF2472E1B4002DC0E5 /* OCLicenseEMMProvider.m */; }; DC44343E21ABFA5200376B16 /* StaticLoginProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC44343D21ABFA5200376B16 /* StaticLoginProfile.swift */; }; + DC49B55928365C5F00DAF13B /* OCVault+VFSManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DC49B55728365C5F00DAF13B /* OCVault+VFSManager.h */; }; + DC49B55A28365C5F00DAF13B /* OCVault+VFSManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DC49B55828365C5F00DAF13B /* OCVault+VFSManager.m */; }; DC4C575D233958B70098BAE9 /* FixedHeightImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4C575C233958B70098BAE9 /* FixedHeightImageView.swift */; }; DC4D5A0A247C1398008ADDB6 /* MessageGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4D5A09247C1398008ADDB6 /* MessageGroup.swift */; }; DC4FEAE7209E3A7700D4476B /* OCIssue+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4FEAE6209E3A7700D4476B /* OCIssue+Extension.swift */; }; @@ -509,6 +511,8 @@ DCE4E4A324C1FB750051722F /* icon-search.tvg in Resources */ = {isa = PBXBuildFile; fileRef = 239F437D20D0EE6300B1276D /* icon-search.tvg */; }; DCE4E4C724C255E00051722F /* AppExtensionNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE4E4C624C255E00051722F /* AppExtensionNavigationController.swift */; }; DCE684F6241BD4E800799C30 /* Branding.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3931206A2326451900E8DFBA /* Branding.plist */; }; + DCEA7F41282D3B110050A3C0 /* VFSManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DCEA7F3F282D3B110050A3C0 /* VFSManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCEA7F42282D3B110050A3C0 /* VFSManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DCEA7F40282D3B110050A3C0 /* VFSManager.m */; }; DCEAF06D280767CF00980B6D /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = DCEAF06C280767CF00980B6D /* OpenSSL */; }; DCEAF08A2808254800980B6D /* DriveListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCEAF0892808254800980B6D /* DriveListCell.swift */; }; DCEAF08D28084B3800980B6D /* Down in Frameworks */ = {isa = PBXBuildFile; productRef = DCEAF08C28084B3800980B6D /* Down */; }; @@ -1303,6 +1307,8 @@ DC44344321AC031600376B16 /* StaticLoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLoginViewController.swift; sourceTree = ""; }; DC4434C321AC894700376B16 /* StaticLoginStepViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLoginStepViewController.swift; sourceTree = ""; }; DC4434C521AC898700376B16 /* StaticLoginSetupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLoginSetupViewController.swift; sourceTree = ""; }; + DC49B55728365C5F00DAF13B /* OCVault+VFSManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCVault+VFSManager.h"; sourceTree = ""; }; + DC49B55828365C5F00DAF13B /* OCVault+VFSManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCVault+VFSManager.m"; sourceTree = ""; }; DC4C575C233958B70098BAE9 /* FixedHeightImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixedHeightImageView.swift; sourceTree = ""; }; DC4D5A09247C1398008ADDB6 /* MessageGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageGroup.swift; sourceTree = ""; }; DC4FEAE6209E3A7700D4476B /* OCIssue+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCIssue+Extension.swift"; sourceTree = ""; }; @@ -1490,6 +1496,8 @@ DCE93FEE21FCA434000E14F2 /* libzip.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = libzip.xcodeproj; path = external/libzip/libzip.xcodeproj; sourceTree = SOURCE_ROOT; }; DCE974B1207E3AF80069FC2B /* ThemeNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeNavigationController.swift; sourceTree = ""; }; DCE974BB207EACA60069FC2B /* UIImage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = ""; }; + DCEA7F3F282D3B110050A3C0 /* VFSManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VFSManager.h; sourceTree = ""; }; + DCEA7F40282D3B110050A3C0 /* VFSManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VFSManager.m; sourceTree = ""; }; DCEAF0892808254800980B6D /* DriveListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DriveListCell.swift; sourceTree = ""; }; DCEC3DE3242F665D0076B43C /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; DCEE1C9B23A0EADD00FE8D98 /* LicenseOfferView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicenseOfferView.swift; sourceTree = ""; }; @@ -2774,6 +2782,7 @@ DCF575E52796CBB3003BEBBA /* View Providers */, DC774E5422F44DF6000B11A1 /* SDK Extensions */, DC0030BE2350B1CE00BB8570 /* Tools */, + DCEA7F38282D3ACA0050A3C0 /* VFS */, DC774E5B22F44E4A000B11A1 /* ZIP Archive */, DC774E6522F44EA7000B11A1 /* Resources */, ); @@ -3070,6 +3079,17 @@ name = Products; sourceTree = ""; }; + DCEA7F38282D3ACA0050A3C0 /* VFS */ = { + isa = PBXGroup; + children = ( + DCEA7F40282D3B110050A3C0 /* VFSManager.m */, + DCEA7F3F282D3B110050A3C0 /* VFSManager.h */, + DC49B55828365C5F00DAF13B /* OCVault+VFSManager.m */, + DC49B55728365C5F00DAF13B /* OCVault+VFSManager.h */, + ); + path = VFS; + sourceTree = ""; + }; DCEAF0842808250E00980B6D /* Collection Views */ = { isa = PBXGroup; children = ( @@ -3371,6 +3391,7 @@ DCF2DA8124C836240026D790 /* OCBookmark+FPServices.h in Headers */, DC66F3A523965A1400CF4812 /* NSDate+RFC3339.h in Headers */, DC0030C22350B1CE00BB8570 /* NSData+Encoding.h in Headers */, + DCEA7F41282D3B110050A3C0 /* VFSManager.h in Headers */, DCCD77792604C91600098573 /* NSDate+ComputedTimes.h in Headers */, DCD71E7F27427463001592C6 /* BuildOptions.h in Headers */, DCB458ED2604A7D4006A02AB /* OCQueryCondition+SearchSegmenter.h in Headers */, @@ -3379,6 +3400,7 @@ DC049156258C00C400DEDC27 /* OCFileProviderServiceStandby.h in Headers */, DCFEFE972368D099009A142F /* OCLicenseEnvironment.h in Headers */, DC6A0E5426EA9E740076B533 /* AppLockSettings.h in Headers */, + DC49B55928365C5F00DAF13B /* OCVault+VFSManager.h in Headers */, DC36885824DC98BF00333600 /* OCFileProviderServiceSession.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4485,6 +4507,7 @@ buildActionMask = 2147483647; files = ( DCFEFE9D2368D7FA009A142F /* OCLicenseObserver.m in Sources */, + DC49B55A28365C5F00DAF13B /* OCVault+VFSManager.m in Sources */, DC66F39D239659C000CF4812 /* OCASN1.m in Sources */, DCF072ED27986CCA00E0B01D /* OCResourceTextPlaceholder+ViewProvider.m in Sources */, DCCD778C2604C91B00098573 /* NSDate+ComputedTimes.m in Sources */, @@ -4503,6 +4526,7 @@ DCF2DA8224C836240026D790 /* OCBookmark+FPServices.m in Sources */, DCFEFE2B236876BD009A142F /* OCLicenseManager.m in Sources */, DCDC20A22399A715003CFF5B /* OCCore+LicenseEnvironment.m in Sources */, + DCEA7F42282D3B110050A3C0 /* VFSManager.m in Sources */, DCDC20AC2399A8CF003CFF5B /* OCLicenseEnterpriseProvider.m in Sources */, DC70398626128B89009F2DC1 /* NSString+ByteCountParser.m in Sources */, DCFEFE3A236877A7009A142F /* OCLicenseFeature.m in Sources */, @@ -4898,7 +4922,7 @@ APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; APP_SHORT_VERSION = 12.0; - APP_VERSION = 216; + APP_VERSION = 217; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -4968,7 +4992,7 @@ APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; APP_SHORT_VERSION = 12.0; - APP_VERSION = 216; + APP_VERSION = 217; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; diff --git a/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme b/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme index f66769198..45f5993ea 100644 --- a/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme +++ b/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme @@ -123,7 +123,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "en" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/ownCloudAppFramework/VFS/OCVault+VFSManager.h b/ownCloudAppFramework/VFS/OCVault+VFSManager.h new file mode 100644 index 000000000..629e82303 --- /dev/null +++ b/ownCloudAppFramework/VFS/OCVault+VFSManager.h @@ -0,0 +1,27 @@ +// +// OCVault+VFSManager.h +// ownCloudApp +// +// Created by Felix Schwarz on 19.05.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface OCVault (VFSManager) + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudAppFramework/VFS/OCVault+VFSManager.m b/ownCloudAppFramework/VFS/OCVault+VFSManager.m new file mode 100644 index 000000000..18bdee873 --- /dev/null +++ b/ownCloudAppFramework/VFS/OCVault+VFSManager.m @@ -0,0 +1,139 @@ +// +// OCVault+VFSManager.m +// ownCloudApp +// +// Created by Felix Schwarz on 19.05.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import "OCVault+VFSManager.h" +#import "OCVFSNode+FileProviderItem.h" +#import "OCItem+FileProviderItem.h" +#import "VFSManager.h" + +@implementation OCVault (VFSManager) + +#pragma mark - OCVaultVFSTranslation +- (OCVFSNode *)vfsNodeForDriveID:(OCDriveID)driveID +{ + return ([self.vfs driveRootNodeForLocation:[[OCLocation alloc] initWithBookmarkUUID:self.bookmark.uuid driveID:driveID path:@"/"]]); +} + +- (nullable NSSet *)vfsRefreshIDsForDriveChangesWithAdditions:(nullable NSArray *)addedDrives updates:(nullable NSArray *)updatedDrives removals:(nullable NSArray *)removedDrives +{ + NSMutableSet *vfsIDs = [NSMutableSet new]; + + void(^AddDriveParents)(NSArray *drives) = ^(NSArray *drives) { + for (OCDrive *drive in drives) + { + OCVFSNode *driveParentNode; + + if ((driveParentNode = [self vfsNodeForDriveID:drive.identifier].parentNode) != nil) + { + [vfsIDs addObject:driveParentNode.vfsItemID]; + } + } + }; + + AddDriveParents(addedDrives); + AddDriveParents(updatedDrives); + + if (removedDrives.count > 0) + { + AddDriveParents(removedDrives); + + [vfsIDs addObject:OCVFSItemIDRoot]; + } + + return (vfsIDs); +} + +- (nullable NSArray *)vfsRefreshParentIDsForItem:(nonnull OCItem *)item +{ + // Drive-based layout with VFS + OCVFSNode *vfsNode = nil; + OCVFSItemID vfsItemID = nil, parentVFSItemID = nil; + + item.bookmarkUUID = self.bookmark.uuid.UUIDString; + + switch (item.type) + { + case OCItemTypeFile: + if (item.path.parentPath.isRootPath) + { + // Parent is root + if (item.driveID) + { + if ((vfsNode = [self vfsNodeForDriveID:item.driveID]) != nil) + { + vfsItemID = vfsNode.vfsItemID; + } + } + else + { + vfsItemID = OCVFSItemIDRoot; + } + } + else + { + vfsItemID = item.vfsParentItemID; + } + break; + + case OCItemTypeCollection: + if (item.isRoot) + { + // Folder is root folder of drive + if ((vfsNode = [self vfsNodeForDriveID:item.driveID]) != nil) + { + vfsItemID = vfsNode.vfsItemID; + parentVFSItemID = vfsNode.vfsParentItemID; + } + } + else + { + // Regular folder on drive + vfsItemID = item.vfsItemID; + + if (item.path.parentPath.isRootPath) + { + // Parent folder is root folder of drive + if ((vfsNode = [self vfsNodeForDriveID:item.driveID]) != nil) + { + parentVFSItemID = vfsNode.vfsItemID; + } + } + } + break; + } + + if (vfsItemID != nil) + { + if (parentVFSItemID != nil) + { + return (@[ parentVFSItemID, vfsItemID ]); + } + + return (@[ vfsItemID ]); + } + + return (nil); +} + +#pragma mark - OCVaultVFSProvider +- (OCVFSCore *)provideVFS +{ + return ([VFSManager.sharedManager vfsForVault:self]); +} + +@end diff --git a/ownCloudAppFramework/VFS/VFSManager.h b/ownCloudAppFramework/VFS/VFSManager.h new file mode 100644 index 000000000..dcf455969 --- /dev/null +++ b/ownCloudAppFramework/VFS/VFSManager.h @@ -0,0 +1,33 @@ +// +// VFSManager.h +// ownCloudApp +// +// Created by Felix Schwarz on 12.05.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface VFSManager : NSObject + +@property(readonly,nonatomic,class) VFSManager *sharedManager; + +- (OCVFSCore *)vfsForBookmark:(OCBookmark *)bookmark; +- (OCVFSCore *)vfsForVault:(OCVault *)vault; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudAppFramework/VFS/VFSManager.m b/ownCloudAppFramework/VFS/VFSManager.m new file mode 100644 index 000000000..9b96703a4 --- /dev/null +++ b/ownCloudAppFramework/VFS/VFSManager.m @@ -0,0 +1,182 @@ +// +// VFSManager.m +// ownCloudApp +// +// Created by Felix Schwarz on 12.05.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import "VFSManager.h" +#import "OCBookmark+AppExtensions.h" + +@interface VFSManager () +{ + NSMapTable *_vfsCoreByBookmarkUUID; +} + +@end + +@implementation VFSManager + ++ (VFSManager *)sharedManager +{ + static dispatch_once_t onceToken; + static VFSManager *sharedManager; + + dispatch_once(&onceToken, ^{ + sharedManager = [[VFSManager alloc] init]; + }); + + return (sharedManager); +} + +- (instancetype)init +{ + if ((self = [super init]) != nil) + { + _vfsCoreByBookmarkUUID = [NSMapTable strongToWeakObjectsMapTable]; + + [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(handleDriveListChangeNotification:) name:OCVaultDriveListChanged object:nil]; + } + + return (self); +} + +- (void)dealloc +{ + [NSNotificationCenter.defaultCenter removeObserver:self name:OCVaultDriveListChanged object:nil]; +} + +- (OCVFSCore *)_vfsForBookmarkUUID:(OCBookmarkUUID)bookmarkUUID setup:(void(^)(OCVFSCore *vfsCore))setupVFS +{ + OCVFSCore *vfsCore; + + @synchronized (_vfsCoreByBookmarkUUID) + { + if ((vfsCore = [_vfsCoreByBookmarkUUID objectForKey:bookmarkUUID]) == nil) + { + // Set up new VFS Core + vfsCore = [[OCVFSCore alloc] init]; + vfsCore.delegate = self; + + // Save VFS core + [_vfsCoreByBookmarkUUID setObject:vfsCore forKey:bookmarkUUID]; + + // Setup VFS core + setupVFS(vfsCore); + } + } + + return (vfsCore); +} + +- (OCVFSCore *)vfsForBookmark:(OCBookmark *)bookmark +{ + return ([self _vfsForBookmarkUUID:bookmark.uuid setup:^(OCVFSCore *vfsCore) { + // Initially populate drive list + [self populateVFS:vfsCore forBookmark:bookmark]; + }]); +} + +- (OCVFSCore *)vfsForVault:(OCVault *)vault +{ + return ([self _vfsForBookmarkUUID:vault.bookmark.uuid setup:^(OCVFSCore *vfsCore) { + // Initially populate drive list + [self updateVFS:vfsCore fromVault:vault]; + }]); +} + +- (void)populateVFS:(OCVFSCore *)vfsCore forBookmark:(OCBookmark *)bookmark +{ + OCVault *vault; + + if ((vault = [[OCVault alloc] initWithBookmark:bookmark]) != nil) + { + [vault startDriveUpdates]; + [self updateVFS:vfsCore fromVault:vault]; + [vault stopDriveUpdates]; + } +} + +#pragma mark - Update/create VFS nodes +- (void)updateVFS:(OCVFSCore *)vfsCore fromVault:(OCVault *)vault +{ + if ([vault.bookmark hasCapability:OCBookmarkCapabilityDrives]) + { + if (vault.subscribedDrives.count > 0) + { + NSMutableArray *nodes = [NSMutableArray new]; + + OCVFSNode *vfsRootNode = [OCVFSNode virtualFolderAtPath:@"/" location:nil]; + + if (vault.bookmark.shortName.length > 0) + { + // Set shortname as name of the root folder (Files.app sometimes uses it as name in the navigation bar - and it would otherwise be "/") + vfsRootNode.name = vault.bookmark.shortName; + } + + [nodes addObject:vfsRootNode]; + + for (OCDrive *drive in vault.subscribedDrives) + { + OCLocation *driveRootLocation = drive.rootLocation; + driveRootLocation.bookmarkUUID = vault.bookmark.uuid; + + [nodes addObject:[OCVFSNode virtualFolderAtPath:[@"/" stringByAppendingPathComponent:drive.name].normalizedDirectoryPath location:driveRootLocation]]; + } + + [vfsCore setNodes:nodes]; + } + } + else + { + OCLocation *legacyRoot = [[OCLocation alloc] initWithBookmarkUUID:vault.bookmark.uuid driveID:nil path:@"/"]; + + [vfsCore setNodes:@[ + [OCVFSNode virtualFolderAtPath:@"/" location:legacyRoot] + ]]; + } +} + +#pragma mark - +- (void)handleDriveListChangeNotification:(NSNotification *)notification +{ + OCVault *originatingVault; + + if ((originatingVault = notification.object) != nil) + { + [self updateVFS:[self vfsForBookmark:originatingVault.bookmark] fromVault:originatingVault]; + } +} + +#pragma mark - OCVFSCoreDelegate +- (void)acquireCoreForBookmark:(OCBookmark *)bookmark completionHandler:(void(^)(NSError * _Nullable error, OCCore * _Nullable core))completionHandler +{ + [OCCoreManager.sharedCoreManager requestCoreForBookmark:bookmark setup:nil completionHandler:^(OCCore * _Nullable core, NSError * _Nullable error) { + completionHandler(error, core); + }]; +} + +- (void)relinquishCoreForBookmark:(OCBookmark *)bookmark completionHandler:(void(^)(NSError * _Nullable error))completionHandler +{ + // Delay return by 5 to avoid immediate shutdown and reopening of cores when folder changes don't overlap + NSTimeInterval returnDelayInterval = 5; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(returnDelayInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [OCCoreManager.sharedCoreManager returnCoreForBookmark:bookmark completionHandler:^{ + completionHandler(nil); + }]; + }); +} + +@end diff --git a/ownCloudAppFramework/ownCloudApp.h b/ownCloudAppFramework/ownCloudApp.h index da72ec07f..c4cec81f9 100644 --- a/ownCloudAppFramework/ownCloudApp.h +++ b/ownCloudAppFramework/ownCloudApp.h @@ -80,3 +80,5 @@ FOUNDATION_EXPORT const unsigned char ownCloudAppVersionString[]; #import #import #import + +#import From 633fb60582e04fa0716334823151f4e5908be348 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Tue, 24 May 2022 11:10:50 +0200 Subject: [PATCH 033/328] - File Provider: disallow deletion, renaming and moving of a space's root folder - update known issues --- KNOWN_ISSUES.md | 9 ++--- .../FileProviderExtension.m | 35 +++++++++++++------ .../OCVFSNode+FileProviderItem.m | 10 +++++- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md index 2594c2b84..c76e0dfc7 100644 --- a/KNOWN_ISSUES.md +++ b/KNOWN_ISSUES.md @@ -1,4 +1,4 @@ -# Known issues in version 12.0 alpha 1 +# Known issues in version 12.0 alpha 2 ## WARNING @@ -25,11 +25,8 @@ It should only be used with dedicated test servers, test data - and test devices - handling of detached drives with user data in them (see OCVault.detachedDrives) ## File Provider -- the list of spaces doesn't update dynamically -- the list of spaces may contain spaces of unsupported types -- not all actions are working correctly, especially in the root folder of spaces -- OCCores may not be managed correctly under all circumstances, causing undefined behaviour -- file uploads are broken +- file uploads are at least partially broken (error 500) +- dragging an entire space on top of another starts a full copy of the space, which eventually fails halfway through ## SDK - local storage consumed by spaces that are then deleted or inactivated is not reclaimed diff --git a/ownCloud File Provider/FileProviderExtension.m b/ownCloud File Provider/FileProviderExtension.m index 0f4be3b71..c78f3545f 100644 --- a/ownCloud File Provider/FileProviderExtension.m +++ b/ownCloud File Provider/FileProviderExtension.m @@ -210,7 +210,7 @@ - (NSFileProviderItem)itemForIdentifier:(NSFileProviderItemIdentifier)identifier return item; } -- (OCItem *)ocItemForIdentifier:(NSFileProviderItemIdentifier)identifier error:(NSError *__autoreleasing _Nullable *)outError +- (OCItem *)ocItemForIdentifier:(NSFileProviderItemIdentifier)identifier vfsNode:(OCVFSNode **)outNode error:(NSError *__autoreleasing _Nullable *)outError { id item; @@ -225,6 +225,11 @@ - (OCItem *)ocItemForIdentifier:(NSFileProviderItemIdentifier)identifier error:( { OCVFSNode *vfsNode = (OCVFSNode *)item; + if (outNode != NULL) + { + *outNode = vfsNode; + } + if (vfsNode.location != nil) { return (vfsNode.locationItem); @@ -489,7 +494,7 @@ - (void)createDirectoryWithName:(NSString *)directoryName inParentItemIdentifier FPLogCmdBegin(@"CreateDir", @"Start of createDirectoryWithName=%@, inParentItemIdentifier=%@", directoryName, parentItemIdentifier); - if ((parentItem = [self ocItemForIdentifier:parentItemIdentifier error:&error]) != nil) + if ((parentItem = [self ocItemForIdentifier:parentItemIdentifier vfsNode:NULL error:&error]) != nil) { // Detect collission with existing items FPLogCmd(@"Creating folder %@ inside %@", directoryName, parentItem.path); @@ -579,8 +584,8 @@ - (void)reparentItemWithIdentifier:(NSFileProviderItemIdentifier)itemIdentifier FPLogCmdBegin(@"Reparent", @"Start of reparentItemWithIdentifier=%@, toParentItemWithIdentifier=%@, newName=%@", itemIdentifier, parentItemIdentifier, newName); - if (((item = [self ocItemForIdentifier:itemIdentifier error:&error]) != nil) && - ((parentItem = [self ocItemForIdentifier:parentItemIdentifier error:&error]) != nil)) + if (((item = [self ocItemForIdentifier:itemIdentifier vfsNode:NULL error:&error]) != nil) && + ((parentItem = [self ocItemForIdentifier:parentItemIdentifier vfsNode:NULL error:&error]) != nil)) { FPLogCmd(@"Moving %@ to %@ as %@", item, parentItem, ((newName != nil) ? newName : item.name)); @@ -615,7 +620,7 @@ - (void)renameItemWithIdentifier:(NSFileProviderItemIdentifier)itemIdentifier to FPLogCmdBegin(@"Rename", @"Start of renameItemWithIdentifier=%@, toName=%@", itemIdentifier, itemName); - if ((item = [self ocItemForIdentifier:itemIdentifier error:&error]) != nil) + if ((item = [self ocItemForIdentifier:itemIdentifier vfsNode:NULL error:&error]) != nil) { FPLogCmd(@"Renaming %@ in %@ to %@", item, item.path.parentPath, itemName); @@ -635,10 +640,20 @@ - (void)deleteItemWithIdentifier:(NSFileProviderItemIdentifier)itemIdentifier co { NSError *error = nil; OCItem *item; + OCVFSNode *vfsNode = nil; FPLogCmdBegin(@"Delete", @"Start of deleteItemWithIdentifier=%@", itemIdentifier); - if ((item = [self ocItemForIdentifier:itemIdentifier error:&error]) != nil) + item = [self ocItemForIdentifier:itemIdentifier vfsNode:&vfsNode error:&error]; + +// if (vfsNode != nil) +// { +// FPLogCmd(@"Rejecting deletion of %@", vfsNode); +// completionHandler([NSError fileProviderErrorForRejectedDeletionOfItem:]); +// return; +// } + + if (item != nil) { FPLogCmd(@"Deleting %@", item); @@ -697,7 +712,7 @@ - (void)importDocumentAtURL:(NSURL *)fileURL toParentItemIdentifier:(NSFileProvi } } - if ((parentItem = [self ocItemForIdentifier:parentItemIdentifier error:&error]) != nil) + if ((parentItem = [self ocItemForIdentifier:parentItemIdentifier vfsNode:NULL error:&error]) != nil) { // Detect name collissions OCItem *existingItem; @@ -771,7 +786,7 @@ - (void)setFavoriteRank:(NSNumber *)favoriteRank forItemIdentifier:(NSFileProvid FPLogCmdBegin(@"FavoriteRank", @"Start of setFavoriteRank=%@, forItemIdentifier=%@", favoriteRank, itemIdentifier); - if ((item = [self ocItemForIdentifier:itemIdentifier error:&error]) != nil) + if ((item = [self ocItemForIdentifier:itemIdentifier vfsNode:NULL error:&error]) != nil) { // item.isFavorite = @(favoriteRank != nil); // Stored on server @@ -808,7 +823,7 @@ - (void)setTagData:(NSData *)tagData forItemIdentifier:(NSFileProviderItemIdenti FPLogCmdBegin(@"TagData", @"Start of setTagData=%@, forItemIdentifier=%@", tagData, itemIdentifier); - if ((item = [self ocItemForIdentifier:itemIdentifier error:&error]) != nil) + if ((item = [self ocItemForIdentifier:itemIdentifier vfsNode:NULL error:&error]) != nil) { [item setLocalTagData:tagData]; // Stored in local attributes @@ -844,7 +859,7 @@ - (void)trashItemWithIdentifier:(NSFileProviderItemIdentifier)itemIdentifier com FPLogCmdBegin(@"Trash", @"Start of trashItemWithIdentifier=%@", itemIdentifier); - if ((item = [self ocItemForIdentifier:itemIdentifier error:&error]) != nil) + if ((item = [self ocItemForIdentifier:itemIdentifier vfsNode:NULL error:&error]) != nil) { FPLogCmd(@"Deleting %@", item); diff --git a/ownCloud File Provider/OCVFSNode+FileProviderItem.m b/ownCloud File Provider/OCVFSNode+FileProviderItem.m index 95f9d21f1..2823205cb 100644 --- a/ownCloud File Provider/OCVFSNode+FileProviderItem.m +++ b/ownCloud File Provider/OCVFSNode+FileProviderItem.m @@ -65,7 +65,15 @@ - (NSFileProviderItemCapabilities)capabilities if ((locationItem = self.locationItem) != nil) { - return (locationItem.capabilities); + NSFileProviderItemCapabilities capabilities = locationItem.capabilities; + + if (locationItem.path.isRootPath) + { + // Disallow renaming, moving or deletion of root folders + capabilities &= ~(NSFileProviderItemCapabilitiesAllowsReparenting|NSFileProviderItemCapabilitiesAllowsRenaming|NSFileProviderItemCapabilitiesAllowsDeleting); + } + + return (capabilities); } } From d6bf5963592820ecd33a203eb3f4862f41381fcc Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Tue, 24 May 2022 11:13:02 +0200 Subject: [PATCH 034/328] - bump version to 219 --- ownCloud.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 6a2fcae91..545795259 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -4922,7 +4922,7 @@ APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; APP_SHORT_VERSION = 12.0; - APP_VERSION = 217; + APP_VERSION = 219; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -4992,7 +4992,7 @@ APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; APP_SHORT_VERSION = 12.0; - APP_VERSION = 217; + APP_VERSION = 219; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; From b9e93fc2b6374a8b0f916c86f9f6c08170c49623 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Fri, 27 May 2022 00:24:29 +0200 Subject: [PATCH 035/328] - FileProvider: - file uploads fixed (serverside) - Photo uploads: - refactor to add OCLocation / drive support - photo upload action now correctly uploads to the respective drive (not always to the personal folder) - bump build number to 220 --- KNOWN_ISSUES.md | 2 - ios-sdk | 2 +- .../FileProviderContentEnumerator.m | 57 ------------------- ownCloud.xcodeproj/project.pbxproj | 6 +- .../UploadMediaAction.swift | 4 +- .../Media Uploads/MediaUploadOperation.swift | 23 +++++--- ownCloud/Media Uploads/MediaUploadQueue.swift | 8 +-- .../Media Uploads/MediaUploadStorage.swift | 28 +++++---- .../InstantMediaUploadTaskExtension.swift | 12 ++-- .../ClientSpacesTableViewController.swift | 2 +- 10 files changed, 46 insertions(+), 98 deletions(-) diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md index c76e0dfc7..f9f3b80d0 100644 --- a/KNOWN_ISSUES.md +++ b/KNOWN_ISSUES.md @@ -18,14 +18,12 @@ It should only be used with dedicated test servers, test data - and test devices - subscription of spaces can't be turned on/off yet - the root of spaces-based accounts is not yet shown as hierarchic sidebar - support for sharing is widely untested and/or unavailable in the alpha -- photo/media uploads from the app are broken - inactived state of spaces is not yet represented in the UI - "Empty folder" not shown for empty folders - Copy & Paste allows copying a folder into a subfolder of its own / itself, leading to an infinite cycle - handling of detached drives with user data in them (see OCVault.detachedDrives) ## File Provider -- file uploads are at least partially broken (error 500) - dragging an entire space on top of another starts a full copy of the space, which eventually fails halfway through ## SDK diff --git a/ios-sdk b/ios-sdk index ed7980e30..b16847902 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit ed7980e307d9e598e8b5abfd96c5a91cab462462 +Subproject commit b16847902b6a034d2cd7520b9e2e890a23d6f65d diff --git a/ownCloud File Provider/FileProviderContentEnumerator.m b/ownCloud File Provider/FileProviderContentEnumerator.m index 2f494a6d1..7bc81146d 100644 --- a/ownCloud File Provider/FileProviderContentEnumerator.m +++ b/ownCloud File Provider/FileProviderContentEnumerator.m @@ -126,63 +126,6 @@ - (void)enumerateItemsForObserver:(id)observe return (YES); }]; -// -// [FileProviderContentEnumerator.queue async:^(dispatch_block_t _Nonnull completionHandler) { -// FileProviderContentEnumerator *strongSelf = weakSelf; -// -// if (strongSelf == nil) { -// completionHandler(); -// return; -// } -// -// [strongSelf.vfsCore provideContentForContainerItemID:strongSelf->_containerItemIdentifier changesFromSyncAnchor:nil completionHandler:^(NSError * _Nullable error, OCVFSContent * _Nullable content) { -// dispatch_async(FileProviderContentEnumerator.dispatchQueue, ^{ -// FileProviderContentEnumerator *strongSelf = weakSelf; -// -// if (strongSelf == nil) { -// completionHandler(); -// return; -// } -// -// if (error != nil) -// { -// [observer finishEnumeratingWithError:error]; -// completionHandler(); -// } -// else -// { -// if (content.isSnapshot) -// { -// // Content is a snapshot, so there's no need to keep the content around - it can be sent now -// [strongSelf provideItemsToEnumerationObserver:observer fromContent:content]; -// completionHandler(); -// } -// else -// { -// // Content is self-updating, so we can send it -// if (strongSelf.content != nil) -// { -// [strongSelf provideItemsToEnumerationObserver:observer fromContent:self.content]; -// completionHandler(); -// } -// else -// { -// FileProviderEnumeratorObserver *enumerationObserver = [FileProviderEnumeratorObserver new]; -// -// enumerationObserver.enumerationObserver = observer; -// enumerationObserver.enumerationStartPage = page; -// enumerationObserver.didProvideInitialItems = NO; -// enumerationObserver.enumerationCompletionHandler = completionHandler; -// -// [strongSelf->_enumerationObservers addObject:enumerationObserver]; -// -// strongSelf.content = content; -// } -// } -// } -// }); -// }]; -// }]; /* TODO: - inspect the page to determine whether this is an initial or a follow-up request diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 545795259..bc4b25817 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -408,7 +408,6 @@ DCC5E446232654DE002E5B84 /* NSObject+AnnotatedProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC5E445232654DE002E5B84 /* NSObject+AnnotatedProperties.m */; }; DCC5E4472326564F002E5B84 /* NSObject+AnnotatedProperties.h in Headers */ = {isa = PBXBuildFile; fileRef = DCC5E444232654DE002E5B84 /* NSObject+AnnotatedProperties.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCC6564A20C9B7E400110A97 /* FileProviderExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC6564920C9B7E400110A97 /* FileProviderExtension.m */; }; - DCC6565020C9B7E400110A97 /* FileProviderEnumerator.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC6564F20C9B7E400110A97 /* FileProviderEnumerator.m */; }; DCC6566520C9B7E400110A97 /* ownCloud File Provider.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = DCC6564620C9B7E300110A97 /* ownCloud File Provider.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; DCC832DE242C0C3700153F8C /* DisplaySleepPreventer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC832DD242C0C3700153F8C /* DisplaySleepPreventer.swift */; }; DCC832E2242C0EAC00153F8C /* MessageSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC832E1242C0EAC00153F8C /* MessageSelector.swift */; }; @@ -4573,7 +4572,6 @@ DC2218C62822C5B900808BCE /* OCVFSNode+FileProviderItem.m in Sources */, DC98BBD420FF824600F4ED3E /* FileProviderEnumeratorObserver.m in Sources */, DC2218CC2823329100808BCE /* FileProviderContentEnumerator.m in Sources */, - DCC6565020C9B7E400110A97 /* FileProviderEnumerator.m in Sources */, DC27A1E920CC56B0008ACB6C /* FileProviderExtensionThumbnailRequest.m in Sources */, DCF2DA7A24C82E480026D790 /* FileProviderServiceSource.m in Sources */, DC625141225C904700736874 /* NSError+MessageResolution.m in Sources */, @@ -4922,7 +4920,7 @@ APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; APP_SHORT_VERSION = 12.0; - APP_VERSION = 219; + APP_VERSION = 220; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -4992,7 +4990,7 @@ APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; APP_SHORT_VERSION = 12.0; - APP_VERSION = 219; + APP_VERSION = 220; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; diff --git a/ownCloud/Client/Actions/Actions+Extensions/UploadMediaAction.swift b/ownCloud/Client/Actions/Actions+Extensions/UploadMediaAction.swift index 33d4b6f93..5cf59b82e 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/UploadMediaAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/UploadMediaAction.swift @@ -165,10 +165,10 @@ class UploadMediaAction: UploadBaseAction { private func presentImageGalleryPicker() { func addAssetsToQueue(assets:[PHAsset]) { - guard let path = self.context.items.first?.path else { return } + guard let targetLocation = self.context.items.first?.location else { return } guard let bookmark = self.core?.bookmark else { return } - MediaUploadQueue.shared.addUploads(assets, for: bookmark, at: path) + MediaUploadQueue.shared.addUploads(assets, for: bookmark, at: targetLocation) } if let viewController = self.context.viewController { diff --git a/ownCloud/Media Uploads/MediaUploadOperation.swift b/ownCloud/Media Uploads/MediaUploadOperation.swift index 166794ffb..75b6a83c6 100644 --- a/ownCloud/Media Uploads/MediaUploadOperation.swift +++ b/ownCloud/Media Uploads/MediaUploadOperation.swift @@ -54,8 +54,8 @@ class MediaUploadOperation : Operation { // If item is found and it's not a placeholder, upload was finished if existingItem.isPlaceholder == false { // Now upload is done and the job can be removed completely - if let itemPath = existingItem.path { - removeUploadJob(with: itemPath) + if let itemLocation = existingItem.location { + removeUploadJob(with: itemLocation) } } // Otherwise if isPlaceholder property is true, then upload is still ongoing, just skip it here @@ -69,12 +69,12 @@ class MediaUploadOperation : Operation { } // Make sure that we have valid upload path - guard let path = mediaUploadJob.targetPath else { return } + guard let targetLocation = mediaUploadJob.targetLocation else { return } // Make sure that valid PHAsset is existing guard let asset = self.fetchAsset(with: assetId) else { // Otherwise remove the job - removeUploadJob(with: path) + removeUploadJob(with: targetLocation) return } @@ -89,7 +89,7 @@ class MediaUploadOperation : Operation { // Track the target path importGroup.enter() - self.itemTracking = core.trackItem(at: OCLocation.legacyRootPath(path), trackingHandler: { (_, item, isInitial) in + self.itemTracking = core.trackItem(at: targetLocation, trackingHandler: { (error, item, isInitial) in let importGroup = importGroupLeaveOnce importGroupLeaveOnce = nil @@ -97,6 +97,11 @@ class MediaUploadOperation : Operation { importGroup?.leave() } + if let error = error { + Log.error("Error resolving media import target location \(targetLocation): \(error)") + return + } + if isInitial { self.itemTracking = nil } @@ -117,11 +122,11 @@ class MediaUploadOperation : Operation { // Perform asset import if let itemLocalId = self.importAsset(asset: asset, with: core, at: item, uploadCompletion: { // Import successful - self.removeUploadJob(with: path) + self.removeUploadJob(with: targetLocation) }) { // Update media upload storage object core.bookmark.modifyMediaUploadStorage { (storage) in - storage.update(localItemID: itemLocalId, assetId: self.assetId, targetPath: path) + storage.update(localItemID: itemLocalId, assetId: self.assetId, targetLocation: targetLocation) return storage } } @@ -133,9 +138,9 @@ class MediaUploadOperation : Operation { // MARK: - Private helpers - private func removeUploadJob(with targetPath:String) { + private func removeUploadJob(with targetLocation: OCLocation) { core?.bookmark.modifyMediaUploadStorage { (storage) -> MediaUploadStorage in - storage.removeJob(with: self.assetId, targetPath: targetPath) + storage.removeJob(with: self.assetId, targetLocation: targetLocation) return storage } } diff --git a/ownCloud/Media Uploads/MediaUploadQueue.swift b/ownCloud/Media Uploads/MediaUploadQueue.swift index a7cd40a81..69b8ffa03 100644 --- a/ownCloud/Media Uploads/MediaUploadQueue.swift +++ b/ownCloud/Media Uploads/MediaUploadQueue.swift @@ -70,20 +70,20 @@ class MediaUploadQueue : OCActivitySource { importQueue.maxConcurrentOperationCount = maxConcurrentOperationCount } - func addUpload(_ asset:PHAsset, for bookmark:OCBookmark, at path:String) { + func addUpload(_ asset:PHAsset, for bookmark:OCBookmark, at targetLocation: OCLocation) { bookmark.modifyMediaUploadStorage { (storage) -> MediaUploadStorage in - storage.addJob(with: asset.localIdentifier, targetPath: path) + storage.addJob(with: asset.localIdentifier, targetLocation: targetLocation) return storage } self.setNeedsScheduling(in: bookmark) } - func addUploads(_ assets:[PHAsset], for bookmark:OCBookmark, at path:String) { + func addUploads(_ assets:[PHAsset], for bookmark:OCBookmark, at targetLocation: OCLocation) { bookmark.modifyMediaUploadStorage { (storage) -> MediaUploadStorage in for asset in assets { - storage.addJob(with: asset.localIdentifier, targetPath: path) + storage.addJob(with: asset.localIdentifier, targetLocation: targetLocation) } return storage } diff --git a/ownCloud/Media Uploads/MediaUploadStorage.swift b/ownCloud/Media Uploads/MediaUploadStorage.swift index e95e23a52..2a644fae8 100644 --- a/ownCloud/Media Uploads/MediaUploadStorage.swift +++ b/ownCloud/Media Uploads/MediaUploadStorage.swift @@ -23,7 +23,7 @@ import ownCloudAppShared class MediaUploadJob : NSObject, NSSecureCoding { - var targetPath: String? + var targetLocation : OCLocation? /// Target parent location of the upload var scheduledUploadLocalID: OCLocalID? static var supportsSecureCoding: Bool { @@ -31,17 +31,21 @@ class MediaUploadJob : NSObject, NSSecureCoding { } func encode(with coder: NSCoder) { - coder.encode(targetPath, forKey: "targetPath") + coder.encode(targetLocation, forKey: "targetLocation") coder.encode(scheduledUploadLocalID, forKey: "scheduledUploadLocalID") } required init?(coder: NSCoder) { - self.targetPath = coder.decodeObject(forKey: "targetPath") as? String + if let targetPath = coder.decodeObject(of: NSString.self, forKey: "targetPath") { + self.targetLocation = OCLocation.legacyRootPath(targetPath as String) + } else { + self.targetLocation = coder.decodeObject(of: OCLocation.self, forKey: "targetLocation") + } self.scheduledUploadLocalID = coder.decodeObject(forKey: "scheduledUploadLocalID") as? OCLocalID } - init(_ path:String) { - self.targetPath = path + init(_ targetLocation: OCLocation) { + self.targetLocation = targetLocation } } @@ -80,19 +84,19 @@ class MediaUploadStorage : NSObject, NSSecureCoding { jobs = [String : [MediaUploadJob]]() } - func addJob(with assetID:String, targetPath:String) { + func addJob(with assetID:String, targetLocation: OCLocation) { if !queue.contains(assetID) { self.queue.append(assetID) } var existingJobs: [MediaUploadJob] = jobs[assetID] != nil ? jobs[assetID]! : [MediaUploadJob]() - if existingJobs.filter({$0.targetPath == targetPath}).count == 0 { - existingJobs.append(MediaUploadJob(targetPath)) + if existingJobs.filter({ (existingJob) in existingJob.targetLocation == targetLocation}).count == 0 { + existingJobs.append(MediaUploadJob(targetLocation)) } jobs[assetID] = existingJobs } - func removeJob(with assetID:String, targetPath:String) { - if let remainingJobs = jobs[assetID]?.filter({$0.targetPath != targetPath}) { + func removeJob(with assetID:String, targetLocation: OCLocation) { + if let remainingJobs = jobs[assetID]?.filter({ (existingJob) in existingJob.targetLocation != targetLocation}) { jobs[assetID] = remainingJobs if remainingJobs.count == 0 { if let assetIdQueueIndex = queue.firstIndex(of: assetID) { @@ -102,8 +106,8 @@ class MediaUploadStorage : NSObject, NSSecureCoding { } } - func update(localItemID:OCLocalID, assetId:String, targetPath:String) { - jobs[assetId]?.filter({$0.targetPath == targetPath}).first?.scheduledUploadLocalID = localItemID + func update(localItemID:OCLocalID, assetId:String, targetLocation: OCLocation) { + jobs[assetId]?.filter({ (job) in job.targetLocation == targetLocation}).first?.scheduledUploadLocalID = localItemID } } diff --git a/ownCloud/Tasks/InstantMediaUploadTaskExtension.swift b/ownCloud/Tasks/InstantMediaUploadTaskExtension.swift index 6bc80c7d0..60f482aab 100644 --- a/ownCloud/Tasks/InstantMediaUploadTaskExtension.swift +++ b/ownCloud/Tasks/InstantMediaUploadTaskExtension.swift @@ -44,7 +44,7 @@ class InstantMediaUploadTaskExtension : ScheduledTaskAction { if userDefaults.instantUploadPhotos == true { if let bookmarkUUID = userDefaults.instantPhotoUploadBookmarkUUID, let path = userDefaults.instantPhotoUploadPath { if let bookmark = OCBookmarkManager.shared.bookmark(for: bookmarkUUID) { - enqueuedAssetCount += uploadPhotoAssets(for: bookmark, at: path) + enqueuedAssetCount += uploadPhotoAssets(for: bookmark, at: OCLocation.legacyRootPath(path)) } } else { Log.warning(tagged: ["INSTANT_MEDIA_UPLOAD"], "Instant photo upload enabled, but bookmark or path not configured") @@ -54,7 +54,7 @@ class InstantMediaUploadTaskExtension : ScheduledTaskAction { if userDefaults.instantUploadVideos == true { if let bookmarkUUID = userDefaults.instantVideoUploadBookmarkUUID, let path = userDefaults.instantVideoUploadPath { if let bookmark = OCBookmarkManager.shared.bookmark(for: bookmarkUUID) { - enqueuedAssetCount += uploadVideoAssets(for: bookmark, at: path) + enqueuedAssetCount += uploadVideoAssets(for: bookmark, at: OCLocation.legacyRootPath(path)) } } else { Log.warning(tagged: ["INSTANT_MEDIA_UPLOAD"], "Instant video upload enabled, but bookmark or path not configured") @@ -71,7 +71,7 @@ class InstantMediaUploadTaskExtension : ScheduledTaskAction { Log.debug(tagged: ["INSTANT_MEDIA_UPLOAD"], "Task finished") } - private func uploadPhotoAssets(for bookmark:OCBookmark, at path:String) -> Int { + private func uploadPhotoAssets(for bookmark:OCBookmark, at targetLocation: OCLocation) -> Int { guard let userDefaults = OCAppIdentity.shared.userDefaults else { return 0 } var photoAssets = [PHAsset]() @@ -91,7 +91,7 @@ class InstantMediaUploadTaskExtension : ScheduledTaskAction { Log.debug(tagged: ["INSTANT_MEDIA_UPLOAD"], "Importing \(photoAssets.count) photo assets") if photoAssets.count > 0 { - MediaUploadQueue.shared.addUploads(Array(photoAssets), for: bookmark, at: path) + MediaUploadQueue.shared.addUploads(Array(photoAssets), for: bookmark, at: targetLocation) userDefaults.instantUploadPhotosAfter = photoAssets.last?.creationDate Log.debug(tagged: ["INSTANT_MEDIA_UPLOAD"], "Last added photo asset modification date: \(String(describing: userDefaults.instantUploadPhotosAfter))") } @@ -99,7 +99,7 @@ class InstantMediaUploadTaskExtension : ScheduledTaskAction { return photoAssets.count } - private func uploadVideoAssets(for bookmark:OCBookmark, at path:String) -> Int { + private func uploadVideoAssets(for bookmark:OCBookmark, at targetLocation: OCLocation) -> Int { guard let userDefaults = OCAppIdentity.shared.userDefaults else { return 0 } var videoAssets = [PHAsset]() @@ -119,7 +119,7 @@ class InstantMediaUploadTaskExtension : ScheduledTaskAction { Log.debug(tagged: ["INSTANT_MEDIA_UPLOAD"], "Importing \(videoAssets.count) video assets") if videoAssets.count > 0 { - MediaUploadQueue.shared.addUploads(videoAssets, for: bookmark, at: path) + MediaUploadQueue.shared.addUploads(videoAssets, for: bookmark, at: targetLocation) userDefaults.instantUploadVideosAfter = videoAssets.last?.creationDate Log.debug(tagged: ["INSTANT_MEDIA_UPLOAD"], "Last added video asset modification date: \(String(describing: userDefaults.instantUploadPhotosAfter))") } diff --git a/ownCloudAppShared/Client/File Lists/ClientSpacesTableViewController.swift b/ownCloudAppShared/Client/File Lists/ClientSpacesTableViewController.swift index 727b64148..6ee89b47c 100644 --- a/ownCloudAppShared/Client/File Lists/ClientSpacesTableViewController.swift +++ b/ownCloudAppShared/Client/File Lists/ClientSpacesTableViewController.swift @@ -74,7 +74,7 @@ public class ClientSpacesTableViewController: StaticTableViewController { self?.navigationController?.pushViewController(rootFolderViewController, animated: true) } - }, title: drive.name ?? drive.identifier, subtitle: drive.type, accessoryType: .disclosureIndicator)) + }, title: drive.name ?? drive.identifier, subtitle: drive.type.rawValue, accessoryType: .disclosureIndicator)) } removeSection(driveRowsSection) From 47337b1a9436bab7dbf35c1dc2c0407947d34243 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Fri, 27 May 2022 18:31:46 +0200 Subject: [PATCH 036/328] - AlertView: add support for text alignment - BookmarkViewController: disable infinite PROPFIND for drive-based accounts (for now) - ClientItemViewController: add basic "Empty folder" message support - ThemeableCollectionViewListCell: implement themed base class based on UICollectionViewListCell - CollectionViewCellProvider+StandardImplementations: - use ThemeableCollectionViewListCell for .presentable items - make disclosure indicator dependant on child status for .presentable items - CollectionViewController: add support for theming the background - DriveListCell, DriveHeaderCell, ItemListCell: inherit from ThemeableCollectionViewListCell - ItemListCell: add themeing support - NSObject+ThemeApplication: - fix appearance issue for UITabBarAppearance in iOS 15 - add support for UICollectionViewListCell - KNOWN_ISSUES: update, amend with ideas for further evolution, beyond known issues --- KNOWN_ISSUES.md | 29 +++++++++- ownCloud.xcodeproj/project.pbxproj | 12 +++- .../xcschemes/ownCloud File Provider.xcscheme | 6 +- .../Bookmarks/BookmarkViewController.swift | 6 ++ ownCloud/Messages/AlertView.swift | 9 ++- .../Cells/DriveHeaderCell.swift | 14 ++++- .../Cells/DriveListCell.swift | 2 +- .../Collection Views/Cells/ItemListCell.swift | 19 +++--- .../Collection Views/Cells/MessageCell.swift | 25 ++++++++ .../ThemeableCollectionViewListCell.swift | 58 +++++++++++++++++++ .../CollectionViewCellConfiguration.swift | 1 + ...CellProvider+StandardImplementations.swift | 9 ++- .../CollectionViewController.swift | 27 ++++++++- .../ClientItemViewController.swift | 35 ++++++++++- .../Theme/NSObject+ThemeApplication.swift | 16 +++++ 15 files changed, 239 insertions(+), 29 deletions(-) create mode 100644 ownCloudAppShared/Client/Collection Views/Cells/MessageCell.swift create mode 100644 ownCloudAppShared/Client/Collection Views/Cells/ThemeableCollectionViewListCell.swift diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md index f9f3b80d0..5c6b6408f 100644 --- a/KNOWN_ISSUES.md +++ b/KNOWN_ISSUES.md @@ -12,14 +12,12 @@ It should only be used with dedicated test servers, test data - and test devices - search - sorting - a grid view - - full themeing support - breadcrumb title - spaces do not yet show a member count or provide access to a list of members - subscription of spaces can't be turned on/off yet - the root of spaces-based accounts is not yet shown as hierarchic sidebar - support for sharing is widely untested and/or unavailable in the alpha - inactived state of spaces is not yet represented in the UI -- "Empty folder" not shown for empty folders - Copy & Paste allows copying a folder into a subfolder of its own / itself, leading to an infinite cycle - handling of detached drives with user data in them (see OCVault.detachedDrives) @@ -28,3 +26,30 @@ It should only be used with dedicated test servers, test data - and test devices ## SDK - local storage consumed by spaces that are then deleted or inactivated is not reclaimed +- pre-population of accounts using infinite PROPFIND + +# Evolution roadmap +- make sync smarter, f.ex.: + - a file that is updated locally multiple times only should be uploaded once, not once for every update + - a file or folder that is scheduled for upload / creation - and then deleted, should not be uploaded then deleted + - a file scheduled for upload in a folder that is then deleted should not be uploaded then deleted + +- make sync more resilient + - more rigid dependency tracking -> stuck sync actions waiting for a request to return should no longer be possible as a result + - allow users to manually reschedule sync actions (=> maybe only after implementing cross-process progress reporting) + +- progress reporting sync across processes + - app -> FP + - FP -> app + - possibly use dedicated OC KVS + OCProgress for that + +- support for versions + +- photo uploads + - needs better error reporting / handling + - photos vanished from photos between upload request and when it is its turn + - report to user, drop silently, retry (how often/long?)? + - other errors + - report to user, drop silently, retry (how often/long?)? + +- more expressive "Empty folder" message display, based on new .message item type diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index bc4b25817..dc69b9812 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -198,6 +198,8 @@ DC0030C22350B1CE00BB8570 /* NSData+Encoding.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0030C02350B1CE00BB8570 /* NSData+Encoding.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC0030CB2350B75000BB8570 /* ScanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1AC7CF2319ADAE002B7892 /* ScanViewController.swift */; }; DC0196AB20F7690C00C41B78 /* OCBookmark+FileProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DC27A1A420CBEF85008ACB6C /* OCBookmark+FileProvider.m */; }; + DC01AF1C28411C0B00903101 /* MessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC01AF1B28411C0B00903101 /* MessageCell.swift */; }; + DC01AF2128411D8400903101 /* ThemeableCollectionViewListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC01AF2028411D8400903101 /* ThemeableCollectionViewListCell.swift */; }; DC01CDCC212EDDF600FC8E38 /* TextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC01CDCB212EDDF600FC8E38 /* TextViewController.swift */; }; DC049156258C00C400DEDC27 /* OCFileProviderServiceStandby.h in Headers */ = {isa = PBXBuildFile; fileRef = DC049154258C00C400DEDC27 /* OCFileProviderServiceStandby.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC049157258C00C400DEDC27 /* OCFileProviderServiceStandby.m in Sources */ = {isa = PBXBuildFile; fileRef = DC049155258C00C400DEDC27 /* OCFileProviderServiceStandby.m */; }; @@ -1224,6 +1226,8 @@ DC0030BF2350B1CE00BB8570 /* NSData+Encoding.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+Encoding.m"; sourceTree = ""; }; DC0030C02350B1CE00BB8570 /* NSData+Encoding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+Encoding.h"; sourceTree = ""; }; DC018F8B20A1060A00135198 /* ProgressHUDViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressHUDViewController.swift; sourceTree = ""; }; + DC01AF1B28411C0B00903101 /* MessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCell.swift; sourceTree = ""; }; + DC01AF2028411D8400903101 /* ThemeableCollectionViewListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeableCollectionViewListCell.swift; sourceTree = ""; }; DC01CDCB212EDDF600FC8E38 /* TextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewController.swift; sourceTree = ""; }; DC049154258C00C400DEDC27 /* OCFileProviderServiceStandby.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCFileProviderServiceStandby.h; sourceTree = ""; }; DC049155258C00C400DEDC27 /* OCFileProviderServiceStandby.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCFileProviderServiceStandby.m; sourceTree = ""; }; @@ -3106,10 +3110,12 @@ DCEAF0882808252300980B6D /* Cells */ = { isa = PBXGroup; children = ( - DCEAF0892808254800980B6D /* DriveListCell.swift */, + DC01AF2028411D8400903101 /* ThemeableCollectionViewListCell.swift */, DC3AB2412810404000789435 /* DriveHeaderCell.swift */, - DC3AB23D280FFE3400789435 /* ItemListCell.swift */, + DCEAF0892808254800980B6D /* DriveListCell.swift */, DC3AB2472810A10300789435 /* ExpandableResourceCell.swift */, + DC3AB23D280FFE3400789435 /* ItemListCell.swift */, + DC01AF1B28411C0B00903101 /* MessageCell.swift */, ); path = Cells; sourceTree = ""; @@ -4355,6 +4361,7 @@ DCDC0AD123CD18D200DFE36D /* OCLicenseManager+Setup.swift in Sources */, DCE4E44724C1AC4F0051722F /* MessageView.swift in Sources */, DCE4E48824C1FA430051722F /* NamingViewController.swift in Sources */, + DC01AF2128411D8400903101 /* ThemeableCollectionViewListCell.swift in Sources */, 399725E1233DF39300FC3B94 /* Calendar+Extension.swift in Sources */, DCE4E44124C1A07E0051722F /* UITableViewController+Extension.swift in Sources */, DC0A358E24C0E44B00FB58FC /* ThemeableColoredView.swift in Sources */, @@ -4386,6 +4393,7 @@ DCE4E45124C1E4430051722F /* UIBarButtonItem+Extension.swift in Sources */, DCA2EDE2279B16F1001F04E6 /* ResourceSourceItemIcons.swift in Sources */, DCEAF08A2808254800980B6D /* DriveListCell.swift in Sources */, + DC01AF1C28411C0B00903101 /* MessageCell.swift in Sources */, DC0A357724C0E43200FB58FC /* ProgressSummarizer.swift in Sources */, 392CFEB72705831700631D2B /* LAContext+Extension.swift in Sources */, DCE4E48724C1F9F50051722F /* CreateFolderAction.swift in Sources */, diff --git a/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud File Provider.xcscheme b/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud File Provider.xcscheme index 9cb3af7b1..4672c5768 100644 --- a/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud File Provider.xcscheme +++ b/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud File Provider.xcscheme @@ -107,9 +107,9 @@ allowLocationSimulation = "YES" launchAutomaticallySubstyle = "2"> + runnableDebuggingMode = "1" + BundleIdentifier = "com.apple.DocumentsApp" + RemotePath = "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/Applications/Files.app"> . + * + */ + import UIKit -class DriveHeaderCell: DriveListCell, Themeable { +class DriveHeaderCell: DriveListCell { let darkBackgroundView = UIView() weak var collectionViewController : CollectionViewController? @@ -40,7 +50,7 @@ class DriveHeaderCell: DriveListCell, Themeable { Theme.shared.register(client: self, applyImmediately: true) } - func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { + override func applyThemeCollectionToCellContents(theme: Theme, collection: ThemeCollection, state: ThemeItemState) { coverImageResourceView.backgroundColor = collection.lightBrandColor titleLabel.textColor = .white diff --git a/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift index 512e9028c..5e71d9fc6 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift @@ -19,7 +19,7 @@ import UIKit import ownCloudSDK -class DriveListCell: UICollectionViewListCell { +class DriveListCell: ThemeableCollectionViewListCell { let coverImageResourceView = ResourceViewHost() let spaceFallbackImageView = UIImageView() diff --git a/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift index 5d65eda3e..984f97d22 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift @@ -20,7 +20,7 @@ import UIKit import ownCloudSDK import ownCloudApp -open class ItemListCell: UICollectionViewListCell { +open class ItemListCell: ThemeableCollectionViewListCell { private let horizontalMargin : CGFloat = 15 private let verticalLabelMargin : CGFloat = 10 private let verticalIconMargin : CGFloat = 10 @@ -507,17 +507,13 @@ open class ItemListCell: UICollectionViewListCell { Log.debug("Highlighted!") } - //!! applyThemeCollectionToCellContents(theme: Theme.shared, collection: Theme.shared.activeCollection) + applyThemeCollectionToCellContents(theme: Theme.shared, collection: Theme.shared.activeCollection, state: ThemeItemState(selected: isSelected)) } } - //!! - /* - override open func applyThemeCollectionToCellContents(theme: Theme, collection: ThemeCollection) { - let itemState = ThemeItemState(selected: self.isSelected) - - titleLabel.applyThemeCollection(collection, itemStyle: .title, itemState: itemState) - detailLabel.applyThemeCollection(collection, itemStyle: .message, itemState: itemState) + open override func applyThemeCollectionToCellContents(theme: Theme, collection: ThemeCollection, state: ThemeItemState) { + titleLabel.applyThemeCollection(collection, itemStyle: .title, itemState: state) + detailLabel.applyThemeCollection(collection, itemStyle: .message, itemState: state) sharedStatusIconView.tintColor = collection.tableRowColors.secondaryLabelColor publicLinkStatusIconView.tintColor = collection.tableRowColors.secondaryLabelColor @@ -527,12 +523,11 @@ open class ItemListCell: UICollectionViewListCell { moreButton.tintColor = collection.tableRowColors.secondaryLabelColor if revealHighlight { - backgroundColor = collection.tableRowHighlightColors.backgroundColor?.withAlphaComponent(0.5) + self.backgroundView?.backgroundColor = collection.tableRowHighlightColors.backgroundColor?.withAlphaComponent(0.5) } else { - backgroundColor = collection.tableBackgroundColor + self.backgroundView?.backgroundColor = collection.tableBackgroundColor } } - */ // MARK: - Editing mode open func setMoreButton(hidden:Bool, animated: Bool = false) { diff --git a/ownCloudAppShared/Client/Collection Views/Cells/MessageCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/MessageCell.swift new file mode 100644 index 000000000..35471ef25 --- /dev/null +++ b/ownCloudAppShared/Client/Collection Views/Cells/MessageCell.swift @@ -0,0 +1,25 @@ +// +// MessageCell.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 27.05.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK +import ownCloudApp + +class MessageCell : ThemeableCollectionViewListCell { + +} diff --git a/ownCloudAppShared/Client/Collection Views/Cells/ThemeableCollectionViewListCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/ThemeableCollectionViewListCell.swift new file mode 100644 index 000000000..96a97b2e1 --- /dev/null +++ b/ownCloudAppShared/Client/Collection Views/Cells/ThemeableCollectionViewListCell.swift @@ -0,0 +1,58 @@ +// +// ThemeableCollectionViewListCell.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 27.05.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudApp + +open class ThemeableCollectionViewListCell: UICollectionViewListCell, Themeable { + private var themeRegistered : Bool = false + public var updateLabelColors : Bool = true + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + deinit { + if themeRegistered { + Theme.shared.unregister(client: self) + } + } + + open override func willMove(toSuperview newSuperview: UIView?) { + super.willMove(toSuperview: newSuperview) + + if !themeRegistered { + // Postpone registration with theme until we actually need to. Makes sure self.applyThemeCollection() can take all properties into account + Theme.shared.register(client: self, applyImmediately: true) + themeRegistered = true + } + } + + open func applyThemeCollectionToCellContents(theme: Theme, collection: ThemeCollection, state: ThemeItemState) { + } + + open func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { + self.applyThemeCollection(Theme.shared.activeCollection) + + self.applyThemeCollectionToCellContents(theme: theme, collection: collection, state: ThemeItemState(selected: self.isSelected)) + } +} diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift index 5b8160e5b..f02c5cc6b 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift @@ -24,6 +24,7 @@ public enum CollectionViewCellStyle { case footer case tableCell case gridCell + case fillSpace } public class CollectionViewCellConfiguration: NSObject { diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewCellProvider+StandardImplementations.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewCellProvider+StandardImplementations.swift index 5f8cfa0f4..8f506216e 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewCellProvider+StandardImplementations.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewCellProvider+StandardImplementations.swift @@ -30,8 +30,9 @@ public extension CollectionViewCellProvider { } static func registerPresentableCellProvider() { - let presentableCellRegistration = UICollectionView.CellRegistration { (cell, indexPath, collectionItemRef) in + let presentableCellRegistration = UICollectionView.CellRegistration { (cell, indexPath, collectionItemRef) in var content = cell.defaultContentConfiguration() + var hasDisclosureIndicator : Bool = false if let cellConfiguration = collectionItemRef.ocCellConfiguration { var itemRecord = cellConfiguration.record @@ -78,6 +79,10 @@ public extension CollectionViewCellProvider { if let readmeRequest = readmeRequest { cellConfiguration.core?.vault.resourceManager?.start(readmeRequest) } + + if let datasource = cellConfiguration.source { + hasDisclosureIndicator = presentable.hasChildren(using: datasource) + } } } else { // Request reconfiguration of cell @@ -91,7 +96,7 @@ public extension CollectionViewCellProvider { } cell.contentConfiguration = content - cell.accessories = [ .disclosureIndicator() ] + cell.accessories = hasDisclosureIndicator ? [ .disclosureIndicator() ] : [ ] } CollectionViewCellProvider.register(CollectionViewCellProvider(for: .presentable, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift index 0dfba35e7..97a252454 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift @@ -20,8 +20,7 @@ import UIKit import ownCloudApp import ownCloudSDK -public class CollectionViewController: UIViewController, UICollectionViewDelegate { - +public class CollectionViewController: UIViewController, UICollectionViewDelegate, Themeable { public var clientContext: ClientContext? public var supportsHierarchicContent: Bool @@ -50,6 +49,10 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat fatalError("init(coder:) has not been implemented") } + deinit { + Theme.shared.unregister(client: self) + } + // MARK: - Collection View var collectionView : UICollectionView! = nil var collectionViewDataSource: UICollectionViewDiffableDataSource! = nil @@ -60,10 +63,21 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat super.viewDidLoad() configureViews() configureDataSource() + + Theme.shared.register(client: self, applyImmediately: true) } public func createCollectionViewLayout() -> UICollectionViewLayout { - let config = UICollectionLayoutListConfiguration(appearance: listAppearance) + var config = UICollectionLayoutListConfiguration(appearance: listAppearance) + switch listAppearance { + case .plain: + config.backgroundColor = Theme.shared.activeCollection.tableBackgroundColor + + case .grouped, .insetGrouped: + config.backgroundColor = Theme.shared.activeCollection.tableGroupBackgroundColor + + default: break + } return UICollectionViewCompositionalLayout.list(using: config) } @@ -262,6 +276,13 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat @discardableResult public func provideContextMenuConfiguration(for record: OCDataItemRecord, at indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { return nil } + + // MARK: - Themeing + public func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { + if event != .initial { + collectionView.setCollectionViewLayout(createCollectionViewLayout(), animated: false) + } + } } public extension CollectionViewController { diff --git a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift index e292033e7..a44727d74 100644 --- a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift @@ -32,6 +32,10 @@ public class ClientItemViewController: CollectionViewController { private var singleDriveDatasourceSubscription : OCDataSourceSubscription? public var driveAdditionalItemsDataSource : OCDataSourceArray = OCDataSourceArray() + public var emptyItemListDataSource : OCDataSourceArray = OCDataSourceArray() + public var emptyItemListDecisionSubscription : OCDataSourceSubscription? + public var emptyItemListItem : OCDataItemPresentable? + public init(context inContext: ClientContext?, query inQuery: OCQuery, reveal inItem: OCItem? = nil) { query = inQuery @@ -84,13 +88,25 @@ public class ClientItemViewController: CollectionViewController { } } + sections.append(CollectionViewSection(identifier: "empty", dataSource: emptyItemListDataSource, cellStyle: .fillSpace, clientContext: itemControllerContext)) + super.init(context: itemControllerContext, sections: sections, listAppearance: .plain) // Subscribe to singleDriveDatasource for changes, to update driveSectionDataSource - singleDriveDatasourceSubscription = singleDriveDatasource?.subscribe(updateHandler: { [weak self] subscription in + singleDriveDatasourceSubscription = singleDriveDatasource?.subscribe(updateHandler: { [weak self] (subscription) in self?.updateAdditionalDriveItems(from: subscription) }, on: .main, trackDifferences: true, performIntialUpdate: true) + if let queryDatasource = query?.queryResultsDataSource { + emptyItemListItem = OCDataItemPresentable(reference: "_emptyItemList" as NSString, originalDataItemType: nil, version: nil) + emptyItemListItem?.title = "Empty folder".localized + emptyItemListItem?.childrenDataSourceProvider = nil + + emptyItemListDecisionSubscription = queryDatasource.subscribe(updateHandler: { [weak self] (subscription) in + self?.updateEmptyItemList(from: subscription) + }, on: .main, trackDifferences: false, performIntialUpdate: true) + } + query?.sortComparator = SortMethod.alphabetically.comparator(direction: .ascendant) if let navigationTitle = query?.queryLocation?.isRoot == true ? clientContext?.drive?.name : query?.queryLocation?.lastPathComponent { @@ -223,6 +239,23 @@ public class ClientItemViewController: CollectionViewController { var _actionProgressHandler : ActionProgressHandler? + // MARK: - Empty item list handling + func updateEmptyItemList(from subscription: OCDataSourceSubscription) { + hasNoItems = subscription.snapshotResettingChangeTracking(true).numberOfItems == 0 + } + + public var hasNoItems : Bool = false { + didSet { + if hasNoItems != oldValue { + if hasNoItems, let emptyItemListItem = emptyItemListItem { + emptyItemListDataSource.setItems([emptyItemListItem], updated: nil) + } else { + emptyItemListDataSource.setItems(nil, updated: nil) + } + } + } + } + // MARK: - Navigation Bar Actions @objc open func moreBarButtonPressed(_ sender: UIBarButtonItem) { guard let rootItem = query?.rootItem else { diff --git a/ownCloudAppShared/User Interface/Theme/NSObject+ThemeApplication.swift b/ownCloudAppShared/User Interface/Theme/NSObject+ThemeApplication.swift index f8db5a38f..2bec230fc 100644 --- a/ownCloudAppShared/User Interface/Theme/NSObject+ThemeApplication.swift +++ b/ownCloudAppShared/User Interface/Theme/NSObject+ThemeApplication.swift @@ -132,6 +132,12 @@ public extension NSObject { tabBar.barTintColor = collection.toolbarColors.backgroundColor tabBar.tintColor = collection.toolbarColors.tintColor tabBar.unselectedItemTintColor = collection.toolbarColors.secondaryLabelColor + + let appearance = UITabBarAppearance() + appearance.backgroundColor = collection.toolbarColors.backgroundColor + + tabBar.standardAppearance = appearance + tabBar.scrollEdgeAppearance = appearance } if let tableView = self as? UITableView { @@ -247,6 +253,16 @@ public extension NSObject { cell.overrideUserInterfaceStyle = collection.interfaceStyle.userInterfaceStyle } + if let cell = self as? UICollectionViewListCell { + var backgroundConfig = cell.backgroundConfiguration + backgroundConfig?.backgroundColor = collection.tableRowColors.backgroundColor + cell.backgroundConfiguration = backgroundConfig + + cell.tintColor = collection.lightBrandColor + + cell.overrideUserInterfaceStyle = collection.interfaceStyle.userInterfaceStyle + } + if let progressView = self as? UIProgressView { progressView.tintColor = collection.tintColor progressView.trackTintColor = collection.tableSeparatorColor From de3af5e0ec1efc8251e9d71a00d90feee75c1d53 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Fri, 27 May 2022 18:33:25 +0200 Subject: [PATCH 037/328] - bump app version to 221 --- ownCloud.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index dc69b9812..b8537f00f 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -4928,7 +4928,7 @@ APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; APP_SHORT_VERSION = 12.0; - APP_VERSION = 220; + APP_VERSION = 221; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -4998,7 +4998,7 @@ APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; APP_SHORT_VERSION = 12.0; - APP_VERSION = 220; + APP_VERSION = 221; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; From 034398043ddc23379ca9dbf8214f651b0f22697e Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Mon, 30 May 2022 22:40:32 +0200 Subject: [PATCH 038/328] - add OCDataItem+InteractionProtocols allow OCDataItems to provide handling for: - selection / opening - swipe actions - context menu items - OCDrive: implement DataItemSelectionInteraction - OCItem: implement DataItemSelectionInteraction, DataItemSwipeInteraction, DataItemContextMenuInteraction, refactored from ClientRootViewController, CollectionViewProvider and ClientItemViewController - Action: add new .emptyFolder location - ClientItemViewController: - implement empty folder handling, showing actions matching to the .emptyFolder location - add search related code (WIP) - ClientContext: - factor out ActionProgressHandler generation into ActionProgressHandlerProvider protocol - factor out Item Viewing into ViewItemAction protocol - change OpenItemAction, MoreItemAction, RevealItemAction, ContextMenuProvider to accept OCDataItem instead of OCItem - add new SwipeActionsProvider to provide UISwipeActionsConfiguration for trailing and leading swipes - add new ClientItemInteraction enum to describe all common item interactions - add .permissions and .permissionHandler to drive .validate(permission:for:) to validate if an item interaction is allowed - CollectionView: - add section-based layout support, simplified via CollectionViewSection.CellLayout enum with layout generator --- KNOWN_ISSUES.md | 6 +- ios-sdk | 2 +- ownCloud.xcodeproj/project.pbxproj | 30 +++ .../Actions+Extensions/UploadFileAction.swift | 4 +- .../UploadMediaAction.swift | 4 +- .../Client/Actions/Scanner/ScanAction.swift | 4 +- ...ClientRootViewController+ItemActions.swift | 85 +------- .../Client/ClientRootViewController.swift | 8 +- .../Viewer/DisplayHostViewController.swift | 6 +- ownCloudAppShared/Client/Actions/Action.swift | 17 ++ .../Client/Actions/CreateFolderAction.swift | 4 +- .../Collection Views/Cells/ActionCell.swift | 72 ++++++ ...CellProvider+StandardImplementations.swift | 10 +- .../CollectionViewController.swift | 96 +++++--- .../CollectionViewSection.swift | 139 +++++++++++- .../ClientItemViewController.swift | 206 +++++++++++++----- .../Client/Context/ClientContext.swift | 61 +++++- .../OCDataItem+InteractionProtocols.swift | 40 ++++ .../OCDrive+Interactions.swift | 42 ++++ .../OCItem+Interactions.swift | 125 +++++++++++ 20 files changed, 772 insertions(+), 189 deletions(-) create mode 100644 ownCloudAppShared/Client/Collection Views/Cells/ActionCell.swift create mode 100644 ownCloudAppShared/Client/Data Item Interactions/OCDataItem+InteractionProtocols.swift create mode 100644 ownCloudAppShared/Client/Data Item Interactions/OCDrive+Interactions.swift create mode 100644 ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md index 5c6b6408f..ee5c0f7f4 100644 --- a/KNOWN_ISSUES.md +++ b/KNOWN_ISSUES.md @@ -7,7 +7,6 @@ It should only be used with dedicated test servers, test data - and test devices ## App - in the new browsing experience, some features are not yet available: - - row actions - drag and drop - search - sorting @@ -26,7 +25,7 @@ It should only be used with dedicated test servers, test data - and test devices ## SDK - local storage consumed by spaces that are then deleted or inactivated is not reclaimed -- pre-population of accounts using infinite PROPFIND +- pre-population of accounts using infinite PROPFIND is not supported # Evolution roadmap - make sync smarter, f.ex.: @@ -43,6 +42,9 @@ It should only be used with dedicated test servers, test data - and test devices - FP -> app - possibly use dedicated OC KVS + OCProgress for that +- empty folders + - replace simple "Empty folder" message with message + direct access to actions like "Create folder", "Scan document", "Upload photo", "Take photo", … using action extensions as source + - support for versions - photo uploads diff --git a/ios-sdk b/ios-sdk index b16847902..d84ed5290 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit b16847902b6a034d2cd7520b9e2e890a23d6f65d +Subproject commit d84ed529045db10e9b5f73f60ea88bea47305f2f diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index b8537f00f..01d39d76d 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -323,6 +323,11 @@ DC4332002472E1B4002DC0E5 /* OCLicenseEMMProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = DC4331FE2472E1B4002DC0E5 /* OCLicenseEMMProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC4332012472E1B4002DC0E5 /* OCLicenseEMMProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DC4331FF2472E1B4002DC0E5 /* OCLicenseEMMProvider.m */; }; DC44343E21ABFA5200376B16 /* StaticLoginProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC44343D21ABFA5200376B16 /* StaticLoginProfile.swift */; }; + DC46F3C72844A75200038880 /* OCDataItem+InteractionProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC46F3C62844A75200038880 /* OCDataItem+InteractionProtocols.swift */; }; + DC46F3CC2844A8EA00038880 /* SortBarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC46F3CB2844A8EA00038880 /* SortBarCell.swift */; }; + DC46F3CE2844A92A00038880 /* ActionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC46F3CD2844A92A00038880 /* ActionCell.swift */; }; + DC46F3D1284546F000038880 /* OCItem+Interactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC46F3D0284546F000038880 /* OCItem+Interactions.swift */; }; + DC46F3D32845583D00038880 /* OCDrive+Interactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC46F3D22845583D00038880 /* OCDrive+Interactions.swift */; }; DC49B55928365C5F00DAF13B /* OCVault+VFSManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DC49B55728365C5F00DAF13B /* OCVault+VFSManager.h */; }; DC49B55A28365C5F00DAF13B /* OCVault+VFSManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DC49B55828365C5F00DAF13B /* OCVault+VFSManager.m */; }; DC4C575D233958B70098BAE9 /* FixedHeightImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4C575C233958B70098BAE9 /* FixedHeightImageView.swift */; }; @@ -1310,6 +1315,11 @@ DC44344321AC031600376B16 /* StaticLoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLoginViewController.swift; sourceTree = ""; }; DC4434C321AC894700376B16 /* StaticLoginStepViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLoginStepViewController.swift; sourceTree = ""; }; DC4434C521AC898700376B16 /* StaticLoginSetupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLoginSetupViewController.swift; sourceTree = ""; }; + DC46F3C62844A75200038880 /* OCDataItem+InteractionProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCDataItem+InteractionProtocols.swift"; sourceTree = ""; }; + DC46F3CB2844A8EA00038880 /* SortBarCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortBarCell.swift; sourceTree = ""; }; + DC46F3CD2844A92A00038880 /* ActionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionCell.swift; sourceTree = ""; }; + DC46F3D0284546F000038880 /* OCItem+Interactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCItem+Interactions.swift"; sourceTree = ""; }; + DC46F3D22845583D00038880 /* OCDrive+Interactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCDrive+Interactions.swift"; sourceTree = ""; }; DC49B55728365C5F00DAF13B /* OCVault+VFSManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCVault+VFSManager.h"; sourceTree = ""; }; DC49B55828365C5F00DAF13B /* OCVault+VFSManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCVault+VFSManager.m"; sourceTree = ""; }; DC4C575C233958B70098BAE9 /* FixedHeightImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixedHeightImageView.swift; sourceTree = ""; }; @@ -1860,6 +1870,7 @@ 3912208023436E9B0026C290 /* Client */ = { isa = PBXGroup; children = ( + DC46F3CF284546DA00038880 /* Data Item Interactions */, DC82664028168DAA00F91F7D /* Context */, DCEAF0842808250E00980B6D /* Collection Views */, DCA2EDDB279B0E5D001F04E6 /* Resource Sources */, @@ -2484,6 +2495,16 @@ path = Interface; sourceTree = ""; }; + DC46F3CF284546DA00038880 /* Data Item Interactions */ = { + isa = PBXGroup; + children = ( + DC46F3C62844A75200038880 /* OCDataItem+InteractionProtocols.swift */, + DC46F3D0284546F000038880 /* OCItem+Interactions.swift */, + DC46F3D22845583D00038880 /* OCDrive+Interactions.swift */, + ); + path = "Data Item Interactions"; + sourceTree = ""; + }; DC66F3A723965BE300CF4812 /* Parser Support */ = { isa = PBXGroup; children = ( @@ -3116,6 +3137,8 @@ DC3AB2472810A10300789435 /* ExpandableResourceCell.swift */, DC3AB23D280FFE3400789435 /* ItemListCell.swift */, DC01AF1B28411C0B00903101 /* MessageCell.swift */, + DC46F3CB2844A8EA00038880 /* SortBarCell.swift */, + DC46F3CD2844A92A00038880 /* ActionCell.swift */, ); path = Cells; sourceTree = ""; @@ -4348,6 +4371,7 @@ DC3AB2462810602500789435 /* UILabel+Extension.swift in Sources */, DC0A356C24C0E42200FB58FC /* AppLockWindow.swift in Sources */, DCFC9ED128002335005D9144 /* CollectionViewCellProvider.swift in Sources */, + DC46F3D32845583D00038880 /* OCDrive+Interactions.swift in Sources */, DCE4E43B24C19B4F0051722F /* NSLayoutConstraint+Extension.swift in Sources */, DCE2F03E27FADF2600E9E136 /* UICollectionViewDiffableDataSource+Tools.swift in Sources */, DC0A355724C0E35B00FB58FC /* UIDevice+UIUserInterfaceIdiom.swift in Sources */, @@ -4371,6 +4395,7 @@ DCA35DA724D309B600DBE2B0 /* OCFileProviderServiceSession+UploadByFileProvider.swift in Sources */, 39BE385D23435AFE0062A2FE /* String+Extension.swift in Sources */, DC0A357A24C0E43700FB58FC /* CardViewController.swift in Sources */, + DC46F3D1284546F000038880 /* OCItem+Interactions.swift in Sources */, DC0A356B24C0E42200FB58FC /* AppLockManager.swift in Sources */, DCE4E4C724C255E00051722F /* AppExtensionNavigationController.swift in Sources */, DC9A116B27D0338400D90BA4 /* ClientSpacesTableViewController.swift in Sources */, @@ -4423,6 +4448,7 @@ DCE4E45424C1EC040051722F /* BreadCrumbTableViewController.swift in Sources */, 399EA73A25E656A900B6FF11 /* UITableView+Extension.swift in Sources */, 399EA6F725E6544100B6FF11 /* PublicLinkEditTableViewController.swift in Sources */, + DC46F3C72844A75200038880 /* OCDataItem+InteractionProtocols.swift in Sources */, DC0A358824C0E44B00FB58FC /* ThemeTableViewCell.swift in Sources */, DC36886224DDA9AB00333600 /* ProgressIndicatorViewController.swift in Sources */, 399EA72625E6565900B6FF11 /* OCCore+Extension.swift in Sources */, @@ -4445,11 +4471,13 @@ DC0A358424C0E44200FB58FC /* VectorImageView.swift in Sources */, DC0A355324C0E2C200FB58FC /* ClientItemCell.swift in Sources */, DC0A357D24C0E43C00FB58FC /* ThemeStyle.swift in Sources */, + DC46F3CC2844A8EA00038880 /* SortBarCell.swift in Sources */, DC0A357524C0E43200FB58FC /* ProgressView.swift in Sources */, 3912208223436EB80026C290 /* SortMethod.swift in Sources */, DCE4E43F24C19D370051722F /* UIAlertController+OCIssue.swift in Sources */, DC0A359524C0E5F900FB58FC /* UIImage+Extension.swift in Sources */, DC66A9F8279F467200792AC8 /* UIKeyCommand+Extension.swift in Sources */, + DC46F3CE2844A92A00038880 /* ActionCell.swift in Sources */, DCE4E43724C19A910051722F /* LicenseRequirements.swift in Sources */, DCE4E43A24C19ADC0051722F /* MoreViewHeader.swift in Sources */, 399EA74625E6575B00B6FF11 /* NotificationHUDViewController.swift in Sources */, @@ -5062,6 +5090,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + APP_VERSION = 222; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = ownCloud/ownCloud.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; @@ -5092,6 +5121,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + APP_VERSION = 222; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = ownCloud/ownCloud.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; diff --git a/ownCloud/Client/Actions/Actions+Extensions/UploadFileAction.swift b/ownCloud/Client/Actions/Actions+Extensions/UploadFileAction.swift index 9ec1b1cfc..2d0530dda 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/UploadFileAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/UploadFileAction.swift @@ -26,7 +26,7 @@ class UploadFileAction: UploadBaseAction { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.uploadfile") } override class var category : ActionCategory? { return .normal } override class var name : String { return "Upload file".localized } - override class var locations : [OCExtensionLocationIdentifier]? { return [.folderAction, .keyboardShortcut] } + override class var locations : [OCExtensionLocationIdentifier]? { return [.folderAction, .keyboardShortcut, .emptyFolder] } override class var keyCommand : String? { return "+" } override class var keyModifierFlags: UIKeyModifierFlags? { return [.command] } @@ -53,7 +53,7 @@ class UploadFileAction: UploadBaseAction { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .folderAction { + if location == .folderAction || location == .emptyFolder { Theme.shared.add(tvgResourceFor: "text") return Theme.shared.image(for: "text", size: CGSize(width: 30.0, height: 30.0))!.withRenderingMode(.alwaysTemplate) } diff --git a/ownCloud/Client/Actions/Actions+Extensions/UploadMediaAction.swift b/ownCloud/Client/Actions/Actions+Extensions/UploadMediaAction.swift index 5cf59b82e..590fb1c26 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/UploadMediaAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/UploadMediaAction.swift @@ -133,7 +133,7 @@ class UploadMediaAction: UploadBaseAction { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.uploadphotos") } override class var category : ActionCategory? { return .normal } override class var name : String { return "Upload from your photo library".localized } - override class var locations : [OCExtensionLocationIdentifier]? { return [.folderAction, .keyboardShortcut] } + override class var locations : [OCExtensionLocationIdentifier]? { return [.folderAction, .keyboardShortcut, .emptyFolder] } override class var keyCommand : String? { return "M" } override class var keyModifierFlags: UIKeyModifierFlags? { return [.command] } @@ -196,7 +196,7 @@ class UploadMediaAction: UploadBaseAction { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .folderAction { + if location == .folderAction || location == .emptyFolder { Theme.shared.add(tvgResourceFor: "image") return Theme.shared.image(for: "image", size: CGSize(width: 30.0, height: 30.0))!.withRenderingMode(.alwaysTemplate) } diff --git a/ownCloud/Client/Actions/Scanner/ScanAction.swift b/ownCloud/Client/Actions/Scanner/ScanAction.swift index f6963fbb7..5dec266bf 100644 --- a/ownCloud/Client/Actions/Scanner/ScanAction.swift +++ b/ownCloud/Client/Actions/Scanner/ScanAction.swift @@ -25,7 +25,7 @@ class ScanAction: Action, VNDocumentCameraViewControllerDelegate { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.scan") } override class var category : ActionCategory? { return .normal } override class var name : String? { return "Scan document".localized } - override class var locations : [OCExtensionLocationIdentifier]? { return [ .folderAction, .keyboardShortcut ] } + override class var locations : [OCExtensionLocationIdentifier]? { return [ .folderAction, .keyboardShortcut, .emptyFolder ] } override class var keyCommand : String? { return "S" } override class var keyModifierFlags: UIKeyModifierFlags? { return [.command, .shift] } override class var licenseRequirements: LicenseRequirements? { return LicenseRequirements(feature: .documentScanner) } @@ -98,7 +98,7 @@ class ScanAction: Action, VNDocumentCameraViewControllerDelegate { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .folderAction { + if location == .folderAction || location == .emptyFolder { return UIImage(systemName: "doc.text.viewfinder", withConfiguration: UIImage.SymbolConfiguration(pointSize: 26, weight: .regular)) } diff --git a/ownCloud/Client/ClientRootViewController+ItemActions.swift b/ownCloud/Client/ClientRootViewController+ItemActions.swift index ea5490a95..07b6910be 100644 --- a/ownCloud/Client/ClientRootViewController+ItemActions.swift +++ b/ownCloud/Client/ClientRootViewController+ItemActions.swift @@ -21,7 +21,7 @@ import ownCloudSDK import ownCloudAppShared import ownCloudApp -extension ClientRootViewController : MoreItemAction { +extension ClientRootViewController : ActionProgressHandlerProvider { func makeActionProgressHandler() -> ActionProgressHandler { return { [weak self] (progress, publish) in if publish { @@ -31,9 +31,11 @@ extension ClientRootViewController : MoreItemAction { } } } +} - func moreOptions(for item: OCItem, at locationIdentifier: OCExtensionLocationIdentifier, context: ClientContext, sender: AnyObject?) -> Bool { - guard let sender = sender, let core = context.core else { +extension ClientRootViewController : MoreItemAction { + func moreOptions(for item: OCDataItem, at locationIdentifier: OCExtensionLocationIdentifier, context: ClientContext, sender: AnyObject?) -> Bool { + guard let sender = sender, let core = context.core, let item = item as? OCItem else { return false } let originatingViewController : UIViewController = context.originatingViewController ?? self @@ -48,80 +50,17 @@ extension ClientRootViewController : MoreItemAction { } } -extension ClientRootViewController : OpenItemAction { - @discardableResult public func open(item: OCItem, context: ClientContext, animated: Bool, pushViewController: Bool) -> UIViewController? { - if let core = context.core { - if let bookmarkContainer = self.tabBarController as? BookmarkContainer { - let activity = OpenItemUserActivity(detailItem: item, detailBookmark: bookmarkContainer.bookmark) - view.window?.windowScene?.userActivity = activity.openItemUserActivity - } - - switch item.type { - case .collection: - if let location = item.location { - let query = OCQuery(for: location) - DisplaySettings.shared.updateQuery(withDisplaySettings: query) - - let queryViewController = ClientItemViewController(context: context, query: query) - if pushViewController { - context.navigationController?.pushViewController(queryViewController, animated: animated) - } - return queryViewController - } - - case .file: - guard let query = context.query else { - return nil - } - - let itemViewController = DisplayHostViewController(core: core, selectedItem: item, query: query) - itemViewController.hidesBottomBarWhenPushed = true - //!! itemViewController.progressSummarizer = self.progressSummarizer - context.navigationController?.pushViewController(itemViewController, animated: animated) - } - } - - return nil - } -} - -extension ClientRootViewController : ContextMenuProvider { - func composeContextMenuElements(for viewController: UIViewController, item: OCItem, location: OCExtensionLocationIdentifier, context: ClientContext, sender: AnyObject?) -> [UIMenuElement]? { - guard let core = context.core else { +extension ClientRootViewController : ViewItemAction { + func provideViewer(for item: OCDataItem, context: ClientContext) -> UIViewController? { + guard let item = item as? OCItem, let query = context.query, let core = context.core else { return nil } - let actionsLocation = OCExtensionLocation(ofType: .action, identifier: location) // .contextMenuItem) - let actionContext = ActionContext(viewController: viewController, core: core, items: [item], location: actionsLocation, sender: sender) - let actions = Action.sortedApplicableActions(for: actionContext) - var actionMenuActions : [UIAction] = [] - for action in actions { - action.progressHandler = makeActionProgressHandler() - - if let menuAction = action.provideUIMenuAction() { - actionMenuActions.append(menuAction) - } - } - - if core.connectionStatus == .online, core.connection.capabilities?.sharingAPIEnabled == 1, location == .contextMenuItem { - // Actions menu - let actionsMenu = UIMenu(title: "", identifier: UIMenu.Identifier("context"), options: .displayInline, children: actionMenuActions) - - // Share Items - let sharingActionsLocation = OCExtensionLocation(ofType: .action, identifier: .contextMenuSharingItem) - let sharingActionContext = ActionContext(viewController: viewController, core: core, items: [item], location: sharingActionsLocation, sender: sender) - let sharingActions = Action.sortedApplicableActions(for: sharingActionContext) - for action in sharingActions { - action.progressHandler = makeActionProgressHandler() - } - - let sharingItems = sharingActions.compactMap({$0.provideUIMenuAction()}) - let shareMenu = UIMenu(title: "", identifier: UIMenu.Identifier("sharing"), options: .displayInline, children: sharingItems) - - return [shareMenu, actionsMenu] - } + let itemViewController = DisplayHostViewController(clientContext: context, core: core, selectedItem: item, query: query) + itemViewController.hidesBottomBarWhenPushed = true + itemViewController.progressSummarizer = context.progressSummarizer - return actionMenuActions + return itemViewController } } diff --git a/ownCloud/Client/ClientRootViewController.swift b/ownCloud/Client/ClientRootViewController.swift index 2c4a0d8fa..2988f635d 100644 --- a/ownCloud/Client/ClientRootViewController.swift +++ b/ownCloud/Client/ClientRootViewController.swift @@ -379,9 +379,9 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa if let core = self.core { self.rootContext = ClientContext(core: core, rootViewController: self, progressSummarizer: self.progressSummarizer, modifier: { context in context.inlineMessageCenter = self - context.openItemHandler = self context.moreItemHandler = self - context.contextMenuProvider = self + context.viewItemHandler = self + context.actionProgressHandlerProvider = self }) core.vault.resourceManager?.add(ResourceSourceItemIcons(core: core)) @@ -456,8 +456,8 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa topLevelViewController = CollectionViewController(context: ClientContext(with: self.rootContext, navigationController: self.filesNavigationController), sections: [ // CollectionViewSection(identifier: "composed", dataSource: composedDataSource) - CollectionViewSection(identifier: "top", dataSource: core.hierarchicDrivesDataSource), - CollectionViewSection(identifier: "projects", dataSource: core.projectDrivesDataSource) + CollectionViewSection(identifier: "top", dataSource: core.hierarchicDrivesDataSource, cellLayout: .list(appearance: .insetGrouped)), + CollectionViewSection(identifier: "projects", dataSource: core.projectDrivesDataSource, cellLayout: .list(appearance: .insetGrouped)) ]) } else { let query = OCQuery(for: .legacyRoot) diff --git a/ownCloud/Client/Viewer/DisplayHostViewController.swift b/ownCloud/Client/Viewer/DisplayHostViewController.swift index 5169649ed..e83e38a6f 100644 --- a/ownCloud/Client/Viewer/DisplayHostViewController.swift +++ b/ownCloud/Client/Viewer/DisplayHostViewController.swift @@ -51,14 +51,18 @@ class DisplayHostViewController: UIPageViewController { var progressSummarizer : ProgressSummarizer? + public var clientContext : ClientContext? + // MARK: - Init & deinit - init(core: OCCore, selectedItem: OCItem, query: OCQuery) { + init(clientContext: ClientContext? = nil, core: OCCore, selectedItem: OCItem, query: OCQuery) { self.core = core self.initialItem = selectedItem self.query = query super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) + self.clientContext = clientContext + if query.state == .stopped { self.core?.start(query) queryStarted = true diff --git a/ownCloudAppShared/Client/Actions/Action.swift b/ownCloudAppShared/Client/Actions/Action.swift index 65103c53d..05e99033d 100644 --- a/ownCloudAppShared/Client/Actions/Action.swift +++ b/ownCloudAppShared/Client/Actions/Action.swift @@ -60,6 +60,7 @@ public extension OCExtensionLocationIdentifier { static let moreItem: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("moreDetailItem") //!< Present in "more" card view for a single item in detail view static let moreDetailItem: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("moreItem") //!< Present in "more" card view for a single item static let moreFolder: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("moreFolder") //!< Present in "more" options for a whole folder + static let emptyFolder: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("emptyFolder") //!< Present in "more" options for a whole folder static let toolbar: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("toolbar") //!< Present in a toolbar static let folderAction: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("folderAction") //!< Present in the alert sheet when the folder action bar button is pressed static let keyboardShortcut: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("keyboardShortcut") //!< Currently used for UIKeyCommand @@ -475,6 +476,22 @@ open class Action : NSObject { return alertAction } + open func provideOCAction() -> OCAction? { + let icon = self.icon?.paddedTo(width: 36, height: nil) + var name = actionExtension.name + + if !isLicensed { + name += " " + proLabel + } + + let ocAction = OCAction(title: name, icon: icon, action: { _, options, completionHandler in + self.perform() + completionHandler(nil) + }) + + return ocAction + } + // MARK: - Action metadata class open func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { return nil diff --git a/ownCloudAppShared/Client/Actions/CreateFolderAction.swift b/ownCloudAppShared/Client/Actions/CreateFolderAction.swift index 60e4c6e83..bf555ff85 100644 --- a/ownCloudAppShared/Client/Actions/CreateFolderAction.swift +++ b/ownCloudAppShared/Client/Actions/CreateFolderAction.swift @@ -22,7 +22,7 @@ open class CreateFolderAction : Action { override open class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.createFolder") } override open class var category : ActionCategory? { return .normal } override open class var name : String? { return "Create folder".localized } - override open class var locations : [OCExtensionLocationIdentifier]? { return [.folderAction, .keyboardShortcut] } + override open class var locations : [OCExtensionLocationIdentifier]? { return [.folderAction, .keyboardShortcut, .emptyFolder] } override open class var keyCommand : String? { return "N" } override open class var keyModifierFlags: UIKeyModifierFlags? { return [.command] } @@ -106,7 +106,7 @@ open class CreateFolderAction : Action { } override open class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .toolbar || location == .folderAction || location == .contextMenuItem { + if location == .toolbar || location == .folderAction || location == .contextMenuItem || location == .emptyFolder { return Theme.shared.image(for: "folder-create", size: CGSize(width: 30.0, height: 30.0))!.withRenderingMode(.alwaysTemplate) } diff --git a/ownCloudAppShared/Client/Collection Views/Cells/ActionCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/ActionCell.swift new file mode 100644 index 000000000..5973da25e --- /dev/null +++ b/ownCloudAppShared/Client/Collection Views/Cells/ActionCell.swift @@ -0,0 +1,72 @@ +// +// ActionCell.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 30.05.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK + +class ActionCell: ThemeableCollectionViewListCell { + static func registerCellProvider() { + let actionCellRegistration = UICollectionView.CellRegistration { (cell, indexPath, collectionItemRef) in + var content = cell.defaultContentConfiguration() + + if let cellConfiguration = collectionItemRef.ocCellConfiguration { + var itemRecord = cellConfiguration.record + + if itemRecord == nil { + if let collectionViewController = cellConfiguration.hostViewController { + let (itemRef, _) = collectionViewController.unwrap(collectionItemRef) + + if let retrievedItemRecord = try? cellConfiguration.source?.record(forItemRef: itemRef) { + itemRecord = retrievedItemRecord + } + } + } + + if let itemRecord = itemRecord { + if let item = itemRecord.item { + if let action = OCDataRenderer.default.renderItem(item, asType: .action, error: nil, withOptions: nil) as? OCAction { + content.text = action.title + content.image = action.icon + } + } else { + // Request reconfiguration of cell + itemRecord.retrieveItem(completionHandler: { error, itemRecord in + if let collectionViewController = cellConfiguration.hostViewController { + collectionViewController.collectionViewDataSource.requestReconfigurationOfItems([collectionItemRef]) + } + }) + } + } + } + + cell.contentConfiguration = content + } + + CollectionViewCellProvider.register(CollectionViewCellProvider(for: .action, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in + return collectionView.dequeueConfiguredReusableCell(using: actionCellRegistration, for: indexPath, item: itemRef) + })) + } +} + +extension OCAction : DataItemSelectionInteraction { + public func handleSelection(in viewController: UIViewController?, with context: ClientContext?, completion: ((Bool) -> Void)?) { + run(options: nil, completionHandler: { error in + completion?(error == nil) + }) + } +} diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewCellProvider+StandardImplementations.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewCellProvider+StandardImplementations.swift index 8f506216e..b6733d709 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewCellProvider+StandardImplementations.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewCellProvider+StandardImplementations.swift @@ -25,6 +25,7 @@ public extension CollectionViewCellProvider { DriveListCell.registerCellProvider() ItemListCell.registerCellProvider() ExpandableResourceCell.registerCellProvider() + ActionCell.registerCellProvider() registerPresentableCellProvider() } @@ -102,9 +103,10 @@ public extension CollectionViewCellProvider { CollectionViewCellProvider.register(CollectionViewCellProvider(for: .presentable, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in return collectionView.dequeueConfiguredReusableCell(using: presentableCellRegistration, for: indexPath, item: itemRef) })) -// -// CollectionViewCellProvider.register(CollectionViewCellProvider(for: .item, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in -// return collectionView.dequeueConfiguredReusableCell(using: presentableCellRegistration, for: indexPath, item: itemRef) -// })) + + // This registration performs conversion to .presentable where necessary, so it can also be used for other types OCDataItemTypes. Example: + // CollectionViewCellProvider.register(CollectionViewCellProvider(for: .item, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in + // return collectionView.dequeueConfiguredReusableCell(using: presentableCellRegistration, for: indexPath, item: itemRef) + // })) } } diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift index 97a252454..02ab6b503 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift @@ -25,9 +25,8 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat public var supportsHierarchicContent: Bool - public init(context inContext: ClientContext?, sections inSections: [CollectionViewSection]?, hierarchic: Bool = false, listAppearance inListAppearance: UICollectionLayoutListConfiguration.Appearance = .insetGrouped) { + public init(context inContext: ClientContext?, sections inSections: [CollectionViewSection]?, hierarchic: Bool = false) { supportsHierarchicContent = hierarchic - listAppearance = inListAppearance super.init(nibName: nil, bundle: nil) @@ -57,8 +56,6 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat var collectionView : UICollectionView! = nil var collectionViewDataSource: UICollectionViewDiffableDataSource! = nil - public var listAppearance : UICollectionLayoutListConfiguration.Appearance - public override func viewDidLoad() { super.viewDidLoad() configureViews() @@ -68,22 +65,25 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat } public func createCollectionViewLayout() -> UICollectionViewLayout { - var config = UICollectionLayoutListConfiguration(appearance: listAppearance) - switch listAppearance { - case .plain: - config.backgroundColor = Theme.shared.activeCollection.tableBackgroundColor + let configuration = UICollectionViewCompositionalLayoutConfiguration() - case .grouped, .insetGrouped: - config.backgroundColor = Theme.shared.activeCollection.tableGroupBackgroundColor + configuration.interSectionSpacing = 0 - default: break - } - return UICollectionViewCompositionalLayout.list(using: config) + return UICollectionViewCompositionalLayout.init(sectionProvider: { sectionIndex, layoutEnvironment in + if sectionIndex > 0, sectionIndex < self.sections.count { + return self.sections[sectionIndex].provideCollectionLayoutSection(layoutEnvironment: layoutEnvironment) + } + + // Fallback to allow compilation - should never be called + return CollectionViewSection.CellLayout.list(appearance: .grouped).collectionLayoutSection(layoutEnvironment: layoutEnvironment) + }, configuration: configuration) } public func configureViews() { collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createCollectionViewLayout()) collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + collectionView.contentInsetAdjustmentBehavior = .never + collectionView.contentInset = .zero view.addSubview(collectionView) collectionView.delegate = self } @@ -235,7 +235,10 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat // MARK: - Collection View Delegate public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { retrieveItem(at: indexPath, action: { [weak self] record, indexPath in - self?.handleSelection(of: record, at: indexPath) + // Return early if .selection is not allowed + if self?.clientContext?.validate(permission: .selection, for: record) != false { + self?.handleSelection(of: record, at: indexPath) + } }, handleError: { error in collectionView.deselectItem(at: indexPath, animated: true) }) @@ -245,7 +248,10 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat var contextMenuConfiguration : UIContextMenuConfiguration? retrieveItem(at: indexPath, synchronous: true, action: { [weak self] record, indexPath in - contextMenuConfiguration = self?.provideContextMenuConfiguration(for: record, at: indexPath, point: point) + // Return early if .contextMenu is not allowed + if self?.clientContext?.validate(permission: .contextMenu, for: record) != false { + contextMenuConfiguration = self?.provideContextMenuConfiguration(for: record, at: indexPath, point: point) + } }, handleError: { error in collectionView.deselectItem(at: indexPath, animated: true) }) @@ -253,27 +259,59 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat return contextMenuConfiguration } - @discardableResult public func handleSelection(of record: OCDataItemRecord, at indexPath: IndexPath) -> Bool { - if let drive = record.item as? OCDrive { - let driveContext = ClientContext(with: clientContext, modifier: { context in - context.drive = drive - }) - let query = OCQuery(for: drive.rootLocation) - DisplaySettings.shared.updateQuery(withDisplaySettings: query) - - let rootFolderViewController = ClientItemViewController(context: driveContext, query: query) - - collectionView.deselectItem(at: indexPath, animated: true) - - self.navigationController?.pushViewController(rootFolderViewController, animated: true) + // MARK: - Cell action subclassing points + @discardableResult public func handleSelection(of record: OCDataItemRecord, at indexPath: IndexPath) -> Bool { + // Use item's DataItemSelectionInteraction + if let selectionInteraction = record.item as? DataItemSelectionInteraction { + // Try selection first + if selectionInteraction.handleSelection?(in: self, with: clientContext, completion: { [weak self] success in + self?.collectionView.deselectItem(at: indexPath, animated: true) + }) == true { + return true + } - return true + // Then try opening + if selectionInteraction.openItem?(in: self, with: clientContext, animated: true, pushViewController: true, completion: { [weak self] success in + self?.collectionView.deselectItem(at: indexPath, animated: true) + }) != nil { + return true + } } return false } @discardableResult public func provideContextMenuConfiguration(for record: OCDataItemRecord, at indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + // Use context.contextMenuProvider + if let item = record.item, let clientContext = clientContext, let contextMenuProvider = clientContext.contextMenuProvider { + return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { [weak self] _ in + guard let self = self else { + return nil + } + + if let menuItems = contextMenuProvider.composeContextMenuElements(for: self, item: item, location: .contextMenuItem, context: clientContext, sender: nil) { + return UIMenu(title: "", children: menuItems) + } + + return nil + }) + } + + // Use item's DataItemContextMenuInteraction + if let contextMenuInteraction = record.item as? DataItemContextMenuInteraction { + return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { [weak self] _ in + guard let self = self else { + return nil + } + + if let menuItems = contextMenuInteraction.composeContextMenuItems(in: self, location: .contextMenuItem, with: self.clientContext) { + return UIMenu(title: "", children: menuItems) + } + + return nil + }) + } + return nil } diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift index e0a3a98c0..f32779ee2 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift @@ -20,6 +20,106 @@ import UIKit import ownCloudSDK public class CollectionViewSection: NSObject { + public enum CellLayout { + case list(appearance: UICollectionLayoutListConfiguration.Appearance) + case fullWidth(heightDimension: NSCollectionLayoutDimension, interItemSpacing: NSCollectionLayoutSpacing? = nil, contentInsets: NSDirectionalEdgeInsets = .zero) + + func collectionLayoutSection(for collectionViewController: CollectionViewController? = nil, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { + switch self { + // List + case .list(let listAppearance): + var config = UICollectionLayoutListConfiguration(appearance: listAppearance) + + // Appearance + switch listAppearance { + case .plain: + config.headerMode = .firstItemInSection + config.headerTopPadding = 0 + config.backgroundColor = Theme.shared.activeCollection.tableBackgroundColor + + case .grouped, .insetGrouped: + config.backgroundColor = Theme.shared.activeCollection.tableGroupBackgroundColor + + default: break + } + +// config.headerTopPadding = 0 +// config.headerMode = .none +// config.footerMode = .none + + // Leading and trailing swipe actions + if let collectionViewController = collectionViewController { + let clientContext = ClientContext(with: collectionViewController.clientContext, modifier: { context in + context.originatingViewController = collectionViewController + }) + + config.leadingSwipeActionsConfigurationProvider = { (_ indexPath: IndexPath) in + var swipeConfiguration : UISwipeActionsConfiguration? + + collectionViewController.retrieveItem(at: indexPath, synchronous: true, action: { record, indexPath in + // Return early if leadingSwipes are not allowed + if !clientContext.validate(permission: .leadingSwipe, for: record) { + return + } + + // Use context's swipeActionsProvider + if let item = record.item, + let swipeActionsProvider = clientContext.swipeActionsProvider, + (swipeActionsProvider as? NSObject)?.responds(to: #selector(SwipeActionsProvider.provideLeadingSwipeActions(for:item:context:))) == true { + swipeConfiguration = swipeActionsProvider.provideLeadingSwipeActions?(for: collectionViewController, item: item, context: clientContext) + } + + // Use item's DataItemSwipeInteraction + if swipeConfiguration == nil, + let dataItem = record.item as? DataItemSwipeInteraction, + dataItem.responds(to: #selector(DataItemSwipeInteraction.provideLeadingSwipeActions(with:))) { + swipeConfiguration = dataItem.provideLeadingSwipeActions?(with: clientContext) + } + }) + + return swipeConfiguration + } + + config.trailingSwipeActionsConfigurationProvider = { (_ indexPath: IndexPath) in + var swipeConfiguration : UISwipeActionsConfiguration? + + collectionViewController.retrieveItem(at: indexPath, synchronous: true, action: { record, indexPath in + // Return early if trailingSwipes are not allowed + if !clientContext.validate(permission: .trailingSwipe, for: record) { + return + } + + // Use context's swipeActionsProvider + if let item = record.item, + let swipeActionsProvider = clientContext.swipeActionsProvider, + (swipeActionsProvider as? NSObject)?.responds(to: #selector(SwipeActionsProvider.provideTrailingSwipeActions(for:item:context:))) == true { + swipeConfiguration = swipeActionsProvider.provideTrailingSwipeActions?(for: collectionViewController, item: item, context: clientContext) + } + + // Use item's DataItemSwipeInteraction + if swipeConfiguration == nil, + let dataItem = record.item as? DataItemSwipeInteraction, + dataItem.responds(to: #selector(DataItemSwipeInteraction.provideTrailingSwipeActions(with:))) { + swipeConfiguration = dataItem.provideTrailingSwipeActions?(with: clientContext) + } + }) + + return swipeConfiguration + } + } + + return NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment) + + // Full width + case .fullWidth(let heightDimension, let interItemSpacing, let contentInsets): + let group = NSCollectionLayoutGroup(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: heightDimension)) + group.interItemSpacing = interItemSpacing + group.contentInsets = contentInsets + return NSCollectionLayoutSection(group: group) + } + } + } + public typealias SectionIdentifier = String public typealias CellConfigurationCustomizer = (_ collectionView: UICollectionView, _ cellConfiguration: CollectionViewCellConfiguration, _ itemRecord: OCDataItemRecord, _ collectionItemRef: CollectionViewController.ItemRef, _ indexPath: IndexPath) -> Void @@ -43,17 +143,12 @@ public class CollectionViewSection: NSObject { public var clientContext: ClientContext? public var cellConfigurationCustomizer : CellConfigurationCustomizer? - func updateDatasourceSubscription() { - if let dataSource = dataSource { - dataSourceSubscription = dataSource.subscribe(updateHandler: { [weak self] (subscription) in - self?.handleListUpdates(from: subscription) - }, on: .main, trackDifferences: true, performIntialUpdate: true) - } - } + public var cellLayout: CellLayout - public init(identifier: SectionIdentifier, dataSource inDataSource: OCDataSource?, cellStyle : CollectionViewCellStyle = .tableCell, clientContext: ClientContext? = nil ) { + public init(identifier: SectionIdentifier, dataSource inDataSource: OCDataSource?, cellStyle : CollectionViewCellStyle = .tableCell, cellLayout: CellLayout = .list(appearance: .plain), clientContext: ClientContext? = nil ) { self.identifier = identifier self.cellStyle = cellStyle + self.cellLayout = cellLayout super.init() @@ -66,10 +161,20 @@ public class CollectionViewSection: NSObject { dataSourceSubscription?.terminate() } + // MARK: - Data source handling + func updateDatasourceSubscription() { + if let dataSource = dataSource { + dataSourceSubscription = dataSource.subscribe(updateHandler: { [weak self] (subscription) in + self?.handleListUpdates(from: subscription) + }, on: .main, trackDifferences: true, performIntialUpdate: true) + } + } + func handleListUpdates(from subscription: OCDataSourceSubscription) { collectionViewController?.updateSource(animatingDifferences: true) } + // MARK: - Item provider func populate(snapshot: inout NSDiffableDataSourceSnapshot) { if let datasourceSnapshot = dataSourceSubscription?.snapshotResettingChangeTracking(true) { if let wrappedItems = collectionViewController?.wrap(references: datasourceSnapshot.items, forSection: identifier) { @@ -83,6 +188,19 @@ public class CollectionViewSection: NSObject { } } +// func provideDataItem(for collectionView: UICollectionView, collectionItemRef: CollectionViewController.ItemRef) -> OCDataItem? { +// var dataItem: OCDataItem? +// +// if let (dataItemRef, _) = collectionViewController?.unwrap(collectionItemRef) { +// if let itemRecord = try? dataSource?.record(forItemRef: dataItemRef) { +// dataItem = itemRecord.item +// } +// } +// +// return dataItem +// } + + // MARK: - Cell provider func provideReusableCell(for collectionView: UICollectionView, collectionItemRef: CollectionViewController.ItemRef, indexPath: IndexPath) -> UICollectionViewCell { var cell: UICollectionViewCell? @@ -108,4 +226,9 @@ public class CollectionViewSection: NSObject { return cell ?? UICollectionViewCell() } + + // MARK: - Section layout + open func provideCollectionLayoutSection(layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { + return cellLayout.collectionLayoutSection(for: self.collectionViewController, layoutEnvironment: layoutEnvironment) + } } diff --git a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift index a44727d74..c472cd0a2 100644 --- a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift @@ -20,9 +20,17 @@ import UIKit import ownCloudSDK import ownCloudApp -public class ClientItemViewController: CollectionViewController { +public class ClientItemViewController: CollectionViewController, UISearchControllerDelegate, UISearchResultsUpdating { + public enum ContentState : String, CaseIterable { + case loading + + case empty + case hasContent + } + public var query: OCQuery? + weak public var queryDataSource : OCDataSource? public var queryItemDataSourceSection : CollectionViewSection? public var driveSection : CollectionViewSection? @@ -36,12 +44,29 @@ public class ClientItemViewController: CollectionViewController { public var emptyItemListDecisionSubscription : OCDataSourceSubscription? public var emptyItemListItem : OCDataItemPresentable? + private var stateObservation : NSKeyValueObservation? + public init(context inContext: ClientContext?, query inQuery: OCQuery, reveal inItem: OCItem? = nil) { query = inQuery var sections : [ CollectionViewSection ] = [] - let itemControllerContext = ClientContext(with: inContext) + let itemControllerContext = ClientContext(with: inContext, modifier: { context in + context.permissionHandler = { (context, record, interaction) in + switch interaction { + case .selection: + if record?.type == .drive { + // Do not react to taps on the drive header cells (=> or show image in the future) + return false + } + + return true + + default: + return true + } + } + }) itemControllerContext.postInitializationModifier = { (owner, context) in if context.openItemHandler == nil { context.openItemHandler = owner as? OpenItemAction @@ -55,7 +80,8 @@ public class ClientItemViewController: CollectionViewController { context.originatingViewController = owner as? UIViewController } - if let queryDatasource = query?.queryResultsDataSource, let core = itemControllerContext.core { + if let queryResultsDatasource = query?.queryResultsDataSource, let core = itemControllerContext.core { + queryDataSource = queryResultsDatasource singleDriveDatasource = OCDataSourceComposition(sources: [core.drivesDataSource]) if query?.queryLocation?.isRoot == true { @@ -74,10 +100,10 @@ public class ClientItemViewController: CollectionViewController { driveSectionDataSource = OCDataSourceComposition(sources: [ singleDriveDatasource!, driveAdditionalItemsDataSource ]) // Create drive section from combined data source - driveSection = CollectionViewSection(identifier: "drive", dataSource: driveSectionDataSource, cellStyle: .header) + driveSection = CollectionViewSection(identifier: "drive", dataSource: driveSectionDataSource, cellStyle: .header, cellLayout: .list(appearance: .plain)) } - queryItemDataSourceSection = CollectionViewSection(identifier: "items", dataSource: queryDatasource, clientContext: itemControllerContext) + queryItemDataSourceSection = CollectionViewSection(identifier: "items", dataSource: queryResultsDatasource, clientContext: itemControllerContext) if let driveSection = driveSection { sections.append(driveSection) @@ -88,9 +114,15 @@ public class ClientItemViewController: CollectionViewController { } } - sections.append(CollectionViewSection(identifier: "empty", dataSource: emptyItemListDataSource, cellStyle: .fillSpace, clientContext: itemControllerContext)) + let emptySection = CollectionViewSection(identifier: "empty", dataSource: emptyItemListDataSource, cellStyle: .fillSpace, cellLayout: .list(appearance: .insetGrouped), clientContext: itemControllerContext) + sections.append(emptySection) - super.init(context: itemControllerContext, sections: sections, listAppearance: .plain) + super.init(context: itemControllerContext, sections: sections) + + // Track query state and recompute content state when it changes + stateObservation = queryDataSource?.observe(\OCDataSource.state, options: [], changeHandler: { [weak self] query, change in + self?.recomputeContentState() + }) // Subscribe to singleDriveDatasource for changes, to update driveSectionDataSource singleDriveDatasourceSubscription = singleDriveDatasource?.subscribe(updateHandler: { [weak self] (subscription) in @@ -99,7 +131,7 @@ public class ClientItemViewController: CollectionViewController { if let queryDatasource = query?.queryResultsDataSource { emptyItemListItem = OCDataItemPresentable(reference: "_emptyItemList" as NSString, originalDataItemType: nil, version: nil) - emptyItemListItem?.title = "Empty folder".localized + emptyItemListItem?.title = "This folder is empty. Fill it with content:".localized emptyItemListItem?.childrenDataSourceProvider = nil emptyItemListDecisionSubscription = queryDatasource.subscribe(updateHandler: { [weak self] (subscription) in @@ -119,6 +151,7 @@ public class ClientItemViewController: CollectionViewController { } deinit { + stateObservation?.invalidate() singleDriveDatasourceSubscription?.terminate() } @@ -146,8 +179,10 @@ public class ClientItemViewController: CollectionViewController { let plusBarButton = UIBarButtonItem(barButtonSystemItem: .add, target: nil, action: nil) plusBarButton.menu = UIMenu(title: "", children: [ UIDeferredMenuElement.uncached({ [weak self] completion in - if let self = self, let contextMenuProvider = self.clientContext?.contextMenuProvider, let rootItem = self.query?.rootItem, let clientContext = self.clientContext { - if let contextMenuElements = contextMenuProvider.composeContextMenuElements(for: self, item: rootItem, location: .folderAction, context: clientContext, sender: nil) { + if let self = self, let rootItem = self.query?.rootItem, let clientContext = self.clientContext { + let contextMenuProvider = rootItem as DataItemContextMenuInteraction + + if let contextMenuElements = contextMenuProvider.composeContextMenuItems(in: self, location: .folderAction, with: clientContext) { completion(contextMenuElements) } } @@ -159,6 +194,17 @@ public class ClientItemViewController: CollectionViewController { } self.navigationItem.rightBarButtonItems = viewActionButtons + + // Setup search controller +// searchController = UISearchController(searchResultsController: nil) +// searchController?.searchResultsUpdater = self +// searchController?.obscuresBackgroundDuringPresentation = false +// searchController?.hidesNavigationBarDuringPresentation = true +// searchController?.searchBar.applyThemeCollection(Theme.shared.activeCollection) +// searchController?.delegate = self +// +// navigationItem.searchController = searchController +// navigationItem.hidesSearchBarWhenScrolling = false } public override func viewWillAppear(_ animated: Bool) { @@ -177,31 +223,6 @@ public class ClientItemViewController: CollectionViewController { } } - public override func handleSelection(of record: OCDataItemRecord, at indexPath: IndexPath) -> Bool { - if let item = record.item as? OCItem, let location = item.location, let clientContext = clientContext { - collectionView.deselectItem(at: indexPath, animated: true) - - if let openHandler = clientContext.openItemHandler { - openHandler.open(item: item, context: clientContext, animated: true, pushViewController: true) - } else { - let query = OCQuery(for: location) - DisplaySettings.shared.updateQuery(withDisplaySettings: query) - - let rootFolderViewController = ClientItemViewController(context: clientContext, query: query) - self.navigationController?.pushViewController(rootFolderViewController, animated: true) - } - - return true - } - - if (record.item as? OCDrive) != nil { - // Do not react to taps on the drive header cells (=> or show image in the future) - return false - } - - return super.handleSelection(of: record, at: indexPath) - } - public func updateAdditionalDriveItems(from subscription: OCDataSourceSubscription) { let snapshot = subscription.snapshotResettingChangeTracking(true) @@ -223,35 +244,70 @@ public class ClientItemViewController: CollectionViewController { } } - @discardableResult public override func provideContextMenuConfiguration(for record: OCDataItemRecord, at indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { - return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { [weak self] _ in - guard let item = record.item as? OCItem, let clientContext = self?.clientContext, let self = self else { - return nil - } + var _actionProgressHandler : ActionProgressHandler? + + // MARK: - Empty item list handling + func emptyActions() -> [OCAction]? { + guard let context = clientContext, let core = context.core, let item = query?.rootItem else { + return nil + } + let locationIdentifier: OCExtensionLocationIdentifier = .emptyFolder + let originatingViewController : UIViewController = context.originatingViewController ?? self + let actionsLocation = OCExtensionLocation(ofType: .action, identifier: locationIdentifier) + let actionContext = ActionContext(viewController: originatingViewController, core: core, query: context.query, items: [item], location: actionsLocation, sender: self) + + let emptyFolderActions = Action.sortedApplicableActions(for: actionContext) + var actions : [OCAction] = [] - if let menuItems = clientContext.contextMenuProvider?.composeContextMenuElements(for: self, item: item, location: .contextMenuItem, context: clientContext, sender: nil) { - return UIMenu(title: "", children: menuItems) + for emptyFolderAction in emptyFolderActions { + if let action = emptyFolderAction.provideOCAction() { + actions.append(action) } + } - return nil - }) + return (actions.count > 0) ? actions : nil } - var _actionProgressHandler : ActionProgressHandler? - - // MARK: - Empty item list handling func updateEmptyItemList(from subscription: OCDataSourceSubscription) { - hasNoItems = subscription.snapshotResettingChangeTracking(true).numberOfItems == 0 + recomputeContentState() + } + + func recomputeContentState() { + OnMainThread { + switch self.queryDataSource?.state { + case .loading: + self.contentState = .loading + + case .idle: + self.contentState = (self.emptyItemListDecisionSubscription?.snapshotResettingChangeTracking(true).numberOfItems == 0) ? .empty : .hasContent + + default: break + } + } } - public var hasNoItems : Bool = false { + public var contentState : ContentState = .loading { didSet { - if hasNoItems != oldValue { - if hasNoItems, let emptyItemListItem = emptyItemListItem { - emptyItemListDataSource.setItems([emptyItemListItem], updated: nil) - } else { + if contentState == oldValue { + return + } + + switch contentState { + case .empty: + var emptyItems : [OCDataItem] = [ ] + + if let emptyItemListItem = emptyItemListItem { + emptyItems.append(emptyItemListItem) + } + + if let emptyActions = emptyActions() { + emptyItems.append(contentsOf: emptyActions) + } + + emptyItemListDataSource.setItems(emptyItems, updated: nil) + + case .hasContent, .loading: emptyItemListDataSource.setItems(nil, updated: nil) - } } } } @@ -266,4 +322,46 @@ public class ClientItemViewController: CollectionViewController { moreItemHandler.moreOptions(for: rootItem, at: .moreFolder, context: clientContext, sender: sender) } } + + // MARK: - Search + open var searchController: UISearchController? + + // MARK: - Search: UISearchResultsUpdating Delegate + open func updateSearchResults(for searchController: UISearchController) { + let searchText = searchController.searchBar.text ?? "" + +// applySearchFilter(for: (searchText == "") ? nil : searchText, to: query) + } + + open func willPresentSearchController(_ searchController: UISearchController) { +// self.sortBar?.showSelectButton = false + } + + open func willDismissSearchController(_ searchController: UISearchController) { +// self.sortBar?.showSelectButton = true + } + + open func applySearchFilter(for searchText: String?, to query: OCQuery) { + if let searchText = searchText { + let queryCondition = OCQueryCondition.fromSearchTerm(searchText) + let filterHandler: OCQueryFilterHandler = { (_, _, item) -> Bool in + if let item = item, let queryCondition = queryCondition { + return queryCondition.fulfilled(by: item) + } + return false + } + + if let filter = query.filter(withIdentifier: "text-search") { + query.updateFilter(filter, applyChanges: { filterToChange in + (filterToChange as? OCQueryFilter)?.filterHandler = filterHandler + }) + } else { + query.addFilter(OCQueryFilter.init(handler: filterHandler), withIdentifier: "text-search") + } + } else { + if let filter = query.filter(withIdentifier: "text-search") { + query.removeFilter(filter) + } + } + } } diff --git a/ownCloudAppShared/Client/Context/ClientContext.swift b/ownCloudAppShared/Client/Context/ClientContext.swift index bbf053c11..696d62082 100644 --- a/ownCloudAppShared/Client/Context/ClientContext.swift +++ b/ownCloudAppShared/Client/Context/ClientContext.swift @@ -25,22 +25,33 @@ import ownCloudSDK // - can be passed around "strongly" while storing OCCore reference "weakly" (structural barrier to accidential retains) public protocol OpenItemAction : AnyObject { - @discardableResult func open(item: OCItem, context: ClientContext, animated: Bool, pushViewController: Bool) -> UIViewController? + @discardableResult func open(item: OCDataItem, context: ClientContext, animated: Bool, pushViewController: Bool) -> UIViewController? +} + +public protocol ViewItemAction : AnyObject { + @discardableResult func provideViewer(for item: OCDataItem, context: ClientContext) -> UIViewController? } public protocol MoreItemAction : AnyObject { - @discardableResult func moreOptions(for item: OCItem, at location: OCExtensionLocationIdentifier, context: ClientContext, sender: AnyObject?) -> Bool + @discardableResult func moreOptions(for item: OCDataItem, at location: OCExtensionLocationIdentifier, context: ClientContext, sender: AnyObject?) -> Bool +} + +public protocol ActionProgressHandlerProvider : AnyObject { func makeActionProgressHandler() -> ActionProgressHandler } public protocol RevealItemAction : AnyObject { - @discardableResult func reveal(item: OCItem, context: ClientContext, sender: AnyObject?) -> Bool + @discardableResult func reveal(item: OCDataItem, context: ClientContext, sender: AnyObject?) -> Bool func showReveal(at path: IndexPath) -> Bool } public protocol ContextMenuProvider : AnyObject { - @available(iOS 13.0, *) - func composeContextMenuElements(for viewController: UIViewController, item: OCItem, location: OCExtensionLocationIdentifier, context: ClientContext, sender: AnyObject?) -> [UIMenuElement]? + func composeContextMenuElements(for viewController: UIViewController, item: OCDataItem, location: OCExtensionLocationIdentifier, context: ClientContext, sender: AnyObject?) -> [UIMenuElement]? +} + +@objc public protocol SwipeActionsProvider : AnyObject { + @objc optional func provideLeadingSwipeActions(for viewController: UIViewController, item: OCDataItem, context: ClientContext?) -> UISwipeActionsConfiguration? + @objc optional func provideTrailingSwipeActions(for viewController: UIViewController, item: OCDataItem, context: ClientContext?) -> UISwipeActionsConfiguration? } public protocol InlineMessageCenter : AnyObject { @@ -48,7 +59,16 @@ public protocol InlineMessageCenter : AnyObject { func showInlineMessageFor(item: OCItem) } +public enum ClientItemInteraction { + case selection + case contextMenu + case leadingSwipe + case trailingSwipe +} + public class ClientContext: NSObject { + public typealias PermissionHandler = (_ context: ClientContext?, _ dataItemRecord: OCDataItemRecord?, _ checkInteraction: ClientItemInteraction) -> Bool + public weak var parent: ClientContext? // MARK: - Core @@ -58,19 +78,29 @@ public class ClientContext: NSObject { public var drive: OCDrive? public weak var query: OCQuery? + // MARK: - Item + public var initialRootItem : OCItem? + // MARK: - UI objects public weak var rootViewController: UIViewController? public weak var navigationController: UINavigationController? // Navigation controller to push to public weak var originatingViewController: UIViewController? // Originating view controller for f.ex. actions + public weak var progressSummarizer: ProgressSummarizer? + public weak var actionProgressHandlerProvider: ActionProgressHandlerProvider? // MARK: - UI item handling public weak var openItemHandler: OpenItemAction? + public weak var viewItemHandler: ViewItemAction? public weak var moreItemHandler: MoreItemAction? public weak var revealItemHandler: RevealItemAction? public weak var contextMenuProvider: ContextMenuProvider? + public weak var swipeActionsProvider: SwipeActionsProvider? public weak var inlineMessageCenter: InlineMessageCenter? + public var permissionHandler : PermissionHandler? + public var permissions : [ClientItemInteraction]? + // MARK: - Post Initialization Modifier // allows postponing of a client context passed into another object until the object it is passed into is initialized and can be referenced public typealias PostInitializationModifier = (_ owner: Any?, _ context: ClientContext) -> Void @@ -89,14 +119,21 @@ public class ClientContext: NSObject { rootViewController = inRootViewController ?? inParent?.rootViewController navigationController = inNavigationController ?? inParent?.navigationController originatingViewController = inOriginatingViewController ?? inParent?.originatingViewController + progressSummarizer = inProgressSummarizer ?? inParent?.progressSummarizer + actionProgressHandlerProvider = inParent?.actionProgressHandlerProvider openItemHandler = inParent?.openItemHandler + viewItemHandler = inParent?.viewItemHandler moreItemHandler = inParent?.moreItemHandler revealItemHandler = inParent?.revealItemHandler contextMenuProvider = inParent?.contextMenuProvider + swipeActionsProvider = inParent?.swipeActionsProvider inlineMessageCenter = inParent?.inlineMessageCenter + permissions = inParent?.permissions + permissionHandler = inParent?.permissionHandler + modifier?(self) } @@ -104,4 +141,18 @@ public class ClientContext: NSObject { postInitializationModifier?(owner, self) postInitializationModifier = nil } + + public func validate(permission: ClientItemInteraction, for record: OCDataItemRecord) -> Bool { + if let permissions = permissions { + if !permissions.contains(permission) { + return false + } + } + + if let permissionHandler = permissionHandler { + return permissionHandler(self, record, permission) + } + + return true + } } diff --git a/ownCloudAppShared/Client/Data Item Interactions/OCDataItem+InteractionProtocols.swift b/ownCloudAppShared/Client/Data Item Interactions/OCDataItem+InteractionProtocols.swift new file mode 100644 index 000000000..7cff7c803 --- /dev/null +++ b/ownCloudAppShared/Client/Data Item Interactions/OCDataItem+InteractionProtocols.swift @@ -0,0 +1,40 @@ +// +// OCDataItem+InteractionProtocols.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 30.05.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK + +// MARK: - Selection +@objc public protocol DataItemSelectionInteraction: OCDataItem { + // Handle selection: suitable for f.ex. actions + @objc optional func handleSelection(in viewController: UIViewController?, with context: ClientContext?, completion: ((_ success: Bool) -> Void)?) -> Bool + + // "Open" the item: suitable when pushing view controllers that should be restorable + @objc optional func openItem(in viewController: UIViewController?, with context: ClientContext?, animated: Bool, pushViewController: Bool, completion: ((_ success: Bool) -> Void)?) -> UIViewController? +} + +// MARK: - Swipe Actions +@objc public protocol DataItemSwipeInteraction: OCDataItem { + @objc optional func provideLeadingSwipeActions(with context: ClientContext?) -> UISwipeActionsConfiguration? + @objc optional func provideTrailingSwipeActions(with context: ClientContext?) -> UISwipeActionsConfiguration? +} + +// MARK: - Context menu +@objc public protocol DataItemContextMenuInteraction: OCDataItem { + func composeContextMenuItems(in viewController: UIViewController?, location: OCExtensionLocationIdentifier, with context: ClientContext?) -> [UIMenuElement]? +} diff --git a/ownCloudAppShared/Client/Data Item Interactions/OCDrive+Interactions.swift b/ownCloudAppShared/Client/Data Item Interactions/OCDrive+Interactions.swift new file mode 100644 index 000000000..64d3f9e69 --- /dev/null +++ b/ownCloudAppShared/Client/Data Item Interactions/OCDrive+Interactions.swift @@ -0,0 +1,42 @@ +// +// OCDrive+Interactions.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 30.05.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK +import ownCloudApp + +// MARK: - Selection > Open +extension OCDrive : DataItemSelectionInteraction { + public func openItem(in viewController: UIViewController?, with context: ClientContext?, animated: Bool, pushViewController: Bool, completion: ((Bool) -> Void)?) -> UIViewController? { + let driveContext = ClientContext(with: context, modifier: { context in + context.drive = self + }) + let query = OCQuery(for: self.rootLocation) + DisplaySettings.shared.updateQuery(withDisplaySettings: query) + + let rootFolderViewController = ClientItemViewController(context: driveContext, query: query) + + if pushViewController { + viewController?.navigationController?.pushViewController(rootFolderViewController, animated: animated) + } + + completion?(true) + + return rootFolderViewController + } +} diff --git a/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift b/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift new file mode 100644 index 000000000..3be3d5662 --- /dev/null +++ b/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift @@ -0,0 +1,125 @@ +// +// OCItem+Interactions.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 30.05.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK +import ownCloudApp + +// MARK: - Selection > Open +extension OCItem : DataItemSelectionInteraction { + public func openItem(in viewController: UIViewController?, with context: ClientContext?, animated: Bool, pushViewController: Bool, completion: ((Bool) -> Void)?) -> UIViewController? { + if let context = context, let core = context.core { + let item = self + + let activity = OpenItemUserActivity(detailItem: item, detailBookmark: core.bookmark) + viewController?.view.window?.windowScene?.userActivity = activity.openItemUserActivity + + switch item.type { + case .collection: + if let location = item.location { + let query = OCQuery(for: location) + DisplaySettings.shared.updateQuery(withDisplaySettings: query) + + let queryViewController = ClientItemViewController(context: context, query: query) + if pushViewController { + context.navigationController?.pushViewController(queryViewController, animated: animated) + } + + completion?(true) + + return queryViewController + } + + case .file: + if let viewController = context.viewItemHandler?.provideViewer(for: self, context: context) { + if pushViewController { + context.navigationController?.pushViewController(viewController, animated: animated) + } + + completion?(true) + + return viewController + } + } + } + + completion?(false) + + return nil + } +} + +// MARK: - Swipe +extension OCItem : DataItemSwipeInteraction { + public func provideTrailingSwipeActions(with context: ClientContext?) -> UISwipeActionsConfiguration? { + guard let context = context, let core = context.core, let originatingViewController = context.originatingViewController else { + return nil + } + + let actionsLocation = OCExtensionLocation(ofType: .action, identifier: .tableRow) + let actionContext = ActionContext(viewController: originatingViewController, core: core, items: [self], location: actionsLocation, sender: nil) + let actions = Action.sortedApplicableActions(for: actionContext) + + let contextualActions = actions.compactMap({ action in + return action.provideContextualAction() + }) + let configuration = UISwipeActionsConfiguration(actions: contextualActions) + return configuration + } +} + +// MARK: - Context Menu +extension OCItem : DataItemContextMenuInteraction { + public func composeContextMenuItems(in viewController: UIViewController?, location: OCExtensionLocationIdentifier, with context: ClientContext?) -> [UIMenuElement]? { + guard let core = context?.core, let viewController = viewController else { + return nil + } + let item = self + let actionsLocation = OCExtensionLocation(ofType: .action, identifier: location) // .contextMenuItem) + let actionContext = ActionContext(viewController: viewController, core: core, items: [item], location: actionsLocation, sender: nil) + let actions = Action.sortedApplicableActions(for: actionContext) + var actionMenuActions : [UIAction] = [] + for action in actions { + action.progressHandler = context?.actionProgressHandlerProvider?.makeActionProgressHandler() + + if let menuAction = action.provideUIMenuAction() { + actionMenuActions.append(menuAction) + } + } + + if core.connectionStatus == .online, core.connection.capabilities?.sharingAPIEnabled == 1, location == .contextMenuItem { + // Actions menu + let actionsMenu = UIMenu(title: "", identifier: UIMenu.Identifier("context"), options: .displayInline, children: actionMenuActions) + + // Share Items + let sharingActionsLocation = OCExtensionLocation(ofType: .action, identifier: .contextMenuSharingItem) + let sharingActionContext = ActionContext(viewController: viewController, core: core, items: [item], location: sharingActionsLocation, sender: nil) + let sharingActions = Action.sortedApplicableActions(for: sharingActionContext) + for action in sharingActions { + action.progressHandler = context?.actionProgressHandlerProvider?.makeActionProgressHandler() + } + + let sharingItems = sharingActions.compactMap({$0.provideUIMenuAction()}) + let shareMenu = UIMenu(title: "", identifier: UIMenu.Identifier("sharing"), options: .displayInline, children: sharingItems) + + return [shareMenu, actionsMenu] + } + + return actionMenuActions + } +} From 95a3bfd606f398fb28ee4c385b69bdce33d282e3 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Mon, 30 May 2022 22:47:58 +0200 Subject: [PATCH 039/328] - SortBarCell: WIP - ActionCell: move OCAction+DataItemSelectionInteraction into a separate source file and update it for the latest DataItemSelectionInteraction protocol version --- ownCloud.xcodeproj/project.pbxproj | 4 +++ .../Collection Views/Cells/ActionCell.swift | 8 ----- .../Collection Views/Cells/SortBarCell.swift | 23 ++++++++++++++ .../OCAction+Interactions.swift | 30 +++++++++++++++++++ 4 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 ownCloudAppShared/Client/Collection Views/Cells/SortBarCell.swift create mode 100644 ownCloudAppShared/Client/Data Item Interactions/OCAction+Interactions.swift diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 01d39d76d..1f91a292f 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -328,6 +328,7 @@ DC46F3CE2844A92A00038880 /* ActionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC46F3CD2844A92A00038880 /* ActionCell.swift */; }; DC46F3D1284546F000038880 /* OCItem+Interactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC46F3D0284546F000038880 /* OCItem+Interactions.swift */; }; DC46F3D32845583D00038880 /* OCDrive+Interactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC46F3D22845583D00038880 /* OCDrive+Interactions.swift */; }; + DC46F3D5284563BD00038880 /* OCAction+Interactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC46F3D4284563BD00038880 /* OCAction+Interactions.swift */; }; DC49B55928365C5F00DAF13B /* OCVault+VFSManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DC49B55728365C5F00DAF13B /* OCVault+VFSManager.h */; }; DC49B55A28365C5F00DAF13B /* OCVault+VFSManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DC49B55828365C5F00DAF13B /* OCVault+VFSManager.m */; }; DC4C575D233958B70098BAE9 /* FixedHeightImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4C575C233958B70098BAE9 /* FixedHeightImageView.swift */; }; @@ -1320,6 +1321,7 @@ DC46F3CD2844A92A00038880 /* ActionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionCell.swift; sourceTree = ""; }; DC46F3D0284546F000038880 /* OCItem+Interactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCItem+Interactions.swift"; sourceTree = ""; }; DC46F3D22845583D00038880 /* OCDrive+Interactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCDrive+Interactions.swift"; sourceTree = ""; }; + DC46F3D4284563BD00038880 /* OCAction+Interactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCAction+Interactions.swift"; sourceTree = ""; }; DC49B55728365C5F00DAF13B /* OCVault+VFSManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCVault+VFSManager.h"; sourceTree = ""; }; DC49B55828365C5F00DAF13B /* OCVault+VFSManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCVault+VFSManager.m"; sourceTree = ""; }; DC4C575C233958B70098BAE9 /* FixedHeightImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixedHeightImageView.swift; sourceTree = ""; }; @@ -2501,6 +2503,7 @@ DC46F3C62844A75200038880 /* OCDataItem+InteractionProtocols.swift */, DC46F3D0284546F000038880 /* OCItem+Interactions.swift */, DC46F3D22845583D00038880 /* OCDrive+Interactions.swift */, + DC46F3D4284563BD00038880 /* OCAction+Interactions.swift */, ); path = "Data Item Interactions"; sourceTree = ""; @@ -4367,6 +4370,7 @@ DCE4E44524C1A4260051722F /* FileListTableViewController.swift in Sources */, 399EA75A25E66DB000B6FF11 /* ClientItemResolvingCell.swift in Sources */, DCE4E43524C1999A0051722F /* Action.swift in Sources */, + DC46F3D5284563BD00038880 /* OCAction+Interactions.swift in Sources */, DCFC9ECC28002303005D9144 /* CollectionViewSection.swift in Sources */, DC3AB2462810602500789435 /* UILabel+Extension.swift in Sources */, DC0A356C24C0E42200FB58FC /* AppLockWindow.swift in Sources */, diff --git a/ownCloudAppShared/Client/Collection Views/Cells/ActionCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/ActionCell.swift index 5973da25e..4f7be8a8a 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/ActionCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/ActionCell.swift @@ -62,11 +62,3 @@ class ActionCell: ThemeableCollectionViewListCell { })) } } - -extension OCAction : DataItemSelectionInteraction { - public func handleSelection(in viewController: UIViewController?, with context: ClientContext?, completion: ((Bool) -> Void)?) { - run(options: nil, completionHandler: { error in - completion?(error == nil) - }) - } -} diff --git a/ownCloudAppShared/Client/Collection Views/Cells/SortBarCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/SortBarCell.swift new file mode 100644 index 000000000..54b5f7442 --- /dev/null +++ b/ownCloudAppShared/Client/Collection Views/Cells/SortBarCell.swift @@ -0,0 +1,23 @@ +// +// SortBarCell.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 30.05.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit + +class SortBarCell: ThemeableCollectionViewListCell { + +} diff --git a/ownCloudAppShared/Client/Data Item Interactions/OCAction+Interactions.swift b/ownCloudAppShared/Client/Data Item Interactions/OCAction+Interactions.swift new file mode 100644 index 000000000..f10d14bb1 --- /dev/null +++ b/ownCloudAppShared/Client/Data Item Interactions/OCAction+Interactions.swift @@ -0,0 +1,30 @@ +// +// OCAction+Interactions.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 30.05.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK + +extension OCAction : DataItemSelectionInteraction { + public func handleSelection(in viewController: UIViewController?, with context: ClientContext?, completion: ((Bool) -> Void)?) -> Bool { + run(options: nil, completionHandler: { error in + completion?(error == nil) + }) + + return true + } +} From c74d31608fe546ca80246a2247220ea4ccd8a3ad Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Fri, 10 Jun 2022 17:13:04 +0200 Subject: [PATCH 040/328] - CollectionView: - add ThemeableCollectionViewCell as regular twin of ThemeableCollectionViewListCell - add configuredConstraints property to make changing constraints simple - CollectionViewCellConfiguration: - add method .configureCell() to simplify fetching of items to configure cell contents - refactor existing implementations to remove duplicate code - CollectionViewController: - add variant to use stack view as root - add support to add and remove "stacked" child view controllers, enabling permanent messages, toolbars and more - fix retain cycle in UICollectionViewCompositionalLayout(sectionProvider:) - add new property .animateDifferences to disable animations on view controller level - add new method to retrieve the items at multiple index paths at once - add multi-select support, including new .multiselection interaction - CollectionViewSection: - extend CellLayout.list - fully implement CellLayout.fullWidth - add new CellLayout.sideways - allow fully custom layouts with CellLayout.custom - add new property .animateDifferences to disable animations on section level - ClientItemViewController: - add additional leadIn & trailing data sources for more flexible layout - support .loading, .empty and .hasContent ContentStates with distinct messages and different contents presented to the user - add SortBar and sorting support - add general-purpose actions bar mechanism - implement multi-select support using that mechanism - ClientContext: - add new ClientItemInteraction.multiSelection - add new item-unspecific hasPermission(for: interaction) method - SortBar: - show context menu instead of custom table view on top (disposing of SortMethodTableViewController) - remove segmented control "wide" alternative - BookmarkViewController: - ensure that, for token-based auth methods, the correct name of the auth method is shown (previously always "OAuth2") - use localized replacements for app and auth method name - Client Actions: - ensure the returned icons are rendered as templates - replace extension location .toolbar with .multiSelection - ActionContext: fix bug in remove(item:) where deleteableItems was used instead of moveableItems, producing wrong results when added and removing items - Action: add additional options to provideOCAction() for more fluid UI updates and additional completion handlers - ActionCell: support vertical and horizontal layout, base on ThemeableCollectionViewCell - OCViewHost: add OCViewHostContentStatus contentStatus to give a broad indication of what is shown - ViewCell: new cell type to embed arbitrary views - RoundedInfoView: update label text when setting .infoText - code hygiene: replace $0 with actual var names in many places, to make the code more readable - update SDK --- ios-sdk | 2 +- ownCloud.xcodeproj/project.pbxproj | 24 +- .../Bookmarks/BookmarkViewController.swift | 16 +- .../CollaborateAction.swift | 2 +- .../Actions+Extensions/CopyAction.swift | 6 +- .../Actions+Extensions/CutAction.swift | 6 +- .../Actions+Extensions/DeleteAction.swift | 6 +- .../Actions+Extensions/DuplicateAction.swift | 6 +- .../Actions+Extensions/FavoriteAction.swift | 2 +- .../ImportPasteboardAction.swift | 2 +- .../Actions+Extensions/LinksAction.swift | 2 +- .../MakeAvailableOfflineAction.swift | 2 +- .../MakeUnavailableOfflineAction.swift | 2 +- .../Actions+Extensions/MoveAction.swift | 6 +- .../Actions+Extensions/OpenInAction.swift | 4 +- .../PresentationModeAction.swift | 2 +- .../Actions+Extensions/UnfavoriteAction.swift | 2 +- .../Actions+Extensions/UnshareAction.swift | 6 +- .../Client/Actions/Scanner/ScanAction.swift | 2 +- .../Resources/en.lproj/Localizable.strings | 2 +- ownCloud/Tools/URL+Extensions.swift | 2 +- ownCloud/UI Elements/RoundedInfoView.swift | 9 +- .../View Providers/OCViewHost.h | 8 + .../View Providers/OCViewHost.m | 19 ++ ownCloudAppShared/Client/Actions/Action.swift | 23 +- .../Client/Actions/CreateFolderAction.swift | 2 +- .../Collection Views/Cells/ActionCell.swift | 185 ++++++++++-- .../Cells/DriveHeaderCell.swift | 12 +- .../Cells/DriveListCell.swift | 78 +---- .../Cells/ExpandableResourceCell.swift | 23 +- .../Collection Views/Cells/ItemListCell.swift | 133 ++++----- .../Cells/ThemeableCollectionViewCell.swift | 70 +++++ .../ThemeableCollectionViewListCell.swift | 14 +- .../Collection Views/Cells/ViewCell.swift | 48 ++++ .../CollectionViewCellConfiguration.swift | 27 ++ ...CellProvider+StandardImplementations.swift | 1 + .../CollectionViewController.swift | 179 +++++++++++- .../CollectionViewSection.swift | 101 ++++--- .../ClientItemViewController.swift | 271 +++++++++++++++++- .../Client/Context/ClientContext.swift | 17 +- .../OCItem+Interactions.swift | 2 +- .../ClientQueryViewController.swift | 2 +- .../QueryFileListTableViewController.swift | 3 +- .../Client/User Interface/SortBar.swift | 161 +++-------- .../SortMethodTableViewController.swift | 67 ----- .../String+Extension.swift | 4 +- .../SDK Extensions/OCItem+Extension.swift | 3 +- .../UIView+OCDataItem.swift} | 17 +- .../More/MoreStaticTableViewController.swift | 4 +- 49 files changed, 1066 insertions(+), 521 deletions(-) create mode 100644 ownCloudAppShared/Client/Collection Views/Cells/ThemeableCollectionViewCell.swift create mode 100644 ownCloudAppShared/Client/Collection Views/Cells/ViewCell.swift delete mode 100644 ownCloudAppShared/Client/User Interface/SortMethodTableViewController.swift rename ownCloudAppShared/{Client/Collection Views/Cells/SortBarCell.swift => UIKit Extension/UIView+OCDataItem.swift} (54%) diff --git a/ios-sdk b/ios-sdk index d84ed5290..ae461a00e 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit d84ed529045db10e9b5f73f60ea88bea47305f2f +Subproject commit ae461a00ef030ddd6a6be2d76702c85f9bc2fad0 diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 1f91a292f..c1618bfd2 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -211,7 +211,6 @@ DC080CF1238C8D850044C5D2 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC080CF0238C8D850044C5D2 /* StoreKit.framework */; }; DC080CF2238C8DF70044C5D2 /* OCLicenseAppStoreItem.h in Headers */ = {isa = PBXBuildFile; fileRef = DC080CE7238BD71F0044C5D2 /* OCLicenseAppStoreItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC080CF3238C92480044C5D2 /* OCLicenseAppStoreItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DC080CE8238BD71F0044C5D2 /* OCLicenseAppStoreItem.m */; }; - DC0A355224C0E2C200FB58FC /* SortMethodTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39DE75C722F960CF0064C1E2 /* SortMethodTableViewController.swift */; }; DC0A355324C0E2C200FB58FC /* ClientItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFED971208095E200A2D984 /* ClientItemCell.swift */; }; DC0A355424C0E2C200FB58FC /* SortBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FA23E520BFD3D8009A6D73 /* SortBar.swift */; }; DC0A355624C0E33A00FB58FC /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF4F18A2052BA4C00189B9A /* Log.swift */; }; @@ -324,13 +323,15 @@ DC4332012472E1B4002DC0E5 /* OCLicenseEMMProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DC4331FF2472E1B4002DC0E5 /* OCLicenseEMMProvider.m */; }; DC44343E21ABFA5200376B16 /* StaticLoginProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC44343D21ABFA5200376B16 /* StaticLoginProfile.swift */; }; DC46F3C72844A75200038880 /* OCDataItem+InteractionProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC46F3C62844A75200038880 /* OCDataItem+InteractionProtocols.swift */; }; - DC46F3CC2844A8EA00038880 /* SortBarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC46F3CB2844A8EA00038880 /* SortBarCell.swift */; }; + DC46F3CC2844A8EA00038880 /* ViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC46F3CB2844A8EA00038880 /* ViewCell.swift */; }; DC46F3CE2844A92A00038880 /* ActionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC46F3CD2844A92A00038880 /* ActionCell.swift */; }; DC46F3D1284546F000038880 /* OCItem+Interactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC46F3D0284546F000038880 /* OCItem+Interactions.swift */; }; DC46F3D32845583D00038880 /* OCDrive+Interactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC46F3D22845583D00038880 /* OCDrive+Interactions.swift */; }; DC46F3D5284563BD00038880 /* OCAction+Interactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC46F3D4284563BD00038880 /* OCAction+Interactions.swift */; }; + DC46F3D72845FCFA00038880 /* UIView+OCDataItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC46F3D62845FCFA00038880 /* UIView+OCDataItem.swift */; }; DC49B55928365C5F00DAF13B /* OCVault+VFSManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DC49B55728365C5F00DAF13B /* OCVault+VFSManager.h */; }; DC49B55A28365C5F00DAF13B /* OCVault+VFSManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DC49B55828365C5F00DAF13B /* OCVault+VFSManager.m */; }; + DC49C22128524D6C00BAA910 /* ThemeableCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC49C22028524D6C00BAA910 /* ThemeableCollectionViewCell.swift */; }; DC4C575D233958B70098BAE9 /* FixedHeightImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4C575C233958B70098BAE9 /* FixedHeightImageView.swift */; }; DC4D5A0A247C1398008ADDB6 /* MessageGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4D5A09247C1398008ADDB6 /* MessageGroup.swift */; }; DC4FEAE7209E3A7700D4476B /* OCIssue+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4FEAE6209E3A7700D4476B /* OCIssue+Extension.swift */; }; @@ -1094,7 +1095,6 @@ 39DC7CD225C2E1570001E08C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; 39DC7CD425C2E1570001E08C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39DC7D0825C30AAB0001E08C /* ownCloud_File_Provider_UI.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = ownCloud_File_Provider_UI.entitlements; sourceTree = ""; }; - 39DE75C722F960CF0064C1E2 /* SortMethodTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortMethodTableViewController.swift; sourceTree = ""; }; 39DF779224EA73B40066E8F0 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 39DF77AD24EA7F0A0066E8F0 /* sq */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sq; path = sq.lproj/Localizable.strings; sourceTree = ""; }; 39DF77AE24EA7F0C0066E8F0 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = ""; }; @@ -1317,13 +1317,15 @@ DC4434C321AC894700376B16 /* StaticLoginStepViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLoginStepViewController.swift; sourceTree = ""; }; DC4434C521AC898700376B16 /* StaticLoginSetupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLoginSetupViewController.swift; sourceTree = ""; }; DC46F3C62844A75200038880 /* OCDataItem+InteractionProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCDataItem+InteractionProtocols.swift"; sourceTree = ""; }; - DC46F3CB2844A8EA00038880 /* SortBarCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortBarCell.swift; sourceTree = ""; }; + DC46F3CB2844A8EA00038880 /* ViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewCell.swift; sourceTree = ""; }; DC46F3CD2844A92A00038880 /* ActionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionCell.swift; sourceTree = ""; }; DC46F3D0284546F000038880 /* OCItem+Interactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCItem+Interactions.swift"; sourceTree = ""; }; DC46F3D22845583D00038880 /* OCDrive+Interactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCDrive+Interactions.swift"; sourceTree = ""; }; DC46F3D4284563BD00038880 /* OCAction+Interactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCAction+Interactions.swift"; sourceTree = ""; }; + DC46F3D62845FCFA00038880 /* UIView+OCDataItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+OCDataItem.swift"; sourceTree = ""; }; DC49B55728365C5F00DAF13B /* OCVault+VFSManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCVault+VFSManager.h"; sourceTree = ""; }; DC49B55828365C5F00DAF13B /* OCVault+VFSManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCVault+VFSManager.m"; sourceTree = ""; }; + DC49C22028524D6C00BAA910 /* ThemeableCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeableCollectionViewCell.swift; sourceTree = ""; }; DC4C575C233958B70098BAE9 /* FixedHeightImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixedHeightImageView.swift; sourceTree = ""; }; DC4D5A09247C1398008ADDB6 /* MessageGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageGroup.swift; sourceTree = ""; }; DC4FEAE6209E3A7700D4476B /* OCIssue+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCIssue+Extension.swift"; sourceTree = ""; }; @@ -1979,6 +1981,7 @@ DC3AB23F280FFF2700789435 /* NSMutableAttributedString+AppendStyled.swift */, DC3AB24328104AA500789435 /* UIFont+Weight.swift */, DC3AB2452810602500789435 /* UILabel+Extension.swift */, + DC46F3D62845FCFA00038880 /* UIView+OCDataItem.swift */, ); path = "UIKit Extension"; sourceTree = ""; @@ -3024,7 +3027,6 @@ DCFED971208095E200A2D984 /* ClientItemCell.swift */, 39B289A7226F1EE000BE0E11 /* MessageView.swift */, 3912208123436EB80026C290 /* SortMethod.swift */, - 39DE75C722F960CF0064C1E2 /* SortMethodTableViewController.swift */, 23FA23E520BFD3D8009A6D73 /* SortBar.swift */, DCD864112811FC5700CA6631 /* GradientView.swift */, ); @@ -3134,14 +3136,15 @@ DCEAF0882808252300980B6D /* Cells */ = { isa = PBXGroup; children = ( - DC01AF2028411D8400903101 /* ThemeableCollectionViewListCell.swift */, + DC46F3CD2844A92A00038880 /* ActionCell.swift */, DC3AB2412810404000789435 /* DriveHeaderCell.swift */, DCEAF0892808254800980B6D /* DriveListCell.swift */, DC3AB2472810A10300789435 /* ExpandableResourceCell.swift */, DC3AB23D280FFE3400789435 /* ItemListCell.swift */, DC01AF1B28411C0B00903101 /* MessageCell.swift */, - DC46F3CB2844A8EA00038880 /* SortBarCell.swift */, - DC46F3CD2844A92A00038880 /* ActionCell.swift */, + DC49C22028524D6C00BAA910 /* ThemeableCollectionViewCell.swift */, + DC01AF2028411D8400903101 /* ThemeableCollectionViewListCell.swift */, + DC46F3CB2844A8EA00038880 /* ViewCell.swift */, ); path = Cells; sourceTree = ""; @@ -4368,6 +4371,7 @@ DC04FFC827F5B79000F22569 /* CollectionViewController.swift in Sources */, DC3AB2422810404000789435 /* DriveHeaderCell.swift in Sources */, DCE4E44524C1A4260051722F /* FileListTableViewController.swift in Sources */, + DC46F3D72845FCFA00038880 /* UIView+OCDataItem.swift in Sources */, 399EA75A25E66DB000B6FF11 /* ClientItemResolvingCell.swift in Sources */, DCE4E43524C1999A0051722F /* Action.swift in Sources */, DC46F3D5284563BD00038880 /* OCAction+Interactions.swift in Sources */, @@ -4468,14 +4472,14 @@ DC0A355824C0E35B00FB58FC /* Synchronized.swift in Sources */, DCA2EDE4279B1789001F04E6 /* ResourceItemIcon.swift in Sources */, 0287DD7D249131E000C912CA /* AppStatistics.swift in Sources */, + DC49C22128524D6C00BAA910 /* ThemeableCollectionViewCell.swift in Sources */, DC0A359924C0E6FE00FB58FC /* VendorServices.swift in Sources */, DC0A358324C0E44200FB58FC /* VectorImage.swift in Sources */, DC0A359824C0E68700FB58FC /* OCItem+AppExtension.swift in Sources */, - DC0A355224C0E2C200FB58FC /* SortMethodTableViewController.swift in Sources */, DC0A358424C0E44200FB58FC /* VectorImageView.swift in Sources */, DC0A355324C0E2C200FB58FC /* ClientItemCell.swift in Sources */, DC0A357D24C0E43C00FB58FC /* ThemeStyle.swift in Sources */, - DC46F3CC2844A8EA00038880 /* SortBarCell.swift in Sources */, + DC46F3CC2844A8EA00038880 /* ViewCell.swift in Sources */, DC0A357524C0E43200FB58FC /* ProgressView.swift in Sources */, 3912208223436EB80026C290 /* SortMethod.swift in Sources */, DCE4E43F24C19D370051722F /* UIAlertController+OCIssue.swift in Sources */, diff --git a/ownCloud/Bookmarks/BookmarkViewController.swift b/ownCloud/Bookmarks/BookmarkViewController.swift index 89b2489d1..fecc5cab9 100644 --- a/ownCloud/Bookmarks/BookmarkViewController.swift +++ b/ownCloud/Bookmarks/BookmarkViewController.swift @@ -40,7 +40,7 @@ class BookmarkViewController: StaticTableViewController { var passwordRow : StaticTableViewRow? var tokenInfoRow : StaticTableViewRow? var deleteAuthDataButtonRow : StaticTableViewRow? - var oAuthInfoView : RoundedInfoView? + var tokenInfoView : RoundedInfoView? var showOAuthInfoHeader = false var showedOAuthInfoHeader : Bool = false var activeTextField: UITextField? @@ -239,9 +239,7 @@ class BookmarkViewController: StaticTableViewController { credentialsSection = StaticTableViewSection(headerTitle: "Credentials".localized, footerTitle: nil, identifier: "section-credentials", rows: [ usernameRow!, passwordRow! ]) - var oAuthInfoText = "If you 'Continue', you will be prompted to allow the '%@' App to open OAuth2 login where you can enter your credentials.".localized - oAuthInfoText = oAuthInfoText.replacingOccurrences(of: "%@", with: VendorServices.shared.appName) - oAuthInfoView = RoundedInfoView(text: oAuthInfoText) + tokenInfoView = RoundedInfoView(text: "") // Input focus tracking urlRow?.textField?.delegate = self @@ -888,7 +886,15 @@ class BookmarkViewController: StaticTableViewController { } if showOAuthInfoHeader { - self.tableView.tableHeaderView = oAuthInfoView + var authMethodName = "OAuth2" + + if let authenticationMethodIdentifier = bookmark?.authenticationMethodIdentifier, let localizedAuthMethodName = OCAuthenticationMethod.localizedName(forAuthenticationMethodIdentifier: authenticationMethodIdentifier) { + authMethodName = localizedAuthMethodName + } + + tokenInfoView?.infoText = "If you 'Continue', you will be prompted to allow the '{{app.name}}' app to open the {{authmethodName}} login page where you can enter your credentials.".localized(["authmethodName" : authMethodName]) + + self.tableView.tableHeaderView = tokenInfoView self.tableView.layoutTableHeaderView() } else { self.tableView.tableHeaderView?.removeFromSuperview() diff --git a/ownCloud/Client/Actions/Actions+Extensions/CollaborateAction.swift b/ownCloud/Client/Actions/Actions+Extensions/CollaborateAction.swift index 9512cc342..54854012b 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/CollaborateAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/CollaborateAction.swift @@ -50,6 +50,6 @@ class CollaborateAction: Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - return UIImage(named: "group") + return UIImage(named: "group")?.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift b/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift index 6e16e3851..d4c7559bb 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift @@ -63,7 +63,7 @@ class CopyAction : Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.copy") } override class var category : ActionCategory? { return .normal } override class var name : String? { return "Copy".localized } - override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .moreFolder, .toolbar, .keyboardShortcut, .contextMenuItem] } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .moreFolder, .multiSelection, .keyboardShortcut, .contextMenuItem] } override class var keyCommand : String? { return "C" } override class var keyModifierFlags: UIKeyModifierFlags? { return [.command] } @@ -104,8 +104,8 @@ class CopyAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem { - return UIImage(named: "copy-file") + if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection { + return UIImage(named: "copy-file")?.withRenderingMode(.alwaysTemplate) } return nil diff --git a/ownCloud/Client/Actions/Actions+Extensions/CutAction.swift b/ownCloud/Client/Actions/Actions+Extensions/CutAction.swift index 62fcadc76..f33bff6d9 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/CutAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/CutAction.swift @@ -25,7 +25,7 @@ class CutAction : Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.cutpasteboard") } override class var category : ActionCategory? { return .normal } override class var name : String? { return "Cut".localized } - override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .moreFolder, .toolbar, .keyboardShortcut, .contextMenuItem] } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .moreFolder, .multiSelection, .keyboardShortcut, .contextMenuItem] } override class var keyCommand : String? { return "X" } override class var keyModifierFlags: UIKeyModifierFlags? { return [.command] } @@ -91,8 +91,8 @@ class CutAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem { - return UIImage(systemName: "scissors") + if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection { + return UIImage(systemName: "scissors")?.withRenderingMode(.alwaysTemplate) } return nil diff --git a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift index 915cddacc..84c97a397 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift @@ -23,7 +23,7 @@ class DeleteAction : Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.delete") } override class var category : ActionCategory? { return .destructive } override class var name : String? { return "Delete".localized } - override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .tableRow, .moreFolder, .toolbar, .keyboardShortcut, .contextMenuItem] } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .tableRow, .moreFolder, .multiSelection, .keyboardShortcut, .contextMenuItem] } override class var keyCommand : String? { return "\u{08}" } override class var keyModifierFlags: UIKeyModifierFlags? { return [.command] } @@ -97,8 +97,8 @@ class DeleteAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem { - return UIImage(named: "trash") + if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection { + return UIImage(named: "trash")?.withRenderingMode(.alwaysTemplate) } return nil diff --git a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift index 1bce438fd..ad363de6a 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift @@ -25,7 +25,7 @@ class DuplicateAction : Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.duplicate") } override class var category : ActionCategory? { return .normal } override class var name : String? { return "Duplicate".localized } - override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .moreFolder, .toolbar, .keyboardShortcut, .contextMenuItem] } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .moreFolder, .multiSelection, .keyboardShortcut, .contextMenuItem] } override class var keyCommand : String? { return "D" } override class var keyModifierFlags: UIKeyModifierFlags? { return [.command] } @@ -77,8 +77,8 @@ class DuplicateAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem { - return UIImage(named: "duplicate-file") + if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection { + return UIImage(named: "duplicate-file")?.withRenderingMode(.alwaysTemplate) } return nil diff --git a/ownCloud/Client/Actions/Actions+Extensions/FavoriteAction.swift b/ownCloud/Client/Actions/Actions+Extensions/FavoriteAction.swift index ce6fedd2d..4a2825d1a 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/FavoriteAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/FavoriteAction.swift @@ -56,7 +56,7 @@ class FavoriteAction : Action { override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { if location == .moreItem || location == .moreDetailItem || location == .contextMenuItem { - return UIImage(systemName: "star") + return UIImage(systemName: "star")?.withRenderingMode(.alwaysTemplate) } return nil diff --git a/ownCloud/Client/Actions/Actions+Extensions/ImportPasteboardAction.swift b/ownCloud/Client/Actions/Actions+Extensions/ImportPasteboardAction.swift index a29438918..4ee90ecdb 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/ImportPasteboardAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/ImportPasteboardAction.swift @@ -245,7 +245,7 @@ class ImportPasteboardAction : Action { override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { if location == .moreItem || location == .moreFolder { - return UIImage(systemName: "doc.on.clipboard") + return UIImage(systemName: "doc.on.clipboard")?.withRenderingMode(.alwaysTemplate) } return nil diff --git a/ownCloud/Client/Actions/Actions+Extensions/LinksAction.swift b/ownCloud/Client/Actions/Actions+Extensions/LinksAction.swift index 6d9c8c0d0..5ea9de37a 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/LinksAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/LinksAction.swift @@ -50,6 +50,6 @@ class LinksAction: Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - return UIImage(named: "link") + return UIImage(named: "link")?.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/MakeAvailableOfflineAction.swift b/ownCloud/Client/Actions/Actions+Extensions/MakeAvailableOfflineAction.swift index 3886fd4d6..a74557b50 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/MakeAvailableOfflineAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/MakeAvailableOfflineAction.swift @@ -65,7 +65,7 @@ class MakeAvailableOfflineAction: Action { override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem { - return UIImage(named: "available-offline") + return UIImage(named: "available-offline")?.withRenderingMode(.alwaysTemplate) } return nil diff --git a/ownCloud/Client/Actions/Actions+Extensions/MakeUnavailableOfflineAction.swift b/ownCloud/Client/Actions/Actions+Extensions/MakeUnavailableOfflineAction.swift index 093e661ec..edd917329 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/MakeUnavailableOfflineAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/MakeUnavailableOfflineAction.swift @@ -81,7 +81,7 @@ class MakeUnavailableOfflineAction: Action { override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem { - return UIImage(named: "available-offline") + return UIImage(named: "available-offline")?.withRenderingMode(.alwaysTemplate) } return nil diff --git a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift index 7a4554c74..0a582f3e3 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift @@ -23,7 +23,7 @@ class MoveAction : Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.move") } override class var category : ActionCategory? { return .normal } override class var name : String? { return "Move".localized } - override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .moreFolder, .toolbar, .keyboardShortcut, .contextMenuItem] } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .moreFolder, .multiSelection, .keyboardShortcut, .contextMenuItem] } override class var keyCommand : String? { return "V" } override class var keyModifierFlags: UIKeyModifierFlags? { return [.command, .alternate] } @@ -77,8 +77,8 @@ class MoveAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem { - return UIImage(named: "folder") + if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection { + return UIImage(named: "folder")?.withRenderingMode(.alwaysTemplate) } return nil diff --git a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift index 886655710..35152b617 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift @@ -23,7 +23,7 @@ class OpenInAction: Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.openin") } override class var category : ActionCategory? { return .normal } override class var name : String { return "Open in".localized } - override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .toolbar, .keyboardShortcut, .contextMenuItem] } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .multiSelection, .keyboardShortcut, .contextMenuItem] } override class var keyCommand : String? { return "O" } override class var keyModifierFlags: UIKeyModifierFlags? { return [.command] } @@ -169,7 +169,7 @@ class OpenInAction: Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem { + if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection { return UIImage(systemName: "square.and.arrow.up")?.withRenderingMode(.alwaysTemplate) } diff --git a/ownCloud/Client/Actions/Actions+Extensions/PresentationModeAction.swift b/ownCloud/Client/Actions/Actions+Extensions/PresentationModeAction.swift index 762dfbd3e..d3783b0db 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/PresentationModeAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/PresentationModeAction.swift @@ -82,7 +82,7 @@ class PresentationModeAction: Action { override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { if location == .moreDetailItem { - return UIImage(systemName: "tv") + return UIImage(systemName: "tv")?.withRenderingMode(.alwaysTemplate) } return nil diff --git a/ownCloud/Client/Actions/Actions+Extensions/UnfavoriteAction.swift b/ownCloud/Client/Actions/Actions+Extensions/UnfavoriteAction.swift index 5253989c9..0a777fc63 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/UnfavoriteAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/UnfavoriteAction.swift @@ -56,7 +56,7 @@ class UnfavoriteAction : Action { override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { if location == .moreItem || location == .moreDetailItem || location == .contextMenuItem { - return UIImage(named: "star") + return UIImage(named: "star")?.withRenderingMode(.alwaysTemplate) } return nil diff --git a/ownCloud/Client/Actions/Actions+Extensions/UnshareAction.swift b/ownCloud/Client/Actions/Actions+Extensions/UnshareAction.swift index c26e2370e..cbfb09b43 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/UnshareAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/UnshareAction.swift @@ -23,7 +23,7 @@ class UnshareAction : Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.unshare") } override class var category : ActionCategory? { return .destructive } override class var name : String? { return "Unshare".localized } - override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .tableRow, .moreFolder, .toolbar] } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .tableRow, .moreFolder, .multiSelection] } // MARK: - Extension matching override class func applicablePosition(forContext: ActionContext) -> ActionPosition { @@ -121,8 +121,8 @@ class UnshareAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder { - return UIImage(named: "trash") + if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .multiSelection { + return UIImage(named: "trash")?.withRenderingMode(.alwaysTemplate) } return nil diff --git a/ownCloud/Client/Actions/Scanner/ScanAction.swift b/ownCloud/Client/Actions/Scanner/ScanAction.swift index 5dec266bf..42bb80e60 100644 --- a/ownCloud/Client/Actions/Scanner/ScanAction.swift +++ b/ownCloud/Client/Actions/Scanner/ScanAction.swift @@ -99,7 +99,7 @@ class ScanAction: Action, VNDocumentCameraViewControllerDelegate { override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { if location == .folderAction || location == .emptyFolder { - return UIImage(systemName: "doc.text.viewfinder", withConfiguration: UIImage.SymbolConfiguration(pointSize: 26, weight: .regular)) + return UIImage(systemName: "doc.text.viewfinder", withConfiguration: UIImage.SymbolConfiguration(pointSize: 26, weight: .regular))?.withRenderingMode(.alwaysTemplate) } return nil diff --git a/ownCloud/Resources/en.lproj/Localizable.strings b/ownCloud/Resources/en.lproj/Localizable.strings index 5b6e79e6f..f7330dcab 100644 --- a/ownCloud/Resources/en.lproj/Localizable.strings +++ b/ownCloud/Resources/en.lproj/Localizable.strings @@ -48,7 +48,7 @@ "Certificate has issues.\nOpen 'Certificate Details' for more informations." = "Certificate has issues.\nOpen 'Certificate Details' for more informations."; "No issues found. Certificate passed validation." = "No issues found. Certificate passed validation."; "Certificate may have issues, but was accepted by user.\nOpen 'Certificate Details' for more informations." = "Certificate may have issues, but was accepted by user.\nOpen 'Certificate Details' for more informations."; -"If you 'Continue', you will be prompted to allow the '%@' App to open OAuth2 login where you can enter your credentials." = "If you 'Continue', you will be prompted to allow the '%@' App to open OAuth2 login where you can enter your credentials."; +"If you 'Continue', you will be prompted to allow the '{{app.name}}' app to open the {{authmethodName}} login page where you can enter your credentials." = "If you 'Continue', you will be prompted to allow the '{{app.name}}' app to open the {{authmethodName}} login page where you can enter your credentials."; "Error opening %@" = "Error opening %@"; diff --git a/ownCloud/Tools/URL+Extensions.swift b/ownCloud/Tools/URL+Extensions.swift index d80804855..7388d3ac2 100644 --- a/ownCloud/Tools/URL+Extensions.swift +++ b/ownCloud/Tools/URL+Extensions.swift @@ -86,7 +86,7 @@ extension URL { } // Find matching bookmarks - let bookmarks = OCBookmarkManager.shared.bookmarks.filter({$0.url?.host == self.host}) + let bookmarks = OCBookmarkManager.shared.bookmarks.filter({bookmark in bookmark.url?.host == self.host}) var matchedBookmark: OCBookmark? var foundItem: OCItem? diff --git a/ownCloud/UI Elements/RoundedInfoView.swift b/ownCloud/UI Elements/RoundedInfoView.swift index a18b579c5..72e8293b4 100644 --- a/ownCloud/UI Elements/RoundedInfoView.swift +++ b/ownCloud/UI Elements/RoundedInfoView.swift @@ -30,7 +30,11 @@ class RoundedInfoView: UIView, Themeable { let font : UIFont = UIFont.systemFont(ofSize: 14) // MARK: - Instance Variables - var infoText : String = "" + var infoText : String = "" { + didSet { + label.text = infoText + } + } var messageThemeApplierToken : ThemeApplierToken? var backgroundView = UIView() var label = UILabel() @@ -64,7 +68,6 @@ class RoundedInfoView: UIView, Themeable { label.textAlignment = .center label.font = font label.numberOfLines = 0 - label.text = infoText label.translatesAutoresizingMaskIntoConstraints = false self.addSubview(label) @@ -81,7 +84,7 @@ class RoundedInfoView: UIView, Themeable { label.bottomAnchor.constraint(equalTo: backgroundView.bottomAnchor, constant: -verticalLabelPadding), label.widthAnchor.constraint(greaterThanOrEqualToConstant: 0), label.heightAnchor.constraint(greaterThanOrEqualToConstant: 0) - ]) + ]) } // MARK: - Theme support diff --git a/ownCloudAppFramework/View Providers/OCViewHost.h b/ownCloudAppFramework/View Providers/OCViewHost.h index dec51d713..0a935916d 100644 --- a/ownCloudAppFramework/View Providers/OCViewHost.h +++ b/ownCloudAppFramework/View Providers/OCViewHost.h @@ -21,8 +21,16 @@ NS_ASSUME_NONNULL_BEGIN +typedef NS_ENUM(NSInteger, OCViewHostContentStatus) { + OCViewHostContentStatusNone, + OCViewHostContentStatusFallback, + OCViewHostContentStatusFromResource +}; + @interface OCViewHost : UIView +@property(readonly,nonatomic) OCViewHostContentStatus contentStatus; + @property(strong,nullable,nonatomic) OCViewProviderContext *viewProviderContext; @property(strong,nullable,nonatomic) OCResourceRequest *request; diff --git a/ownCloudAppFramework/View Providers/OCViewHost.m b/ownCloudAppFramework/View Providers/OCViewHost.m index e84521689..6bc064082 100644 --- a/ownCloudAppFramework/View Providers/OCViewHost.m +++ b/ownCloudAppFramework/View Providers/OCViewHost.m @@ -161,6 +161,8 @@ - (void)setHostedView:(UIView *)newView { if (newView != _hostedView) { + [self willChangeValueForKey:@"contentStatus"]; + [_hostedView removeFromSuperview]; _hostedView = newView; @@ -177,6 +179,8 @@ - (void)setHostedView:(UIView *)newView [newView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor] ]]; } + + [self didChangeValueForKey:@"contentStatus"]; } } @@ -234,4 +238,19 @@ - (void)reloadView [self updateView]; } +- (OCViewHostContentStatus)contentStatus +{ + if (_hostedView == nil) + { + return (OCViewHostContentStatusNone); + } + + if (_hostedView == _fallbackView) + { + return (OCViewHostContentStatusFallback); + } + + return (OCViewHostContentStatusFromResource); +} + @end diff --git a/ownCloudAppShared/Client/Actions/Action.swift b/ownCloudAppShared/Client/Actions/Action.swift index 05e99033d..d3b4d1cff 100644 --- a/ownCloudAppShared/Client/Actions/Action.swift +++ b/ownCloudAppShared/Client/Actions/Action.swift @@ -61,7 +61,7 @@ public extension OCExtensionLocationIdentifier { static let moreDetailItem: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("moreItem") //!< Present in "more" card view for a single item static let moreFolder: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("moreFolder") //!< Present in "more" options for a whole folder static let emptyFolder: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("emptyFolder") //!< Present in "more" options for a whole folder - static let toolbar: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("toolbar") //!< Present in a toolbar + static let multiSelection: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("multiSelection") //!< Present as action when selecting multiple items static let folderAction: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("folderAction") //!< Present in the alert sheet when the folder action bar button is pressed static let keyboardShortcut: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("keyboardShortcut") //!< Currently used for UIKeyCommand static let contextMenuItem: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("contextMenuItem") //!< Used in UIMenu @@ -183,10 +183,10 @@ public class ActionContext: OCExtensionContext { guard self.itemStorage.contains(item) else { return } - self.itemStorage.removeAll(where: {$0.localID == item.localID}) + self.itemStorage.removeAll(where: { storedItem in storedItem.localID == item.localID}) if item.isSharedWithUser { - self.cachedSharedItems.removeAll(where: { $0.localID == item.localID }) + self.cachedSharedItems.removeAll(where: { cachedSharedItem in cachedSharedItem.localID == item.localID }) } if item.isRoot, rootItems > 0 { @@ -197,7 +197,7 @@ public class ActionContext: OCExtensionContext { deleteableItems -= 1 } - if item.permissions.contains(.move), deleteableItems > 0 { + if item.permissions.contains(.move), moveableItems > 0 { moveableItems -= 1 } } @@ -255,9 +255,9 @@ public class ActionContext: OCExtensionContext { private func updateCaches() { cachedSharedItems = itemStorage.sharedWithUser - rootItems = itemStorage.filter({ $0.isRoot }).count - deleteableItems = itemStorage.filter({$0.permissions.contains(.delete)}).count - moveableItems = itemStorage.filter({$0.permissions.contains(.move)}).count + rootItems = itemStorage.filter({ item in item.isRoot }).count + deleteableItems = itemStorage.filter({ item in item.permissions.contains(.delete)}).count + moveableItems = itemStorage.filter({ item in item.permissions.contains(.move)}).count } } @@ -476,7 +476,7 @@ open class Action : NSObject { return alertAction } - open func provideOCAction() -> OCAction? { + open func provideOCAction(singleVersion: Bool = false, with additionalCompletionHandler: (() -> Void)? = nil) -> OCAction? { let icon = self.icon?.paddedTo(width: 36, height: nil) var name = actionExtension.name @@ -487,8 +487,15 @@ open class Action : NSObject { let ocAction = OCAction(title: name, icon: icon, action: { _, options, completionHandler in self.perform() completionHandler(nil) + additionalCompletionHandler?() }) + ocAction.identifier = actionExtension.identifier.rawValue + if singleVersion { + ocAction.version = actionExtension.identifier.rawValue + } + ocAction.type = (actionExtension.category == .destructive) ? .destructive : .regular + return ocAction } diff --git a/ownCloudAppShared/Client/Actions/CreateFolderAction.swift b/ownCloudAppShared/Client/Actions/CreateFolderAction.swift index bf555ff85..e60184ce4 100644 --- a/ownCloudAppShared/Client/Actions/CreateFolderAction.swift +++ b/ownCloudAppShared/Client/Actions/CreateFolderAction.swift @@ -106,7 +106,7 @@ open class CreateFolderAction : Action { } override open class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .toolbar || location == .folderAction || location == .contextMenuItem || location == .emptyFolder { + if location == .multiSelection || location == .folderAction || location == .contextMenuItem || location == .emptyFolder || location == .multiSelection { return Theme.shared.image(for: "folder-create", size: CGSize(width: 30.0, height: 30.0))!.withRenderingMode(.alwaysTemplate) } diff --git a/ownCloudAppShared/Client/Collection Views/Cells/ActionCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/ActionCell.swift index 4f7be8a8a..a02910c72 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/ActionCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/ActionCell.swift @@ -19,46 +19,169 @@ import UIKit import ownCloudSDK -class ActionCell: ThemeableCollectionViewListCell { - static func registerCellProvider() { - let actionCellRegistration = UICollectionView.CellRegistration { (cell, indexPath, collectionItemRef) in - var content = cell.defaultContentConfiguration() +class ActionCell: ThemeableCollectionViewCell { + public enum Style : CaseIterable { + case vertical + case horizontal + } - if let cellConfiguration = collectionItemRef.ocCellConfiguration { - var itemRecord = cellConfiguration.record + override init(frame: CGRect) { + super.init(frame: frame) + configure() + configureLayout() + } - if itemRecord == nil { - if let collectionViewController = cellConfiguration.hostViewController { - let (itemRef, _) = collectionViewController.unwrap(collectionItemRef) + required init?(coder: NSCoder) { + fatalError() + } - if let retrievedItemRecord = try? cellConfiguration.source?.record(forItemRef: itemRef) { - itemRecord = retrievedItemRecord - } - } - } + let iconView = UIImageView() + let titleLabel = UILabel() - if let itemRecord = itemRecord { - if let item = itemRecord.item { - if let action = OCDataRenderer.default.renderItem(item, asType: .action, error: nil, withOptions: nil) as? OCAction { - content.text = action.title - content.image = action.icon - } - } else { - // Request reconfiguration of cell - itemRecord.retrieveItem(completionHandler: { error, itemRecord in - if let collectionViewController = cellConfiguration.hostViewController { - collectionViewController.collectionViewDataSource.requestReconfigurationOfItems([collectionItemRef]) - } - }) - } - } + var iconInsets : UIEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 0, right: 5) + var titleInsets : UIEdgeInsets = UIEdgeInsets(top: 5, left: 3, bottom: 5, right: 3) + + var title : String? { + didSet { + titleLabel.text = title + } + } + var icon : UIImage? { + didSet { + iconView.image = icon + } + } + var type : OCActionType = .regular { + didSet { + if superview != nil { + applyThemeCollectionToCellContents(theme: Theme.shared, collection: Theme.shared.activeCollection, state: .normal) + } + } + } + var style: Style = .vertical { + didSet { + if oldValue != style { + configureLayout() } + } + } + + func configure() { + iconView.translatesAutoresizingMaskIntoConstraints = false + titleLabel.translatesAutoresizingMaskIntoConstraints = false + + contentView.addSubview(titleLabel) + contentView.addSubview(iconView) + + iconView.image = icon + iconView.contentMode = .scaleAspectFit + + titleLabel.setContentHuggingPriority(.required, for: .vertical) +// titleLabel.setContentHuggingPriority(.required, for: .horizontal) + + titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal) + titleLabel.lineBreakMode = .byWordWrapping + titleLabel.numberOfLines = 1 + + iconView.setContentHuggingPriority(.required, for: .horizontal) + + selectedBackgroundView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 10)) + } + + func configureLayout() { + switch style { + case .vertical: + iconInsets = UIEdgeInsets(top: 6, left: 7, bottom: 0, right: 7) + titleInsets = UIEdgeInsets(top: 5, left: 7, bottom: 6, right: 7) + + titleLabel.textAlignment = .center - cell.contentConfiguration = content + titleLabel.font = UIFont.systemFont(ofSize: 10) + + self.configuredConstraints = [ + iconView.leadingAnchor.constraint(greaterThanOrEqualTo: contentView.leadingAnchor, constant: iconInsets.left), + iconView.trailingAnchor.constraint(lessThanOrEqualTo: contentView.trailingAnchor, constant: -iconInsets.right), + iconView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), + iconView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: iconInsets.top), + iconView.bottomAnchor.constraint(equalTo: titleLabel.topAnchor, constant: -(iconInsets.bottom + titleInsets.top)), + + titleLabel.leadingAnchor.constraint(greaterThanOrEqualTo: contentView.leadingAnchor, constant: titleInsets.left), + titleLabel.trailingAnchor.constraint(lessThanOrEqualTo: contentView.trailingAnchor, constant: -titleInsets.right), + titleLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), + titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -titleInsets.bottom) + ] + + case .horizontal: + iconInsets = UIEdgeInsets(top: 8, left: 10, bottom: 8, right: 5) + titleInsets = UIEdgeInsets(top: 13, left: 3, bottom: 13, right: 10) + + titleLabel.textAlignment = .left + + titleLabel.font = UIFont.preferredFont(forTextStyle: .body) + titleLabel.adjustsFontForContentSizeCategory = true + + self.configuredConstraints = [ + iconView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: iconInsets.left), + iconView.trailingAnchor.constraint(equalTo: titleLabel.leadingAnchor, constant: -(iconInsets.right + titleInsets.left)), + iconView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: iconInsets.top), + iconView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -iconInsets.bottom), + + iconView.widthAnchor.constraint(equalTo: iconView.heightAnchor), + + titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -titleInsets.right), + titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: titleInsets.top), + titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -titleInsets.bottom) + ] + } + } + + override func applyThemeCollectionToCellContents(theme: Theme, collection: ThemeCollection, state: ThemeItemState) { + super.applyThemeCollectionToCellContents(theme: theme, collection: collection, state: state) + + titleLabel.textColor = (type == .destructive) ? collection.destructiveColors.normal.foreground : collection.tintColor + iconView.tintColor = (type == .destructive) ? collection.destructiveColors.normal.foreground : collection.tintColor + + backgroundColor = (type == .destructive) ? collection.destructiveColors.normal.background : UIColor(white: 0, alpha: 0.05) + selectedBackgroundView?.backgroundColor = (type == .destructive) ? collection.destructiveColors.highlighted.background : UIColor(white: 0, alpha: 0.10) + + layer.cornerRadius = 8 + selectedBackgroundView?.layer.cornerRadius = 8 + } +} + +extension ActionCell { + static func registerCellProvider() { + let wideActionCellRegistration = UICollectionView.CellRegistration { (cell, indexPath, collectionItemRef) in + collectionItemRef.ocCellConfiguration?.configureCell(for: collectionItemRef, with: { itemRecord, item, cellConfiguration in + if let action = OCDataRenderer.default.renderItem(item, asType: .action, error: nil, withOptions: nil) as? OCAction { + cell.style = .horizontal + cell.title = action.title + cell.icon = action.icon + cell.type = action.type + } + }) + } + + let gridActionCellRegistration = UICollectionView.CellRegistration { (cell, indexPath, collectionItemRef) in + collectionItemRef.ocCellConfiguration?.configureCell(for: collectionItemRef, with: { itemRecord, item, cellConfiguration in + if let action = OCDataRenderer.default.renderItem(item, asType: .action, error: nil, withOptions: nil) as? OCAction { + cell.style = .vertical + cell.title = action.title + cell.icon = action.icon + cell.type = action.type + } + }) } CollectionViewCellProvider.register(CollectionViewCellProvider(for: .action, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in - return collectionView.dequeueConfiguredReusableCell(using: actionCellRegistration, for: indexPath, item: itemRef) + switch cellConfiguration?.style { + case .gridCell: + return collectionView.dequeueConfiguredReusableCell(using: gridActionCellRegistration, for: indexPath, item: itemRef) + + default: + return collectionView.dequeueConfiguredReusableCell(using: wideActionCellRegistration, for: indexPath, item: itemRef) + } + })) } } diff --git a/ownCloudAppShared/Client/Collection Views/Cells/DriveHeaderCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/DriveHeaderCell.swift index 1cc3df37e..59e0dcdd3 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/DriveHeaderCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/DriveHeaderCell.swift @@ -21,10 +21,11 @@ import UIKit class DriveHeaderCell: DriveListCell { let darkBackgroundView = UIView() - weak var collectionViewController : CollectionViewController? + var coverObservation : NSKeyValueObservation? deinit { Theme.shared.unregister(client: self) + coverObservation?.invalidate() } override func configure() { @@ -41,18 +42,25 @@ class DriveHeaderCell: DriveListCell { subtitleLabel.font = UIFont.preferredFont(forTextStyle: .headline, with: .semibold) subtitleLabel.makeLabelWrapText() - textOuterSpacing = 20 + textOuterSpacing = 16 coverImageResourceView.fallbackView = nil contentView.insertSubview(darkBackgroundView, belowSubview: titleLabel) Theme.shared.register(client: self, applyImmediately: true) + + coverObservation = coverImageResourceView.observe(\ResourceViewHost.contentStatus, options: [.initial], changeHandler: { [weak self] viewHost, _ in + self?.darkBackgroundView.isHidden = (viewHost.contentStatus != .fromResource) + }) } override func applyThemeCollectionToCellContents(theme: Theme, collection: ThemeCollection, state: ThemeItemState) { coverImageResourceView.backgroundColor = collection.lightBrandColor + // Different look (unified with navigation bar, problematic in light mode): + // coverImageResourceView.backgroundColor = collection.navigationBarColors.backgroundColor + titleLabel.textColor = .white subtitleLabel.textColor = .white } diff --git a/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift index 5e71d9fc6..ccaac9a69 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift @@ -128,40 +128,16 @@ extension DriveListCell { var title : String? var subtitle : String? - if let cellConfiguration = collectionItemRef.ocCellConfiguration { - var itemRecord = cellConfiguration.record + collectionItemRef.ocCellConfiguration?.configureCell(for: collectionItemRef, with: { itemRecord, item, cellConfiguration in + if let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil, withOptions: nil) as? OCDataItemPresentable { + title = presentable.title + subtitle = presentable.subtitle - resourceManager = cellConfiguration.core?.vault.resourceManager + resourceManager = cellConfiguration.core?.vault.resourceManager - if itemRecord == nil { - if let collectionViewController = cellConfiguration.hostViewController { - let (itemRef, _) = collectionViewController.unwrap(collectionItemRef) - - if let retrievedItemRecord = try? cellConfiguration.source?.record(forItemRef: itemRef) { - itemRecord = retrievedItemRecord - } - } + coverImageRequest = try? presentable.provideResourceRequest(.coverImage, withOptions: nil) } - - if let itemRecord = itemRecord { - if let item = itemRecord.item { - if let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil, withOptions: nil) as? OCDataItemPresentable { - - title = presentable.title - subtitle = presentable.subtitle - - coverImageRequest = try? presentable.provideResourceRequest(.coverImage, withOptions: nil) - } - } else { - // Request reconfiguration of cell - itemRecord.retrieveItem(completionHandler: { error, itemRecord in - if let collectionViewController = cellConfiguration.hostViewController { - collectionViewController.collectionViewDataSource.requestReconfigurationOfItems([collectionItemRef]) - } - }) - } - } - } + }) cell.title = title cell.subtitle = subtitle @@ -181,42 +157,16 @@ extension DriveListCell { var title : String? var subtitle : String? - if let cellConfiguration = collectionItemRef.ocCellConfiguration { - var itemRecord = cellConfiguration.record - - cell.collectionViewController = cellConfiguration.hostViewController + collectionItemRef.ocCellConfiguration?.configureCell(for: collectionItemRef, with: { itemRecord, item, cellConfiguration in + if let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil, withOptions: nil) as? OCDataItemPresentable { + title = presentable.title + subtitle = presentable.subtitle - resourceManager = cellConfiguration.core?.vault.resourceManager + resourceManager = cellConfiguration.core?.vault.resourceManager - if itemRecord == nil { - if let collectionViewController = cellConfiguration.hostViewController { - let (itemRef, _) = collectionViewController.unwrap(collectionItemRef) - - if let retrievedItemRecord = try? cellConfiguration.source?.record(forItemRef: itemRef) { - itemRecord = retrievedItemRecord - } - } - } - - if let itemRecord = itemRecord { - if let item = itemRecord.item { - if let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil, withOptions: nil) as? OCDataItemPresentable { - - title = presentable.title - subtitle = presentable.subtitle - - coverImageRequest = try? presentable.provideResourceRequest(.coverImage, withOptions: nil) - } - } else { - // Request reconfiguration of cell - itemRecord.retrieveItem(completionHandler: { error, itemRecord in - if let collectionViewController = cellConfiguration.hostViewController { - collectionViewController.collectionViewDataSource.requestReconfigurationOfItems([collectionItemRef]) - } - }) - } + coverImageRequest = try? presentable.provideResourceRequest(.coverImage, withOptions: nil) } - } + }) cell.title = title cell.subtitle = subtitle diff --git a/ownCloudAppShared/Client/Collection Views/Cells/ExpandableResourceCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/ExpandableResourceCell.swift index 83d004ae0..375903e7f 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/ExpandableResourceCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/ExpandableResourceCell.swift @@ -156,24 +156,13 @@ class ExpandableResourceCell: UICollectionViewListCell, Themeable { extension ExpandableResourceCell { static func registerCellProvider() { let itemListCellRegistration = UICollectionView.CellRegistration { (cell, indexPath, collectionItemRef) in - if let cellConfiguration = collectionItemRef.ocCellConfiguration { - if let itemRecord = cellConfiguration.record { - if let item = itemRecord.item { - if let textResource = item as? OCResource { - cell.resource = textResource - cell.collectionViewController = cellConfiguration.hostViewController - cell.collectionItemRef = collectionItemRef - } - } else { - // Request reconfiguration of cell - itemRecord.retrieveItem(completionHandler: { error, itemRecord in - if let collectionViewController = cellConfiguration.hostViewController { - collectionViewController.collectionViewDataSource.requestReconfigurationOfItems([collectionItemRef]) - } - }) - } + collectionItemRef.ocCellConfiguration?.configureCell(for: collectionItemRef, with: { itemRecord, item, cellConfiguration in + if let textResource = item as? OCResource { + cell.resource = textResource + cell.collectionViewController = cellConfiguration.hostViewController + cell.collectionItemRef = collectionItemRef } - } + }) } CollectionViewCellProvider.register(CollectionViewCellProvider(for: .textResource, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in diff --git a/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift index 984f97d22..0dfea4a26 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift @@ -47,6 +47,8 @@ open class ItemListCell: ThemeableCollectionViewListCell { open var cloudStatusIconView : UIImageView = UIImageView() open var sharedStatusIconView : UIImageView = UIImageView() open var publicLinkStatusIconView : UIImageView = UIImageView() + + open var actionViewContainer : UIView = UIView() open var moreButton : UIButton = UIButton() open var messageButton : UIButton = UIButton() open var revealButton : UIButton = UIButton() @@ -124,19 +126,14 @@ open class ItemListCell: ThemeableCollectionViewListCell { } func prepareViewAndConstraints() { - titleLabel.translatesAutoresizingMaskIntoConstraints = false + // cell.content setup + titleLabel.translatesAutoresizingMaskIntoConstraints = false detailLabel.translatesAutoresizingMaskIntoConstraints = false iconView.translatesAutoresizingMaskIntoConstraints = false iconView.contentMode = .scaleAspectFit - moreButton.translatesAutoresizingMaskIntoConstraints = false - - revealButton.translatesAutoresizingMaskIntoConstraints = false - - messageButton.translatesAutoresizingMaskIntoConstraints = false - cloudStatusIconView.translatesAutoresizingMaskIntoConstraints = false cloudStatusIconView.contentMode = .center cloudStatusIconView.contentMode = .scaleAspectFit @@ -162,28 +159,6 @@ open class ItemListCell: ThemeableCollectionViewListCell { self.contentView.addSubview(sharedStatusIconView) self.contentView.addSubview(publicLinkStatusIconView) self.contentView.addSubview(cloudStatusIconView) - self.contentView.addSubview(moreButton) - self.contentView.addSubview(revealButton) - self.contentView.addSubview(messageButton) - - moreButton.setImage(UIImage(named: "more-dots"), for: .normal) - moreButton.contentMode = .center - moreButton.isPointerInteractionEnabled = true - - revealButton.setImage(UIImage(systemName: "arrow.right.circle.fill"), for: .normal) - revealButton.isPointerInteractionEnabled = true - revealButton.contentMode = .center - revealButton.isHidden = !showRevealButton - revealButton.accessibilityLabel = "Reveal in folder".localized - - messageButton.setTitle("⚠️", for: .normal) - messageButton.contentMode = .center - messageButton.isPointerInteractionEnabled = true - messageButton.isHidden = true - - moreButton.addTarget(self, action: #selector(moreButtonTapped), for: .touchUpInside) - revealButton.addTarget(self, action: #selector(revealButtonTapped), for: .touchUpInside) - messageButton.addTarget(self, action: #selector(messageButtonTapped), for: .touchUpInside) sharedStatusIconView.setContentHuggingPriority(.required, for: .vertical) sharedStatusIconView.setContentHuggingPriority(.required, for: .horizontal) @@ -205,9 +180,6 @@ open class ItemListCell: ThemeableCollectionViewListCell { titleLabel.setContentCompressionResistancePriority(.defaultHigh, for: .vertical) detailLabel.setContentCompressionResistancePriority(.defaultHigh, for: .vertical) - moreButtonWidthConstraint = moreButton.widthAnchor.constraint(equalToConstant: showRevealButton ? revealButtonWidth : moreButtonWidth) - revealButtonWidthConstraint = revealButton.widthAnchor.constraint(equalToConstant: showRevealButton ? revealButtonWidth : 0) - cloudStatusIconViewZeroWidthConstraint = cloudStatusIconView.widthAnchor.constraint(equalToConstant: 0) sharedStatusIconViewZeroWidthConstraint = sharedStatusIconView.widthAnchor.constraint(equalToConstant: 0) publicLinkStatusIconViewZeroWidthConstraint = publicLinkStatusIconView.widthAnchor.constraint(equalToConstant: 0) @@ -223,8 +195,8 @@ open class ItemListCell: ThemeableCollectionViewListCell { iconView.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: verticalIconMargin), iconView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -verticalIconMargin), - titleLabel.trailingAnchor.constraint(equalTo: moreButton.leadingAnchor, constant: 0), - detailLabel.trailingAnchor.constraint(equalTo: moreButton.leadingAnchor, constant: 0), + titleLabel.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: 0), + detailLabel.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: 0), cloudStatusIconViewZeroWidthConstraint!, sharedStatusIconViewZeroWidthConstraint!, @@ -246,25 +218,60 @@ open class ItemListCell: ThemeableCollectionViewListCell { cloudStatusIconView.heightAnchor.constraint(equalToConstant: detailIconViewHeight), sharedStatusIconView.heightAnchor.constraint(equalToConstant: detailIconViewHeight), - publicLinkStatusIconView.heightAnchor.constraint(equalToConstant: detailIconViewHeight), + publicLinkStatusIconView.heightAnchor.constraint(equalToConstant: detailIconViewHeight) + ]) - moreButton.topAnchor.constraint(equalTo: self.contentView.topAnchor), - moreButton.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor), - moreButtonWidthConstraint!, + // actionViewContainer setup + moreButton.translatesAutoresizingMaskIntoConstraints = false + revealButton.translatesAutoresizingMaskIntoConstraints = false + messageButton.translatesAutoresizingMaskIntoConstraints = false + + actionViewContainer.addSubview(moreButton) + actionViewContainer.addSubview(revealButton) + actionViewContainer.addSubview(messageButton) + + moreButton.setImage(UIImage(named: "more-dots"), for: .normal) + moreButton.contentMode = .center + moreButton.isPointerInteractionEnabled = true + + revealButton.setImage(UIImage(systemName: "arrow.right.circle.fill"), for: .normal) + revealButton.isPointerInteractionEnabled = true + revealButton.contentMode = .center + revealButton.isHidden = !showRevealButton + revealButton.accessibilityLabel = "Reveal in folder".localized + + messageButton.setTitle("⚠️", for: .normal) + messageButton.contentMode = .center + messageButton.isPointerInteractionEnabled = true + messageButton.isHidden = true + + moreButton.addTarget(self, action: #selector(moreButtonTapped), for: .touchUpInside) + revealButton.addTarget(self, action: #selector(revealButtonTapped), for: .touchUpInside) + messageButton.addTarget(self, action: #selector(messageButtonTapped), for: .touchUpInside) + + moreButtonWidthConstraint = moreButton.widthAnchor.constraint(equalToConstant: showRevealButton ? revealButtonWidth : moreButtonWidth) + revealButtonWidthConstraint = revealButton.widthAnchor.constraint(equalToConstant: showRevealButton ? revealButtonWidth : 0) + + NSLayoutConstraint.activate([ + moreButton.topAnchor.constraint(equalTo: actionViewContainer.topAnchor), + moreButton.bottomAnchor.constraint(equalTo: actionViewContainer.bottomAnchor), + moreButton.leadingAnchor.constraint(equalTo: actionViewContainer.leadingAnchor), moreButton.trailingAnchor.constraint(equalTo: revealButton.leadingAnchor), - revealButton.topAnchor.constraint(equalTo: self.contentView.topAnchor), - revealButton.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor), + revealButton.topAnchor.constraint(equalTo: actionViewContainer.topAnchor), + revealButton.bottomAnchor.constraint(equalTo: actionViewContainer.bottomAnchor), + revealButton.trailingAnchor.constraint(equalTo: actionViewContainer.trailingAnchor), + + moreButtonWidthConstraint!, revealButtonWidthConstraint!, - revealButton.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor), - messageButton.leadingAnchor.constraint(equalTo: moreButton.leadingAnchor), - messageButton.trailingAnchor.constraint(equalTo: moreButton.trailingAnchor), messageButton.topAnchor.constraint(equalTo: moreButton.topAnchor), - messageButton.bottomAnchor.constraint(equalTo: moreButton.bottomAnchor) + messageButton.bottomAnchor.constraint(equalTo: moreButton.bottomAnchor), + messageButton.leadingAnchor.constraint(equalTo: moreButton.leadingAnchor), + messageButton.trailingAnchor.constraint(equalTo: moreButton.trailingAnchor) ]) - self.accessibilityElements = [titleLabel, detailLabel, moreButton, revealButton] + self.accessibilityElements = [titleLabel, detailLabel, moreButton, revealButton, messageButton] } // MARK: - Present item @@ -475,7 +482,7 @@ open class ItemListCell: ThemeableCollectionViewListCell { progressView.contentMode = .center progressView.translatesAutoresizingMaskIntoConstraints = false - self.contentView.addSubview(progressView) + actionViewContainer.addSubview(progressView) NSLayoutConstraint.activate([ progressView.leftAnchor.constraint(equalTo: moreButton.leftAnchor), @@ -611,37 +618,19 @@ open class ItemListCell: ThemeableCollectionViewListCell { extension ItemListCell { static func registerCellProvider() { let itemListCellRegistration = UICollectionView.CellRegistration { (cell, indexPath, collectionItemRef) in - if let cellConfiguration = collectionItemRef.ocCellConfiguration { - var itemRecord = cellConfiguration.record - + collectionItemRef.ocCellConfiguration?.configureCell(for: collectionItemRef, with: { itemRecord, item, cellConfiguration in cell.clientContext = cellConfiguration.clientContext cell.core = cellConfiguration.core - if itemRecord == nil { - if let collectionViewController = cellConfiguration.hostViewController { - let (itemRef, _) = collectionViewController.unwrap(collectionItemRef) - - if let retrievedItemRecord = try? cellConfiguration.source?.record(forItemRef: itemRef) { - itemRecord = retrievedItemRecord - } - } + if let ocItem = item as? OCItem { + cell.item = ocItem } - if let itemRecord = itemRecord { - if let item = itemRecord.item { - if let ocItem = item as? OCItem { - cell.item = ocItem - } - } else { - // Request reconfiguration of cell - itemRecord.retrieveItem(completionHandler: { error, itemRecord in - if let collectionViewController = cellConfiguration.hostViewController { - collectionViewController.collectionViewDataSource.requestReconfigurationOfItems([collectionItemRef]) - } - }) - } - } - } + cell.accessories = [ + .multiselect(), + .customView(configuration: UICellAccessory.CustomViewConfiguration(customView: cell.actionViewContainer /* UIButton(configuration: .borderedTinted()) */, placement: .trailing(displayed: .whenNotEditing))) + ] + }) } CollectionViewCellProvider.register(CollectionViewCellProvider(for: .item, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in diff --git a/ownCloudAppShared/Client/Collection Views/Cells/ThemeableCollectionViewCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/ThemeableCollectionViewCell.swift new file mode 100644 index 000000000..4fe651ae8 --- /dev/null +++ b/ownCloudAppShared/Client/Collection Views/Cells/ThemeableCollectionViewCell.swift @@ -0,0 +1,70 @@ +// +// ThemeableCollectionViewCell.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 09.06.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit + +class ThemeableCollectionViewCell: UICollectionViewCell, Themeable { + private var themeRegistered : Bool = false + public var updateLabelColors : Bool = true + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + deinit { + if themeRegistered { + Theme.shared.unregister(client: self) + } + } + + open override func willMove(toSuperview newSuperview: UIView?) { + super.willMove(toSuperview: newSuperview) + + if !themeRegistered { + // Postpone registration with theme until we actually need to. Makes sure self.applyThemeCollection() can take all properties into account + Theme.shared.register(client: self, applyImmediately: true) + themeRegistered = true + } + } + + public var configuredConstraints : [NSLayoutConstraint]? { + willSet { + if let configuredConstraints = configuredConstraints { + NSLayoutConstraint.deactivate(configuredConstraints) + } + } + didSet { + if let configuredConstraints = configuredConstraints { + NSLayoutConstraint.activate(configuredConstraints) + } + } + } + + open func applyThemeCollectionToCellContents(theme: Theme, collection: ThemeCollection, state: ThemeItemState) { + } + + open func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { + self.applyThemeCollection(Theme.shared.activeCollection) + + self.applyThemeCollectionToCellContents(theme: theme, collection: collection, state: ThemeItemState(selected: self.isSelected)) + } +} diff --git a/ownCloudAppShared/Client/Collection Views/Cells/ThemeableCollectionViewListCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/ThemeableCollectionViewListCell.swift index 96a97b2e1..1fc42a505 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/ThemeableCollectionViewListCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/ThemeableCollectionViewListCell.swift @@ -17,7 +17,6 @@ */ import UIKit -import ownCloudApp open class ThemeableCollectionViewListCell: UICollectionViewListCell, Themeable { private var themeRegistered : Bool = false @@ -47,6 +46,19 @@ open class ThemeableCollectionViewListCell: UICollectionViewListCell, Themeable } } + public var configuredConstraints : [NSLayoutConstraint]? { + willSet { + if let configuredConstraints = configuredConstraints { + NSLayoutConstraint.deactivate(configuredConstraints) + } + } + didSet { + if let configuredConstraints = configuredConstraints { + NSLayoutConstraint.activate(configuredConstraints) + } + } + } + open func applyThemeCollectionToCellContents(theme: Theme, collection: ThemeCollection, state: ThemeItemState) { } diff --git a/ownCloudAppShared/Client/Collection Views/Cells/ViewCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/ViewCell.swift new file mode 100644 index 000000000..9631f6b34 --- /dev/null +++ b/ownCloudAppShared/Client/Collection Views/Cells/ViewCell.swift @@ -0,0 +1,48 @@ +// +// ViewCell.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 31.05.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit + +class ViewCell: ThemeableCollectionViewListCell { + static func registerCellProvider() { + let itemListCellRegistration = UICollectionView.CellRegistration { (cell, indexPath, collectionItemRef) in + collectionItemRef.ocCellConfiguration?.configureCell(for: collectionItemRef, with: { itemRecord, item, cellConfiguration in + if let view = item as? UIView { + let contentView = cell.contentView + + contentView.addSubview(view) + + NSLayoutConstraint.activate([ + // Fill cell.contentView + view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + view.topAnchor.constraint(equalTo: contentView.topAnchor), + view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + + // Extend cell seperator to contentView.leadingAnchor + cell.separatorLayoutGuide.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor) + ]) + } + }) + } + + CollectionViewCellProvider.register(CollectionViewCellProvider(for: .view, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in + return collectionView.dequeueConfiguredReusableCell(using: itemListCellRegistration, for: indexPath, item: itemRef) + })) + } +} diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift index f02c5cc6b..428eac7ea 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift @@ -51,6 +51,33 @@ public class CollectionViewCellConfiguration: NSObject { self.hostViewController = hostViewController self.clientContext = clientContext } + + public func configureCell(for collectionItemRef: CollectionViewController.ItemRef, with configurer: (_ itemRecord: OCDataItemRecord, _ item: OCDataItem, _ cellConfiguration: CollectionViewCellConfiguration) -> Void) { + var itemRecord = record + + if itemRecord == nil { + if let collectionViewController = hostViewController { + let (itemRef, _) = collectionViewController.unwrap(collectionItemRef) + + if let retrievedItemRecord = try? source?.record(forItemRef: itemRef) { + itemRecord = retrievedItemRecord + } + } + } + + if let itemRecord = itemRecord { + if let item = itemRecord.item { + configurer(itemRecord, item, self) + } else { + // Request reconfiguration of cell + itemRecord.retrieveItem(completionHandler: { error, itemRecord in + if let collectionViewController = self.hostViewController { + collectionViewController.collectionViewDataSource.requestReconfigurationOfItems([collectionItemRef]) + } + }) + } + } + } } public extension NSObject { diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewCellProvider+StandardImplementations.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewCellProvider+StandardImplementations.swift index b6733d709..561d4ec15 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewCellProvider+StandardImplementations.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewCellProvider+StandardImplementations.swift @@ -26,6 +26,7 @@ public extension CollectionViewCellProvider { ItemListCell.registerCellProvider() ExpandableResourceCell.registerCellProvider() ActionCell.registerCellProvider() + ViewCell.registerCellProvider() registerPresentableCellProvider() } diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift index 02ab6b503..cf9837892 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift @@ -24,9 +24,11 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat public var clientContext: ClientContext? public var supportsHierarchicContent: Bool + public var usesStackViewRoot: Bool - public init(context inContext: ClientContext?, sections inSections: [CollectionViewSection]?, hierarchic: Bool = false) { + public init(context inContext: ClientContext?, sections inSections: [CollectionViewSection]?, useStackViewRoot: Bool = false, hierarchic: Bool = false) { supportsHierarchicContent = hierarchic + usesStackViewRoot = useStackViewRoot super.init(nibName: nil, bundle: nil) @@ -52,10 +54,100 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat Theme.shared.unregister(client: self) } + // MARK: - View configuration + public func configureViews() { + createCollectionView() + + if usesStackViewRoot { + createStackView() + } + + configureLayout() + } + + public func configureLayout() { + if usesStackViewRoot, let stackView = stackView { + collectionView.translatesAutoresizingMaskIntoConstraints = false + stackView.addArrangedSubview(collectionView) + } else { + collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + view.addSubview(collectionView) + } + } + + // MARK: - Stack View + public var stackView : UIStackView? + public var stackedChildren : [UIViewController] = [] + + public func createStackView() { + if stackView == nil { + stackView = UIStackView(frame: .zero) + stackView?.autoresizingMask = [.flexibleWidth, .flexibleHeight] + stackView?.axis = .vertical + stackView?.spacing = 0 + stackView?.distribution = .fill + } + } + + public enum StackedPosition : CaseIterable { + case top + case bottom + } + + public func addStacked(child viewController: UIViewController, position: StackedPosition, relativeTo: UIView? = nil) { + if !usesStackViewRoot { + return + } + + addChild(viewController) + + switch position { + case .top: + if let relativeTo = relativeTo, let position = stackView?.arrangedSubviews.firstIndex(of: relativeTo) { + stackView?.insertArrangedSubview(viewController.view, at: position) + } else { + stackView?.insertArrangedSubview(viewController.view, at: 0) + } + + case .bottom: + if let relativeTo = relativeTo, let position = stackView?.arrangedSubviews.firstIndex(of: relativeTo) { + stackView?.insertArrangedSubview(viewController.view, at: position+1) + } else { + stackView?.addArrangedSubview(viewController.view) + } + } + + stackedChildren.append(viewController) + + viewController.didMove(toParent: self) + } + + public func removeStacked(child viewController: UIViewController) { + if !usesStackViewRoot { + return + } + + viewController.willMove(toParent: nil) + + stackedChildren.removeAll(where: { vc in (vc === viewController) }) + + viewController.view.removeFromSuperview() + viewController.removeFromParent() + } + // MARK: - Collection View var collectionView : UICollectionView! = nil var collectionViewDataSource: UICollectionViewDiffableDataSource! = nil + public override func loadView() { + if usesStackViewRoot { + createStackView() + view = stackView + } else { + super.loadView() + } + } + public override func viewDidLoad() { super.viewDidLoad() configureViews() @@ -69,9 +161,11 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat configuration.interSectionSpacing = 0 - return UICollectionViewCompositionalLayout.init(sectionProvider: { sectionIndex, layoutEnvironment in - if sectionIndex > 0, sectionIndex < self.sections.count { - return self.sections[sectionIndex].provideCollectionLayoutSection(layoutEnvironment: layoutEnvironment) + return UICollectionViewCompositionalLayout(sectionProvider: { [weak self] sectionIndex, layoutEnvironment in + if let self = self { + if sectionIndex >= 0, sectionIndex < self.sections.count { + return self.sections[sectionIndex].provideCollectionLayoutSection(layoutEnvironment: layoutEnvironment) + } } // Fallback to allow compilation - should never be called @@ -79,13 +173,13 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat }, configuration: configuration) } - public func configureViews() { - collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createCollectionViewLayout()) - collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] - collectionView.contentInsetAdjustmentBehavior = .never - collectionView.contentInset = .zero - view.addSubview(collectionView) - collectionView.delegate = self + public func createCollectionView() { + if collectionView == nil { + collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createCollectionViewLayout()) + collectionView.contentInsetAdjustmentBehavior = .never + collectionView.contentInset = .zero + collectionView.delegate = self + } } // MARK: - Collection View Datasource @@ -131,6 +225,8 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat updateSource() } + public var animateDifferences : Bool = true + func updateSource(animatingDifferences: Bool = true) { guard let collectionViewDataSource = collectionViewDataSource else { return @@ -232,15 +328,64 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat } } + public func retrieveItems(at indexPaths: [IndexPath], action: @escaping ((_ recordsByIndexPath: [IndexPath : OCDataItemRecord]) -> Void), handleError: ((_ error: Error?) -> Void)? = nil) { + var recordsByIndexPath : [IndexPath : OCDataItemRecord] = [:] + + for indexPath in indexPaths { + retrieveItem(at: indexPath, synchronous: true, action: { record, indexPath in + recordsByIndexPath[indexPath] = record + }, handleError: handleError) + } + + action(recordsByIndexPath) + } + // MARK: - Collection View Delegate + public func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { + var shouldSelect : Bool = false + let interaction : ClientItemInteraction = collectionView.isEditing ? .multiselection : .selection + + retrieveItem(at: indexPath, synchronous: true, action: { [weak self] record, indexPath in + // Return early if .contextMenu is not allowed + if self?.clientContext?.validate(interaction: interaction, for: record) != false { + shouldSelect = true + } + }) + + return shouldSelect + } + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let interaction : ClientItemInteraction = collectionView.isEditing ? .multiselection : .selection + retrieveItem(at: indexPath, action: { [weak self] record, indexPath in // Return early if .selection is not allowed - if self?.clientContext?.validate(permission: .selection, for: record) != false { - self?.handleSelection(of: record, at: indexPath) + if self?.clientContext?.validate(interaction: interaction, for: record) != false { + if interaction == .multiselection { + self?.handleMultiSelection(of: record, at: indexPath, isSelected: true) + } else { + self?.handleSelection(of: record, at: indexPath) + } } }, handleError: { error in - collectionView.deselectItem(at: indexPath, animated: true) + if interaction == .selection { + collectionView.deselectItem(at: indexPath, animated: true) + } + }) + } + + public func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { + let interaction : ClientItemInteraction = collectionView.isEditing ? .multiselection : .selection + + if interaction != .multiselection { + return + } + + retrieveItem(at: indexPath, action: { [weak self] record, indexPath in + // Return early if .selection is not allowed + if self?.clientContext?.validate(interaction: interaction, for: record) != false { + self?.handleMultiSelection(of: record, at: indexPath, isSelected: false) + } }) } @@ -249,7 +394,7 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat retrieveItem(at: indexPath, synchronous: true, action: { [weak self] record, indexPath in // Return early if .contextMenu is not allowed - if self?.clientContext?.validate(permission: .contextMenu, for: record) != false { + if self?.clientContext?.validate(interaction: .contextMenu, for: record) != false { contextMenuConfiguration = self?.provideContextMenuConfiguration(for: record, at: indexPath, point: point) } }, handleError: { error in @@ -281,6 +426,10 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat return false } + @discardableResult public func handleMultiSelection(of record: OCDataItemRecord, at indexPath: IndexPath, isSelected: Bool) -> Bool { + return false + } + @discardableResult public func provideContextMenuConfiguration(for record: OCDataItemRecord, at indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { // Use context.contextMenuProvider if let item = record.item, let clientContext = clientContext, let contextMenuProvider = clientContext.contextMenuProvider { diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift index f32779ee2..687a4a274 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift @@ -21,20 +21,30 @@ import ownCloudSDK public class CollectionViewSection: NSObject { public enum CellLayout { - case list(appearance: UICollectionLayoutListConfiguration.Appearance) - case fullWidth(heightDimension: NSCollectionLayoutDimension, interItemSpacing: NSCollectionLayoutSpacing? = nil, contentInsets: NSDirectionalEdgeInsets = .zero) + case list(appearance: UICollectionLayoutListConfiguration.Appearance, headerMode: UICollectionLayoutListConfiguration.HeaderMode? = nil, headerTopPadding : CGFloat? = nil, footerMode: UICollectionLayoutListConfiguration.FooterMode? = nil, contentInsets: NSDirectionalEdgeInsets? = nil) + case fullWidth(itemHeightDimension: NSCollectionLayoutDimension, groupHeightDimension: NSCollectionLayoutDimension, edgeSpacing: NSCollectionLayoutEdgeSpacing? = nil, contentInsets: NSDirectionalEdgeInsets? = nil) + case sideways(item: NSCollectionLayoutItem? = nil, groupSize: NSCollectionLayoutSize? = nil, innerInsets : NSDirectionalEdgeInsets? = nil, edgeSpacing: NSCollectionLayoutEdgeSpacing? = nil, contentInsets: NSDirectionalEdgeInsets? = nil, orthogonalScrollingBehaviour: UICollectionLayoutSectionOrthogonalScrollingBehavior = .continuousGroupLeadingBoundary) + case custom(generator: ((_ collectionViewController: CollectionViewController?, _ layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection)) func collectionLayoutSection(for collectionViewController: CollectionViewController? = nil, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { switch self { // List - case .list(let listAppearance): + case .list(let listAppearance, let headerMode, let headerTopPadding, let footerMode, let contentInsets): var config = UICollectionLayoutListConfiguration(appearance: listAppearance) // Appearance + if let headerMode = headerMode { + config.headerMode = headerMode + } + if let headerTopPadding = headerTopPadding { + config.headerTopPadding = headerTopPadding + } + if let footerMode = footerMode { + config.footerMode = footerMode + } + switch listAppearance { case .plain: - config.headerMode = .firstItemInSection - config.headerTopPadding = 0 config.backgroundColor = Theme.shared.activeCollection.tableBackgroundColor case .grouped, .insetGrouped: @@ -43,27 +53,24 @@ public class CollectionViewSection: NSObject { default: break } -// config.headerTopPadding = 0 -// config.headerMode = .none -// config.footerMode = .none - // Leading and trailing swipe actions if let collectionViewController = collectionViewController { let clientContext = ClientContext(with: collectionViewController.clientContext, modifier: { context in context.originatingViewController = collectionViewController }) - config.leadingSwipeActionsConfigurationProvider = { (_ indexPath: IndexPath) in + config.leadingSwipeActionsConfigurationProvider = { [weak collectionViewController] (_ indexPath: IndexPath) in var swipeConfiguration : UISwipeActionsConfiguration? - collectionViewController.retrieveItem(at: indexPath, synchronous: true, action: { record, indexPath in + collectionViewController?.retrieveItem(at: indexPath, synchronous: true, action: { record, indexPath in // Return early if leadingSwipes are not allowed - if !clientContext.validate(permission: .leadingSwipe, for: record) { + if !clientContext.validate(interaction: .leadingSwipe, for: record) { return } // Use context's swipeActionsProvider if let item = record.item, + let collectionViewController = collectionViewController, let swipeActionsProvider = clientContext.swipeActionsProvider, (swipeActionsProvider as? NSObject)?.responds(to: #selector(SwipeActionsProvider.provideLeadingSwipeActions(for:item:context:))) == true { swipeConfiguration = swipeActionsProvider.provideLeadingSwipeActions?(for: collectionViewController, item: item, context: clientContext) @@ -80,17 +87,18 @@ public class CollectionViewSection: NSObject { return swipeConfiguration } - config.trailingSwipeActionsConfigurationProvider = { (_ indexPath: IndexPath) in + config.trailingSwipeActionsConfigurationProvider = { [weak collectionViewController] (_ indexPath: IndexPath) in var swipeConfiguration : UISwipeActionsConfiguration? - collectionViewController.retrieveItem(at: indexPath, synchronous: true, action: { record, indexPath in + collectionViewController?.retrieveItem(at: indexPath, synchronous: true, action: { record, indexPath in // Return early if trailingSwipes are not allowed - if !clientContext.validate(permission: .trailingSwipe, for: record) { + if !clientContext.validate(interaction: .trailingSwipe, for: record) { return } // Use context's swipeActionsProvider if let item = record.item, + let collectionViewController = collectionViewController, let swipeActionsProvider = clientContext.swipeActionsProvider, (swipeActionsProvider as? NSObject)?.responds(to: #selector(SwipeActionsProvider.provideTrailingSwipeActions(for:item:context:))) == true { swipeConfiguration = swipeActionsProvider.provideTrailingSwipeActions?(for: collectionViewController, item: item, context: clientContext) @@ -108,14 +116,49 @@ public class CollectionViewSection: NSObject { } } - return NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment) + let layoutSection = NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment) + if let contentInsets = contentInsets { + layoutSection.contentInsets = contentInsets + } + return layoutSection // Full width - case .fullWidth(let heightDimension, let interItemSpacing, let contentInsets): - let group = NSCollectionLayoutGroup(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: heightDimension)) - group.interItemSpacing = interItemSpacing - group.contentInsets = contentInsets - return NSCollectionLayoutSection(group: group) + case .fullWidth(let itemHeightDimension, let groupHeightDimension, let edgeSpacing, let contentInsets): + let item = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: itemHeightDimension)) + if let edgeSpacing = edgeSpacing { + item.edgeSpacing = edgeSpacing + } + + let group = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: groupHeightDimension), subitems: [ item ]) + + let layoutSection = NSCollectionLayoutSection(group: group) + if let contentInsets = contentInsets { + layoutSection.contentInsets = contentInsets + } + return layoutSection + + case .sideways(let item, let groupSize, let innerInsets, let edgeSpacing, let contentInsets, let orthogonalScrollingBehaviour): + let useItem = item ?? NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))) + if let edgeSpacing = edgeSpacing { + useItem.edgeSpacing = edgeSpacing + } + + let layoutGroupSize = groupSize ?? NSCollectionLayoutSize(widthDimension: .absolute(64), heightDimension: .absolute(64)) + let group = NSCollectionLayoutGroup.horizontal(layoutSize: layoutGroupSize, subitems: [useItem]) + if let innerInsets = innerInsets { + group.contentInsets = innerInsets + } + + let layoutSection = NSCollectionLayoutSection(group: group) + layoutSection.orthogonalScrollingBehavior = orthogonalScrollingBehaviour + if let contentInsets = contentInsets { + layoutSection.contentInsets = contentInsets + } + return layoutSection + + // Custom + case .custom(let generator): + return generator(collectionViewController, layoutEnvironment) } } } @@ -143,6 +186,8 @@ public class CollectionViewSection: NSObject { public var clientContext: ClientContext? public var cellConfigurationCustomizer : CellConfigurationCustomizer? + public var animateDifferences: Bool? //!< If not specified, falls back to collectionViewController.animateDifferences + public var cellLayout: CellLayout public init(identifier: SectionIdentifier, dataSource inDataSource: OCDataSource?, cellStyle : CollectionViewCellStyle = .tableCell, cellLayout: CellLayout = .list(appearance: .plain), clientContext: ClientContext? = nil ) { @@ -171,7 +216,7 @@ public class CollectionViewSection: NSObject { } func handleListUpdates(from subscription: OCDataSourceSubscription) { - collectionViewController?.updateSource(animatingDifferences: true) + collectionViewController?.updateSource(animatingDifferences: animateDifferences ?? (collectionViewController?.animateDifferences ?? true)) } // MARK: - Item provider @@ -188,18 +233,6 @@ public class CollectionViewSection: NSObject { } } -// func provideDataItem(for collectionView: UICollectionView, collectionItemRef: CollectionViewController.ItemRef) -> OCDataItem? { -// var dataItem: OCDataItem? -// -// if let (dataItemRef, _) = collectionViewController?.unwrap(collectionItemRef) { -// if let itemRecord = try? dataSource?.record(forItemRef: dataItemRef) { -// dataItem = itemRecord.item -// } -// } -// -// return dataItem -// } - // MARK: - Cell provider func provideReusableCell(for collectionView: UICollectionView, collectionItemRef: CollectionViewController.ItemRef, indexPath: IndexPath) -> UICollectionViewCell { var cell: UICollectionViewCell? diff --git a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift index c472cd0a2..a8bd366f2 100644 --- a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift @@ -20,7 +20,8 @@ import UIKit import ownCloudSDK import ownCloudApp -public class ClientItemViewController: CollectionViewController, UISearchControllerDelegate, UISearchResultsUpdating { +public class ClientItemViewController: CollectionViewController, SortBarDelegate { + // UISearchControllerDelegate, UISearchResultsUpdating { public enum ContentState : String, CaseIterable { case loading @@ -30,8 +31,11 @@ public class ClientItemViewController: CollectionViewController, UISearchControl public var query: OCQuery? - weak public var queryDataSource : OCDataSource? - public var queryItemDataSourceSection : CollectionViewSection? + public var itemsLeadInDataSource : OCDataSourceArray = OCDataSourceArray() + public var itemsQueryDataSource : OCDataSource? + public var itemsTrailingDataSource : OCDataSourceArray = OCDataSourceArray() + public var itemSectionDataSource : OCDataSourceComposition? + public var itemSection : CollectionViewSection? public var driveSection : CollectionViewSection? @@ -44,7 +48,10 @@ public class ClientItemViewController: CollectionViewController, UISearchControl public var emptyItemListDecisionSubscription : OCDataSourceSubscription? public var emptyItemListItem : OCDataItemPresentable? + public var loadingListItem : OCDataItemPresentable? + private var stateObservation : NSKeyValueObservation? + private var queryRootItemObservation : NSKeyValueObservation? public init(context inContext: ClientContext?, query inQuery: OCQuery, reveal inItem: OCItem? = nil) { query = inQuery @@ -62,6 +69,14 @@ public class ClientItemViewController: CollectionViewController, UISearchControl return true + case .multiselection: + if record?.type == .item { + // Only allow selection of items + return true + } + + return false + default: return true } @@ -81,7 +96,7 @@ public class ClientItemViewController: CollectionViewController, UISearchControl } if let queryResultsDatasource = query?.queryResultsDataSource, let core = itemControllerContext.core { - queryDataSource = queryResultsDatasource + itemsQueryDataSource = queryResultsDatasource singleDriveDatasource = OCDataSourceComposition(sources: [core.drivesDataSource]) if query?.queryLocation?.isRoot == true { @@ -103,24 +118,29 @@ public class ClientItemViewController: CollectionViewController, UISearchControl driveSection = CollectionViewSection(identifier: "drive", dataSource: driveSectionDataSource, cellStyle: .header, cellLayout: .list(appearance: .plain)) } - queryItemDataSourceSection = CollectionViewSection(identifier: "items", dataSource: queryResultsDatasource, clientContext: itemControllerContext) + itemSectionDataSource = OCDataSourceComposition(sources: [itemsLeadInDataSource, queryResultsDatasource, itemsTrailingDataSource]) + itemSection = CollectionViewSection(identifier: "items", dataSource: itemSectionDataSource, cellLayout: .list(appearance: .plain, contentInsets: NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)), clientContext: itemControllerContext) if let driveSection = driveSection { sections.append(driveSection) } - if let queryItemDataSourceSection = queryItemDataSourceSection { + if let queryItemDataSourceSection = itemSection { sections.append(queryItemDataSourceSection) } } - let emptySection = CollectionViewSection(identifier: "empty", dataSource: emptyItemListDataSource, cellStyle: .fillSpace, cellLayout: .list(appearance: .insetGrouped), clientContext: itemControllerContext) + let emptySection = CollectionViewSection(identifier: "empty", dataSource: emptyItemListDataSource, cellStyle: .fillSpace, cellLayout: .fullWidth(itemHeightDimension: .estimated(54), groupHeightDimension: .estimated(54), edgeSpacing: NSCollectionLayoutEdgeSpacing(leading: .fixed(0), top: .fixed(10), trailing: .fixed(0), bottom: .fixed(10)), contentInsets: NSDirectionalEdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20)), clientContext: itemControllerContext) sections.append(emptySection) - super.init(context: itemControllerContext, sections: sections) + super.init(context: itemControllerContext, sections: sections, useStackViewRoot: true) // Track query state and recompute content state when it changes - stateObservation = queryDataSource?.observe(\OCDataSource.state, options: [], changeHandler: { [weak self] query, change in + stateObservation = itemsQueryDataSource?.observe(\OCDataSource.state, options: [], changeHandler: { [weak self] query, change in + self?.recomputeContentState() + }) + + queryRootItemObservation = query?.observe(\OCQuery.rootItem, options: [], changeHandler: { [weak self] query, change in self?.recomputeContentState() }) @@ -134,6 +154,10 @@ public class ClientItemViewController: CollectionViewController, UISearchControl emptyItemListItem?.title = "This folder is empty. Fill it with content:".localized emptyItemListItem?.childrenDataSourceProvider = nil + loadingListItem = OCDataItemPresentable(reference: "_loadingListItem" as NSString, originalDataItemType: nil, version: nil) + loadingListItem?.title = "Loading…".localized + loadingListItem?.childrenDataSourceProvider = nil + emptyItemListDecisionSubscription = queryDatasource.subscribe(updateHandler: { [weak self] (subscription) in self?.updateEmptyItemList(from: subscription) }, on: .main, trackDifferences: false, performIntialUpdate: true) @@ -152,6 +176,7 @@ public class ClientItemViewController: CollectionViewController, UISearchControl deinit { stateObservation?.invalidate() + queryRootItemObservation?.invalidate() singleDriveDatasourceSubscription?.terminate() } @@ -195,6 +220,21 @@ public class ClientItemViewController: CollectionViewController, UISearchControl self.navigationItem.rightBarButtonItems = viewActionButtons + // Setup sort bar + sortBar = SortBar(sortMethod: sortMethod) + sortBar?.translatesAutoresizingMaskIntoConstraints = false + sortBar?.heightAnchor.constraint(equalToConstant: 40).isActive = true + sortBar?.delegate = self + sortBar?.sortMethod = self.sortMethod + sortBar?.searchScope = self.searchScope + sortBar?.showSelectButton = true + + itemsLeadInDataSource.setVersionedItems([ sortBar! ]) + + // Setup multiselect + collectionView.allowsSelectionDuringEditing = true + collectionView.allowsMultipleSelectionDuringEditing = true + // Setup search controller // searchController = UISearchController(searchResultsController: nil) // searchController?.searchResultsUpdater = self @@ -248,7 +288,7 @@ public class ClientItemViewController: CollectionViewController, UISearchControl // MARK: - Empty item list handling func emptyActions() -> [OCAction]? { - guard let context = clientContext, let core = context.core, let item = query?.rootItem else { + guard let context = clientContext, let core = context.core, let item = context.query?.rootItem else { return nil } let locationIdentifier: OCExtensionLocationIdentifier = .emptyFolder @@ -274,7 +314,7 @@ public class ClientItemViewController: CollectionViewController, UISearchControl func recomputeContentState() { OnMainThread { - switch self.queryDataSource?.state { + switch self.itemsQueryDataSource?.state { case .loading: self.contentState = .loading @@ -286,12 +326,17 @@ public class ClientItemViewController: CollectionViewController, UISearchControl } } + private var hadRootItem: Bool = false public var contentState : ContentState = .loading { didSet { - if contentState == oldValue { + let hasRootItem = (query?.rootItem != nil) + + if (contentState == oldValue) && (hadRootItem == hasRootItem) { return } + hadRootItem = hasRootItem + switch contentState { case .empty: var emptyItems : [OCDataItem] = [ ] @@ -305,9 +350,22 @@ public class ClientItemViewController: CollectionViewController, UISearchControl } emptyItemListDataSource.setItems(emptyItems, updated: nil) + itemsLeadInDataSource.setVersionedItems([ ]) + + case .loading: + var loadingItems : [OCDataItem] = [ ] + + if let loadingListItem = loadingListItem { + loadingItems.append(loadingListItem) + } + emptyItemListDataSource.setItems(loadingItems, updated: nil) + itemsLeadInDataSource.setVersionedItems([ ]) - case .hasContent, .loading: + case .hasContent: emptyItemListDataSource.setItems(nil, updated: nil) + if let sortBar = sortBar { + itemsLeadInDataSource.setVersionedItems([ sortBar ]) + } } } } @@ -323,12 +381,197 @@ public class ClientItemViewController: CollectionViewController, UISearchControl } } + // MARK: - Sorting + open var sortBar: SortBar? + open var sortMethod: SortMethod { + set { + UserDefaults.standard.setValue(newValue.rawValue, forKey: "sort-method") + } + + get { + let sort = SortMethod(rawValue: UserDefaults.standard.integer(forKey: "sort-method")) ?? SortMethod.alphabetically + return sort + } + } + open var searchScope: SearchScope = .local { + didSet { +// updateSearchPlaceholder() + } + } + open var sortDirection: SortDirection { + set { + UserDefaults.standard.setValue(newValue.rawValue, forKey: "sort-direction") + } + + get { + let direction = SortDirection(rawValue: UserDefaults.standard.integer(forKey: "sort-direction")) ?? SortDirection.ascendant + return direction + } + } + + public func sortBar(_ sortBar: SortBar, didUpdateSortMethod: SortMethod) { + sortMethod = didUpdateSortMethod + + let comparator = sortMethod.comparator(direction: sortDirection) + + query?.sortComparator = comparator +// customSearchQuery?.sortComparator = comparator +// +// if (customSearchQuery?.queryResults?.count ?? 0) >= maxResultCount { +// updateCustomSearchQuery() +// } + } + + public func sortBar(_ sortBar: SortBar, didUpdateSearchScope: SearchScope) { + } + + public func sortBar(_ sortBar: SortBar, presentViewController: UIViewController, animated: Bool, completionHandler: (() -> Void)?) { + self.present(presentViewController, animated: animated, completion: completionHandler) + } + + // MARK: - Multiselect + public func toggleSelectMode() { + if let clientContext = clientContext, clientContext.hasPermission(for: .multiselection) { + isMultiSelecting = !isMultiSelecting + } + } + + var multiSelectionActionContext: ActionContext? + var multiSelectionActionsDatasource: OCDataSourceArray? + + public var isMultiSelecting : Bool = false { + didSet { + if oldValue != isMultiSelecting { + collectionView.isEditing = isMultiSelecting + + if isMultiSelecting { + // Setup new action context + if let core = clientContext?.core { + let actionsLocation = OCExtensionLocation(ofType: .action, identifier: .multiSelection) + + multiSelectionActionContext = ActionContext(viewController: self, core: core, query: query, items: [OCItem](), location: actionsLocation) + } + + // Setup multi selection action datasource + multiSelectionActionsDatasource = OCDataSourceArray() + refreshMultiselectActions() + showActionsBar(with: multiSelectionActionsDatasource!) + } else { + closeActionsBar() + multiSelectionActionsDatasource = nil + multiSelectionActionContext = nil + } + } + } + } + + private var noActionsTextItem : OCDataItemPresentable? + + func refreshMultiselectActions() { + if let multiSelectionActionContext = multiSelectionActionContext { + var actionItems : [OCDataItem & OCDataItemVersioning] = [] + + if multiSelectionActionContext.items.count == 0 { + if noActionsTextItem == nil { + noActionsTextItem = OCDataItemPresentable(reference: "_emptyActionList" as NSString, originalDataItemType: nil, version: nil) + noActionsTextItem?.title = "Select one or more items.".localized + noActionsTextItem?.childrenDataSourceProvider = nil + } + + if let noActionsTextItem = noActionsTextItem { + actionItems = [ noActionsTextItem ] + OnMainThread { + self.actionsBarViewControllerSection?.animateDifferences = true + } + } + } else { + let actions = Action.sortedApplicableActions(for: multiSelectionActionContext) + let actionCompletionHandler : ActionCompletionHandler = { [weak self] action, error in + OnMainThread { + self?.isMultiSelecting = false + } + } + + for action in actions { + action.completionHandler = actionCompletionHandler + if let ocAction = action.provideOCAction(singleVersion: true) { + actionItems.append(ocAction) + } + } + } + + multiSelectionActionsDatasource?.setVersionedItems(actionItems) + } + } + + public override func handleMultiSelection(of record: OCDataItemRecord, at indexPath: IndexPath, isSelected: Bool) -> Bool { + if !super.handleMultiSelection(of: record, at: indexPath, isSelected: isSelected), + let multiSelectionActionContext = multiSelectionActionContext { + + retrieveItem(at: indexPath, synchronous: true, action: { [weak self] record, indexPath in + if record.type == .item, let item = record.item as? OCItem { + if isSelected { + multiSelectionActionContext.add(item: item) + } else { + multiSelectionActionContext.remove(item: item) + } + + self?.refreshMultiselectActions() + } + }) + } + + return true + } + + // MARK: - Actions + open weak var actionsBarViewControllerSection: CollectionViewSection? + open var actionsBarViewController: CollectionViewController? { + willSet { + if let actionsBarViewController = actionsBarViewController { + removeStacked(child: actionsBarViewController) + } + } + + didSet { + if let actionsBarViewController = actionsBarViewController { + addStacked(child: actionsBarViewController, position: .bottom) + } + } + } + + func showActionsBar(with datasource: OCDataSource, context: ClientContext? = nil) { + if actionsBarViewController == nil { + let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(48), heightDimension: .fractionalHeight(1)) + let item = NSCollectionLayoutItem(layoutSize: itemSize) + let actionSection = CollectionViewSection(identifier: "actions", dataSource: datasource, cellStyle: .gridCell, cellLayout: .sideways(item: item, groupSize: itemSize, edgeSpacing: NSCollectionLayoutEdgeSpacing(leading: .fixed(10), top: .fixed(0), trailing: .fixed(10), bottom: .fixed(0)), contentInsets: NSDirectionalEdgeInsets(top: 10, leading: 0, bottom: 10, trailing: 0), orthogonalScrollingBehaviour: .continuous), clientContext: clientContext) + actionSection.animateDifferences = false + let actionsViewController = CollectionViewController(context: context, sections: [ + actionSection + ]) + actionsBarViewControllerSection = actionSection + + actionsViewController.view.translatesAutoresizingMaskIntoConstraints = false + actionsViewController.view.heightAnchor.constraint(equalToConstant: 72).isActive = true +// actionsViewController.view.widthAnchor.constraint(greaterThanOrEqualToConstant: 256).isActive = true + (actionsViewController.view as? UICollectionView)?.showsVerticalScrollIndicator = false + (actionsViewController.view as? UICollectionView)?.alwaysBounceVertical = false + (actionsViewController.view as? UICollectionView)?.isScrollEnabled = false + + actionsBarViewController = actionsViewController + } + } + + func closeActionsBar() { + actionsBarViewController = nil + } + // MARK: - Search open var searchController: UISearchController? // MARK: - Search: UISearchResultsUpdating Delegate open func updateSearchResults(for searchController: UISearchController) { - let searchText = searchController.searchBar.text ?? "" +// let searchText = searchController.searchBar.text ?? "" // applySearchFilter(for: (searchText == "") ? nil : searchText, to: query) } diff --git a/ownCloudAppShared/Client/Context/ClientContext.swift b/ownCloudAppShared/Client/Context/ClientContext.swift index 696d62082..f89ba589f 100644 --- a/ownCloudAppShared/Client/Context/ClientContext.swift +++ b/ownCloudAppShared/Client/Context/ClientContext.swift @@ -61,6 +61,7 @@ public protocol InlineMessageCenter : AnyObject { public enum ClientItemInteraction { case selection + case multiselection case contextMenu case leadingSwipe case trailingSwipe @@ -142,15 +143,25 @@ public class ClientContext: NSObject { postInitializationModifier = nil } - public func validate(permission: ClientItemInteraction, for record: OCDataItemRecord) -> Bool { + public func hasPermission(for interaction: ClientItemInteraction) -> Bool { if let permissions = permissions { - if !permissions.contains(permission) { + if !permissions.contains(interaction) { + return false + } + } + + return true + } + + public func validate(interaction: ClientItemInteraction, for record: OCDataItemRecord) -> Bool { + if let permissions = permissions { + if !permissions.contains(interaction) { return false } } if let permissionHandler = permissionHandler { - return permissionHandler(self, record, permission) + return permissionHandler(self, record, interaction) } return true diff --git a/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift b/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift index 3be3d5662..c11cf7b6e 100644 --- a/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift +++ b/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift @@ -114,7 +114,7 @@ extension OCItem : DataItemContextMenuInteraction { action.progressHandler = context?.actionProgressHandlerProvider?.makeActionProgressHandler() } - let sharingItems = sharingActions.compactMap({$0.provideUIMenuAction()}) + let sharingItems = sharingActions.compactMap({ action in action.provideUIMenuAction() }) let shareMenu = UIMenu(title: "", identifier: UIMenu.Identifier("sharing"), options: .displayInline, children: sharingItems) return [shareMenu, actionsMenu] diff --git a/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift b/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift index e3baf76f6..61f3b8c7e 100644 --- a/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift +++ b/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift @@ -392,7 +392,7 @@ open class ClientQueryViewController: QueryFileListTableViewController, UIDropIn // Remove duplicates let uniqueItems = Array(Set(items)) // Get possible associated actions - let actionsLocation = OCExtensionLocation(ofType: .action, identifier: .toolbar) + let actionsLocation = OCExtensionLocation(ofType: .action, identifier: .multiSelection) let actionContext = ActionContext(viewController: self, core: core, query: query, items: uniqueItems, location: actionsLocation) self.actions = Action.sortedApplicableActions(for: actionContext) diff --git a/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift b/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift index ca0d6feb4..8c4f99c9c 100644 --- a/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift +++ b/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift @@ -292,7 +292,7 @@ open class QueryFileListTableViewController: FileListTableViewController, SortBa // Setup new action context if let core = self.core { - let actionsLocation = OCExtensionLocation(ofType: .action, identifier: .toolbar) + let actionsLocation = OCExtensionLocation(ofType: .action, identifier: .multiSelection) self.actionContext = ActionContext(viewController: self, core: core, query: query, items: [OCItem](), location: actionsLocation) } @@ -354,7 +354,6 @@ open class QueryFileListTableViewController: FileListTableViewController, SortBa sortBar?.delegate = self sortBar?.sortMethod = self.sortMethod sortBar?.searchScope = self.searchScope - sortBar?.updateForCurrentTraitCollection() sortBar?.showSelectButton = showSelectButton tableView.tableHeaderView = sortBar diff --git a/ownCloudAppShared/Client/User Interface/SortBar.swift b/ownCloudAppShared/Client/User Interface/SortBar.swift index 06143743a..eb5e813ea 100644 --- a/ownCloudAppShared/Client/User Interface/SortBar.swift +++ b/ownCloudAppShared/Client/User Interface/SortBar.swift @@ -76,7 +76,7 @@ public class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate // MARK: - Constants let sideButtonsSize: CGSize = CGSize(width: 44.0, height: 44.0) - let leftPadding: CGFloat = 20.0 + let leftPadding: CGFloat = 16.0 let rightPadding: CGFloat = 20.0 let rightSelectButtonPadding: CGFloat = 8.0 let rightSearchScopePadding: CGFloat = 15.0 @@ -85,7 +85,6 @@ public class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate // MARK: - Instance variables. - public var sortSegmentedControl: SegmentedControl? public var sortButton: UIButton? public var searchScopeSegmentedControl : SegmentedControl? public var selectButton: UIButton? @@ -152,15 +151,6 @@ public class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate sortButton?.accessibilityLabel = NSString(format: "Sort by %@".localized as NSString, sortMethod.localizedName) as String sortButton?.sizeToFit() - if let sortSegmentedControl = sortSegmentedControl, sortSegmentedControl.numberOfSegments > 0 { - if let oldSementIndex = SortMethod.all.firstIndex(of: oldValue) { - sortSegmentedControl.setTitle(oldValue.localizedName, forSegmentAt: oldSementIndex) - } - if let segmentIndex = SortMethod.all.firstIndex(of: sortMethod) { - sortSegmentedControl.selectedSegmentIndex = segmentIndex - sortSegmentedControl.setTitle(sortDirectionTitle(sortMethod.localizedName), forSegmentAt: segmentIndex) - } - } delegate?.sortBar(self, didUpdateSortMethod: sortMethod) } } @@ -174,8 +164,7 @@ public class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate // MARK: - Init & Deinit - public init(frame: CGRect, sortMethod: SortMethod, searchScope: SearchScope = .local) { - sortSegmentedControl = SegmentedControl() + public init(frame: CGRect = .zero, sortMethod: SortMethod, searchScope: SearchScope = .local) { selectButton = UIButton() sortButton = UIButton(type: .system) searchScopeSegmentedControl = SegmentedControl() @@ -185,48 +174,61 @@ public class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate super.init(frame: frame) - if let sortButton = sortButton, let sortSegmentedControl = sortSegmentedControl, let searchScopeSegmentedControl = searchScopeSegmentedControl, let selectButton = selectButton { + if let sortButton = sortButton, let searchScopeSegmentedControl = searchScopeSegmentedControl, let selectButton = selectButton { sortButton.translatesAutoresizingMaskIntoConstraints = false - sortSegmentedControl.translatesAutoresizingMaskIntoConstraints = false selectButton.translatesAutoresizingMaskIntoConstraints = false searchScopeSegmentedControl.translatesAutoresizingMaskIntoConstraints = false sortButton.accessibilityIdentifier = "sort-bar.sortButton" - sortSegmentedControl.accessibilityIdentifier = "sort-bar.segmentedControl" searchScopeSegmentedControl.accessibilityIdentifier = "sort-bar.searchScopeSegmentedControl" searchScopeSegmentedControl.accessibilityLabel = "Search scope".localized searchScopeSegmentedControl.isHidden = !self.showSearchScope searchScopeSegmentedControl.addTarget(self, action: #selector(searchScopeValueChanged), for: .valueChanged) - self.addSubview(sortSegmentedControl) self.addSubview(sortButton) self.addSubview(searchScopeSegmentedControl) self.addSubview(selectButton) // Sort segmented control NSLayoutConstraint.activate([ - sortSegmentedControl.topAnchor.constraint(equalTo: self.topAnchor, constant: topPadding), - sortSegmentedControl.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -bottomPadding), - sortSegmentedControl.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: leftPadding), - searchScopeSegmentedControl.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: -rightSearchScopePadding), searchScopeSegmentedControl.topAnchor.constraint(equalTo: self.topAnchor, constant: topPadding), searchScopeSegmentedControl.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -bottomPadding) ]) - sortSegmentedControl.isHidden = true - sortSegmentedControl.accessibilityElementsHidden = true - sortSegmentedControl.isEnabled = false - sortSegmentedControl.addTarget(self, action: #selector(sortSegmentedControllerValueChanged), for: .valueChanged) - // Sort Button sortButton.titleLabel?.font = UIFont.preferredFont(forTextStyle: .subheadline) sortButton.titleLabel?.adjustsFontForContentSizeCategory = true sortButton.semanticContentAttribute = (sortButton.effectiveUserInterfaceLayoutDirection == .leftToRight) ? .forceRightToLeft : .forceLeftToRight - sortButton.setImage(UIImage(named: "chevron-small-light"), for: .normal) - sortButton.setContentHuggingPriority(.required, for: .horizontal) + sortButton.showsMenuAsPrimaryAction = true + sortButton.menu = UIMenu(title: "", children: [ + UIDeferredMenuElement.uncached({ [weak self] completion in + var menuItems : [UIMenuElement] = [] + + for method in SortMethod.all { + let title = method.localizedName + var sortDirectionTitle = "" + + if self?.delegate?.sortMethod == method { + if self?.delegate?.sortDirection == .ascendant { // Show arrows opposite to the current sort direction to show what choosing them will lead to + sortDirectionTitle = " ↓" + } else { + sortDirectionTitle = " ↑" + } + } + + let menuItem = UIAction(title: "\(title)\(sortDirectionTitle)", image: nil, attributes: []) { [weak self] _ in + self?.sortMethod = method + } + + menuItems.append(menuItem) + } + + completion(menuItems) + }) + ]) NSLayoutConstraint.activate([ sortButton.topAnchor.constraint(equalTo: self.topAnchor, constant: topPadding), @@ -235,11 +237,6 @@ public class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate sortButton.trailingAnchor.constraint(lessThanOrEqualTo: self.trailingAnchor, constant: -rightPadding) ]) - sortButton.isHidden = true - sortButton.accessibilityElementsHidden = true - sortButton.isEnabled = false - sortButton.addTarget(self, action: #selector(presentSortButtonOptions), for: .touchUpInside) - selectButton.setImage(UIImage(named: "select"), for: .normal) selectButton.tintColor = Theme.shared.activeCollection.favoriteEnabledColor selectButton.addTarget(self, action: #selector(toggleSelectMode), for: .touchUpInside) @@ -259,7 +256,6 @@ public class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate Theme.shared.register(client: self) selectButton?.isHidden = !showSelectButton - updateForCurrentTraitCollection() } required init?(coder aDecoder: NSCoder) { @@ -275,71 +271,14 @@ public class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate public func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { self.sortButton?.applyThemeCollection(collection) self.selectButton?.applyThemeCollection(collection) - self.sortSegmentedControl?.applyThemeCollection(collection) self.searchScopeSegmentedControl?.applyThemeCollection(collection) - self.backgroundColor = collection.navigationBarColors.backgroundColor - } - - // MARK: - Sort UI - - override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) - self.updateForCurrentTraitCollection() - } - - public func updateForCurrentTraitCollection() { - switch (traitCollection.horizontalSizeClass, traitCollection.verticalSizeClass) { - case (.compact, .regular): - sortSegmentedControl?.removeAllSegments() - sortSegmentedControl?.isHidden = true - sortSegmentedControl?.accessibilityElementsHidden = true - sortSegmentedControl?.isEnabled = false - sortButton?.isHidden = false - sortButton?.accessibilityElementsHidden = false - sortButton?.isEnabled = true - default: - updateSortSegmentControl() - sortSegmentedControl?.isHidden = false - sortSegmentedControl?.accessibilityElementsHidden = false - sortSegmentedControl?.isEnabled = true - sortButton?.isHidden = true - sortButton?.accessibilityElementsHidden = true - sortButton?.isEnabled = false - } - - UIAccessibility.post(notification: .layoutChanged, argument: nil) + self.backgroundColor = collection.tableRowColors.backgroundColor } // MARK: - Sort Direction Title - func updateSortButtonTitle() { - let title = NSString(format: "Sort by %@".localized as NSString, sortMethod.localizedName) as String - sortButton?.setTitle(sortDirectionTitle(title), for: .normal) - } - - func updateSortSegmentControl() { - if let sortSegmentedControl = sortSegmentedControl { - sortSegmentedControl.removeAllSegments() - var longestTitleWidth : CGFloat = 0.0 - for method in SortMethod.all { - sortSegmentedControl.insertSegment(withTitle: method.localizedName, at: SortMethod.all.firstIndex(of: method)!, animated: false) - let titleWidth = method.localizedName.appending(" ↓").width(withConstrainedHeight: sortSegmentedControl.frame.size.height, font: UIFont.systemFont(ofSize: 16.0)) - if titleWidth > longestTitleWidth { - longestTitleWidth = titleWidth - } - longestTitleWidth += 4 // add a padding to the longest title - } - - var currentIndex = 0 - for _ in SortMethod.all { - sortSegmentedControl.setWidth(longestTitleWidth, forSegmentAt: currentIndex) - currentIndex += 1 - } - if let segmentIndex = SortMethod.all.firstIndex(of: sortMethod) { - sortSegmentedControl.selectedSegmentIndex = segmentIndex - sortSegmentedControl.setTitle(sortDirectionTitle(sortMethod.localizedName), forSegmentAt: segmentIndex) - } - } + let sortButtonTitle = sortDirectionTitle(sortMethod.localizedName) + sortButton?.setTitle(sortButtonTitle, for: .normal) } func sortDirectionTitle(_ title: String) -> String { @@ -351,40 +290,6 @@ public class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate } // MARK: - Actions - @objc private func presentSortButtonOptions(_ sender : UIButton) { - let tableViewController = SortMethodTableViewController() - tableViewController.modalPresentationStyle = .popover - tableViewController.sortBarDelegate = self.delegate - tableViewController.sortBar = self - - // On iOS 13.0/13.1, the table view's content needs to be inset by the height of the arrow - // (this can hopefully be removed again in the future, if/when Apple addresses the issue) - let popoverArrowHeight : CGFloat = 13 - tableViewController.tableView.contentInsetAdjustmentBehavior = .never - tableViewController.tableView.contentInset = UIEdgeInsets(top: popoverArrowHeight, left: 0, bottom: 0, right: 0) - tableViewController.tableView.separatorInset = UIEdgeInsets() - - let popoverPresentationController = tableViewController.popoverPresentationController - popoverPresentationController?.sourceView = sender - popoverPresentationController?.delegate = self - - if self.effectiveUserInterfaceLayoutDirection == .rightToLeft { - popoverPresentationController?.sourceRect = CGRect(x: 5, y: 0, width: 10, height: sender.frame.size.height) - } else { - popoverPresentationController?.sourceRect = CGRect(x: sender.frame.size.width - 12, y: 0, width: 10, height: sender.frame.size.height) - } - popoverPresentationController?.permittedArrowDirections = .up - - delegate?.sortBar(self, presentViewController: tableViewController, animated: true, completionHandler: nil) - } - - @objc private func sortSegmentedControllerValueChanged() { - if let selectedIndex = sortSegmentedControl?.selectedSegmentIndex { - self.sortMethod = SortMethod.all[selectedIndex] - delegate?.sortBar(self, didUpdateSortMethod: self.sortMethod) - } - } - @objc private func searchScopeValueChanged() { if let selectedIndex = searchScopeSegmentedControl?.selectedSegmentIndex { self.searchScope = SearchScope(rawValue: selectedIndex)! diff --git a/ownCloudAppShared/Client/User Interface/SortMethodTableViewController.swift b/ownCloudAppShared/Client/User Interface/SortMethodTableViewController.swift deleted file mode 100644 index 5d051b1d3..000000000 --- a/ownCloudAppShared/Client/User Interface/SortMethodTableViewController.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// SortMethodTableViewController.swift -// ownCloud -// -// Created by Matthias Hühne on 06.08.19. -// Copyright © 2019 ownCloud GmbH. All rights reserved. -// - -/* -* Copyright (C) 2019, ownCloud GmbH. -* -* This code is covered by the GNU Public License Version 3. -* -* For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ -* You should have received a copy of this license along with this program. If not, see . -* -*/ - -import UIKit - -class SortMethodTableViewController: StaticTableViewController { - - // MARK: - Constants - private let maxContentWidth : CGFloat = 150 - private let rowHeight : CGFloat = 44 - - // MARK: - Instance Variables - weak var sortBarDelegate: SortBarDelegate? - weak var sortBar: SortBar? - - override func viewDidLoad() { - super.viewDidLoad() - - self.tableView.isScrollEnabled = false - self.tableView.rowHeight = rowHeight - - var rows : [StaticTableViewRow] = [] - let contentHeight : CGFloat = rowHeight * CGFloat(SortMethod.all.count) - 1 - let contentWidth : CGFloat = maxContentWidth - self.preferredContentSize = CGSize(width: contentWidth, height: contentHeight) - - for method in SortMethod.all { - let title = method.localizedName - var sortDirectionTitle = "" - - if sortBarDelegate?.sortMethod == method { - if sortBarDelegate?.sortDirection == .ascendant { // Show arrows opposite to the current sort direction to show what choosing them will lead to - sortDirectionTitle = "↓" - } else { - sortDirectionTitle = "↑" - } - } - - let aRow = StaticTableViewRow(subtitleRowWithAction: { [weak self] (_, _) in - guard let self = self else { return } - - self.sortBar?.sortMethod = method - - self.dismiss(animated: false, completion: nil) - }, title: title, subtitle: sortDirectionTitle, style: .value1, accessoryType: .none, identifier: nil, withButtonStyle: true) - rows.append(aRow) - } - - let section : StaticTableViewSection = StaticTableViewSection(headerTitle: nil, footerTitle: nil, rows: rows) - self.addSection(section) - } -} diff --git a/ownCloudAppShared/Foundation Extensions/String+Extension.swift b/ownCloudAppShared/Foundation Extensions/String+Extension.swift index 3569d66d2..0f4fc3c0e 100644 --- a/ownCloudAppShared/Foundation Extensions/String+Extension.swift +++ b/ownCloudAppShared/Foundation Extensions/String+Extension.swift @@ -61,8 +61,8 @@ extension String { let regex = try NSRegularExpression(pattern: regex) let results = regex.matches(in: self, range: NSRange(self.startIndex..., in: self)) - return results.map { - String(self[Range($0.range, in: self)!]) + return results.map { result in + String(self[Range(result.range, in: self)!]) } } catch _ { return [] diff --git a/ownCloudAppShared/SDK Extensions/OCItem+Extension.swift b/ownCloudAppShared/SDK Extensions/OCItem+Extension.swift index c8074ac31..8151bd532 100644 --- a/ownCloudAppShared/SDK Extensions/OCItem+Extension.swift +++ b/ownCloudAppShared/SDK Extensions/OCItem+Extension.swift @@ -116,8 +116,7 @@ extension OCItem { "web": "text/code" ] - mimeTypeToIconMap.keys.forEach { - let mimeTypeKey = $0 + mimeTypeToIconMap.keys.forEach { mimeTypeKey in var mimeType : String? = mimeTypeToIconMap[mimeTypeKey] var referenceMIMEType : String? = mimeType diff --git a/ownCloudAppShared/Client/Collection Views/Cells/SortBarCell.swift b/ownCloudAppShared/UIKit Extension/UIView+OCDataItem.swift similarity index 54% rename from ownCloudAppShared/Client/Collection Views/Cells/SortBarCell.swift rename to ownCloudAppShared/UIKit Extension/UIView+OCDataItem.swift index 54b5f7442..b0173802c 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/SortBarCell.swift +++ b/ownCloudAppShared/UIKit Extension/UIView+OCDataItem.swift @@ -1,8 +1,8 @@ // -// SortBarCell.swift +// UIView+OCDataItem.swift // ownCloudAppShared // -// Created by Felix Schwarz on 30.05.22. +// Created by Felix Schwarz on 31.05.22. // Copyright © 2022 ownCloud GmbH. All rights reserved. // @@ -17,7 +17,18 @@ */ import UIKit +import ownCloudSDK -class SortBarCell: ThemeableCollectionViewListCell { +extension UIView : OCDataItem, OCDataItemVersioning { + public var dataItemType: OCDataItemType { + return .view + } + public var dataItemReference: OCDataItemReference { + return NSString(format: "%p", self) + } + + public var dataItemVersion: OCDataItemVersion { + return NSString(format: "%p", self) + } } diff --git a/ownCloudAppShared/User Interface/More/MoreStaticTableViewController.swift b/ownCloudAppShared/User Interface/More/MoreStaticTableViewController.swift index c31b49282..5cdd2aeee 100644 --- a/ownCloudAppShared/User Interface/More/MoreStaticTableViewController.swift +++ b/ownCloudAppShared/User Interface/More/MoreStaticTableViewController.swift @@ -28,9 +28,7 @@ open class MoreStaticTableViewController: StaticTableViewController { } deinit { - themeApplierTokens.forEach({ - Theme.shared.remove(applierForToken: $0) - }) + themeApplierTokens.forEach({ token in Theme.shared.remove(applierForToken: token) }) } required public init?(coder aDecoder: NSCoder) { From fa2e0dcb7c9357db5d06ec5160a38d4b30f29203 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Mon, 20 Jun 2022 10:03:22 +0200 Subject: [PATCH 041/328] - ClientContext: - change .initialRootItem to .rootItem and change type from OCItem to OCDataItem - add new interaction types drag and acceptDrop - OCDataItem+InteractionProtocols: - add new protocol DataItemDragInteraction - add new protocol DataItemDropInteraction - OCItem+Interactions: implement support for new drag and drop interactions - CollectionViewController - add new method section(at:) to get the CollectionViewSection for an index - add new method targetedDataItem(for:,interaction:) that allows modifying the item targeted by an interaction and that returns the context's rootItem if no index path is given - CollectionViewController+DragDropSupport: implement delegates for dragging and dropping, using DataItemDragInteraction and DataItemDropInteraction - ClientItemViewController: - add support for ClientContext.rootItem in combination with OCQuerys - subclass targetedDataItem(for:,interaction:) to return root item as target for drops for contents that actually represents the root folder - bump version to 224 - update SDK --- KNOWN_ISSUES.md | 10 +- ios-sdk | 2 +- ownCloud.xcodeproj/project.pbxproj | 10 +- ...ectionViewController+DragDropSupport.swift | 69 ++++++ .../CollectionViewController.swift | 34 ++- .../ClientItemViewController.swift | 31 ++- .../Client/Context/ClientContext.swift | 4 +- .../OCDataItem+InteractionProtocols.swift | 15 ++ .../OCItem+Interactions.swift | 212 ++++++++++++++++++ 9 files changed, 372 insertions(+), 15 deletions(-) create mode 100644 ownCloudAppShared/Client/Collection Views/CollectionViewController+DragDropSupport.swift diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md index ee5c0f7f4..cee4bd327 100644 --- a/KNOWN_ISSUES.md +++ b/KNOWN_ISSUES.md @@ -7,18 +7,19 @@ It should only be used with dedicated test servers, test data - and test devices ## App - in the new browsing experience, some features are not yet available: - - drag and drop + - drag and drop actions - search - - sorting - a grid view - breadcrumb title + - item / folder / usage info at the bottom of lists - spaces do not yet show a member count or provide access to a list of members - subscription of spaces can't be turned on/off yet - the root of spaces-based accounts is not yet shown as hierarchic sidebar - support for sharing is widely untested and/or unavailable in the alpha -- inactived state of spaces is not yet represented in the UI +- inactivated state of spaces is not yet represented in the UI - Copy & Paste allows copying a folder into a subfolder of its own / itself, leading to an infinite cycle - handling of detached drives with user data in them (see OCVault.detachedDrives) +- sync actions that are actually complete are not always cleared from the Status tab until a logout/login ## File Provider - dragging an entire space on top of another starts a full copy of the space, which eventually fails halfway through @@ -42,9 +43,6 @@ It should only be used with dedicated test servers, test data - and test devices - FP -> app - possibly use dedicated OC KVS + OCProgress for that -- empty folders - - replace simple "Empty folder" message with message + direct access to actions like "Create folder", "Scan document", "Upload photo", "Take photo", … using action extensions as source - - support for versions - photo uploads diff --git a/ios-sdk b/ios-sdk index ae461a00e..7b70fcca7 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit ae461a00ef030ddd6a6be2d76702c85f9bc2fad0 +Subproject commit 7b70fcca75f513c2601cb49bba840fd7b2098d15 diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index c1618bfd2..e8b8a1b4c 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -402,6 +402,7 @@ DCB5D60B25FC14B6004C52D9 /* OCIssue+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCB5D60A25FC14B6004C52D9 /* OCIssue+Extension.swift */; }; DCB6C4D72453A6CA00C1EAE1 /* ClientAuthenticationUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCB6C4D62453A6CA00C1EAE1 /* ClientAuthenticationUpdater.swift */; }; DCB6C4DE24559B1600C1EAE1 /* ClientAuthenticationUpdaterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCB6C4DD24559B1600C1EAE1 /* ClientAuthenticationUpdaterViewController.swift */; }; + DCBA96132857594E00AF333C /* CollectionViewController+DragDropSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCBA96122857594D00AF333C /* CollectionViewController+DragDropSupport.swift */; }; DCBD8EA824B3751900D92E1F /* OCItem+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 397754E123279EED00119FCB /* OCItem+Extension.swift */; }; DCC085512293ED52008CC05C /* DisplaySettingsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC085502293ED52008CC05C /* DisplaySettingsSection.swift */; }; DCC085652293F1FD008CC05C /* ownCloudApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCC0855C2293F1FD008CC05C /* ownCloudApp.framework */; }; @@ -1416,6 +1417,7 @@ DCB5D60A25FC14B6004C52D9 /* OCIssue+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCIssue+Extension.swift"; sourceTree = ""; }; DCB6C4D62453A6CA00C1EAE1 /* ClientAuthenticationUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientAuthenticationUpdater.swift; sourceTree = ""; }; DCB6C4DD24559B1600C1EAE1 /* ClientAuthenticationUpdaterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientAuthenticationUpdaterViewController.swift; sourceTree = ""; }; + DCBA96122857594D00AF333C /* CollectionViewController+DragDropSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "CollectionViewController+DragDropSupport.swift"; path = "ownCloudAppShared/Client/Collection Views/CollectionViewController+DragDropSupport.swift"; sourceTree = SOURCE_ROOT; }; DCBD8EA924B3755B00D92E1F /* OCItem+AppExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OCItem+AppExtension.swift"; sourceTree = ""; }; DCC085502293ED52008CC05C /* DisplaySettingsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplaySettingsSection.swift; sourceTree = ""; }; DCC0855C2293F1FD008CC05C /* ownCloudApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ownCloudApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -3128,6 +3130,7 @@ DCFC9ED2280023BB005D9144 /* CollectionViewCellProvider+StandardImplementations.swift */, DCFC9ECB28002303005D9144 /* CollectionViewSection.swift */, DC04FFC727F5B79000F22569 /* CollectionViewController.swift */, + DCBA96122857594D00AF333C /* CollectionViewController+DragDropSupport.swift */, DC3AB1932808C2DE00789435 /* View Controllers */, ); path = "Collection Views"; @@ -4401,6 +4404,7 @@ DC24B2AB25BA316D005783E2 /* Branding+App.swift in Sources */, 0234EF0E2515138B00AE921A /* PasscodeSetupCoordinator.swift in Sources */, DCA35DA724D309B600DBE2B0 /* OCFileProviderServiceSession+UploadByFileProvider.swift in Sources */, + DCBA96132857594E00AF333C /* CollectionViewController+DragDropSupport.swift in Sources */, 39BE385D23435AFE0062A2FE /* String+Extension.swift in Sources */, DC0A357A24C0E43700FB58FC /* CardViewController.swift in Sources */, DC46F3D1284546F000038880 /* OCItem+Interactions.swift in Sources */, @@ -4964,7 +4968,7 @@ APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; APP_SHORT_VERSION = 12.0; - APP_VERSION = 221; + APP_VERSION = 224; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -5034,7 +5038,7 @@ APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; APP_SHORT_VERSION = 12.0; - APP_VERSION = 221; + APP_VERSION = 224; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -5098,7 +5102,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - APP_VERSION = 222; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = ownCloud/ownCloud.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; @@ -5129,7 +5132,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - APP_VERSION = 222; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = ownCloud/ownCloud.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewController+DragDropSupport.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewController+DragDropSupport.swift new file mode 100644 index 000000000..5391d4d40 --- /dev/null +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewController+DragDropSupport.swift @@ -0,0 +1,69 @@ +// +// CollectionViewController+DragDropSupport.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 13.06.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudApp +import ownCloudSDK + +// MARK: - Drag and drop support +extension CollectionViewController : UICollectionViewDragDelegate { + public func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { + if let item = targetedDataItem(for: indexPath, interaction: .drag), + let dragInteraction = item as? DataItemDragInteraction { + if let dragItems = dragInteraction.provideDragItems(with: clientContext) { + return dragItems + } + } + + return [] + } + + public func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] { + if let item = targetedDataItem(for: indexPath, interaction: .drag), + let dragInteraction = item as? DataItemDragInteraction { + if let dragItems = dragInteraction.provideDragItems(with: clientContext) { + return dragItems + } + } + + return [] + } +} + +extension CollectionViewController : UICollectionViewDropDelegate { + public func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal { + if let item = targetedDataItem(for: destinationIndexPath, interaction: .acceptDrop), + let dropInteraction = item as? DataItemDropInteraction { + if let dropProposal = dropInteraction.allowDropOperation?(for: session, with: clientContext) { + return dropProposal + } + } + + return UICollectionViewDropProposal(operation: .forbidden, intent: .unspecified) + } + + public func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) { + if let item = targetedDataItem(for: coordinator.destinationIndexPath, interaction: .acceptDrop), + let dropInteraction = item as? DataItemDropInteraction { + let dragItems = coordinator.items.compactMap { collectionViewDropItem in collectionViewDropItem.dragItem } + + dropInteraction.performDropOperation(of: dragItems, with: clientContext, handlingCompletion: { didSucceed in + }) + } + } +} diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift index cf9837892..01e5eec68 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift @@ -168,7 +168,8 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat } } - // Fallback to allow compilation - should never be called + // Fallback - will typically only be called if the CollectionViewController has already been deallocated + // (such as when navigating upwards from the originating view controller during a drag & drop operation) return CollectionViewSection.CellLayout.list(appearance: .grouped).collectionLayoutSection(layoutEnvironment: layoutEnvironment) }, configuration: configuration) } @@ -179,6 +180,8 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat collectionView.contentInsetAdjustmentBehavior = .never collectionView.contentInset = .zero collectionView.delegate = self + collectionView.dragDelegate = self + collectionView.dropDelegate = self } } @@ -243,6 +246,14 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat collectionViewDataSource.apply(snapshot, animatingDifferences: animatingDifferences) } + public func section(at index: Int) -> CollectionViewSection? { + if (index >= 0) && (index < sections.count) { + return sections[index] + } + + return nil + } + // MARK: - Item references public typealias ItemRef = NSObject public class WrappedItem : NSObject { @@ -464,6 +475,27 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat return nil } + // MARK: - Drag and drop support + public func targetedDataItem(for indexPath: IndexPath?, interaction: ClientItemInteraction) -> OCDataItem? { + var item : OCDataItem? + + if let destinationIndexPath = indexPath { + // Retrieve item at index path if provided + retrieveItem(at: destinationIndexPath, synchronous: true, action: { record, indexPath in + if self.clientContext?.validate(interaction: interaction, for: record) != false { + item = record.item + } + }, handleError: { error in + Log.debug("Error \(String(describing: error)) retrieving item at destinationIndexPath \(String(describing: destinationIndexPath))") + }) + } else { + // Return root item if no index path was provided + item = clientContext?.rootItem + } + + return item + } + // MARK: - Themeing public func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { if event != .initial { diff --git a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift index a8bd366f2..d89b9912a 100644 --- a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift @@ -47,6 +47,7 @@ public class ClientItemViewController: CollectionViewController, SortBarDelegate public var emptyItemListDataSource : OCDataSourceArray = OCDataSourceArray() public var emptyItemListDecisionSubscription : OCDataSourceSubscription? public var emptyItemListItem : OCDataItemPresentable? + public var emptySection: CollectionViewSection? public var loadingListItem : OCDataItemPresentable? @@ -130,8 +131,8 @@ public class ClientItemViewController: CollectionViewController, SortBarDelegate } } - let emptySection = CollectionViewSection(identifier: "empty", dataSource: emptyItemListDataSource, cellStyle: .fillSpace, cellLayout: .fullWidth(itemHeightDimension: .estimated(54), groupHeightDimension: .estimated(54), edgeSpacing: NSCollectionLayoutEdgeSpacing(leading: .fixed(0), top: .fixed(10), trailing: .fixed(0), bottom: .fixed(10)), contentInsets: NSDirectionalEdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20)), clientContext: itemControllerContext) - sections.append(emptySection) + emptySection = CollectionViewSection(identifier: "empty", dataSource: emptyItemListDataSource, cellStyle: .fillSpace, cellLayout: .fullWidth(itemHeightDimension: .estimated(54), groupHeightDimension: .estimated(54), edgeSpacing: NSCollectionLayoutEdgeSpacing(leading: .fixed(0), top: .fixed(10), trailing: .fixed(0), bottom: .fixed(10)), contentInsets: NSDirectionalEdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20)), clientContext: itemControllerContext) + sections.append(emptySection!) super.init(context: itemControllerContext, sections: sections, useStackViewRoot: true) @@ -141,6 +142,9 @@ public class ClientItemViewController: CollectionViewController, SortBarDelegate }) queryRootItemObservation = query?.observe(\OCQuery.rootItem, options: [], changeHandler: { [weak self] query, change in + OnMainThread(inline: true) { + self?.clientContext?.rootItem = query.rootItem + } self?.recomputeContentState() }) @@ -524,6 +528,28 @@ public class ClientItemViewController: CollectionViewController, SortBarDelegate return true } + // MARK: - Drag & Drop + public override func targetedDataItem(for indexPath: IndexPath?, interaction: ClientItemInteraction) -> OCDataItem? { + var dataItem: OCDataItem? = super.targetedDataItem(for: indexPath, interaction: interaction) + + if interaction == .acceptDrop { + if let indexPath = indexPath { + if let section = section(at: indexPath.section) { + if (section == emptySection) || (section == driveSection) || ((dataItem as? OCItem)?.type == .file), clientContext?.hasPermission(for: interaction) == true { + // Return root item of view controller if a drop operation targets + // - the empty section + // - the drive (header) section + // - a file + // and drops are permitted + dataItem = clientContext?.rootItem + } + } + } + } + + return dataItem + } + // MARK: - Actions open weak var actionsBarViewControllerSection: CollectionViewSection? open var actionsBarViewController: CollectionViewController? { @@ -608,3 +634,4 @@ public class ClientItemViewController: CollectionViewController, SortBarDelegate } } } + diff --git a/ownCloudAppShared/Client/Context/ClientContext.swift b/ownCloudAppShared/Client/Context/ClientContext.swift index f89ba589f..484c97707 100644 --- a/ownCloudAppShared/Client/Context/ClientContext.swift +++ b/ownCloudAppShared/Client/Context/ClientContext.swift @@ -65,6 +65,8 @@ public enum ClientItemInteraction { case contextMenu case leadingSwipe case trailingSwipe + case drag + case acceptDrop } public class ClientContext: NSObject { @@ -80,7 +82,7 @@ public class ClientContext: NSObject { public weak var query: OCQuery? // MARK: - Item - public var initialRootItem : OCItem? + public var rootItem : OCDataItem? // MARK: - UI objects public weak var rootViewController: UIViewController? diff --git a/ownCloudAppShared/Client/Data Item Interactions/OCDataItem+InteractionProtocols.swift b/ownCloudAppShared/Client/Data Item Interactions/OCDataItem+InteractionProtocols.swift index 7cff7c803..c108077a5 100644 --- a/ownCloudAppShared/Client/Data Item Interactions/OCDataItem+InteractionProtocols.swift +++ b/ownCloudAppShared/Client/Data Item Interactions/OCDataItem+InteractionProtocols.swift @@ -38,3 +38,18 @@ import ownCloudSDK @objc public protocol DataItemContextMenuInteraction: OCDataItem { func composeContextMenuItems(in viewController: UIViewController?, location: OCExtensionLocationIdentifier, with context: ClientContext?) -> [UIMenuElement]? } + +// MARK: - Drag & drop +public struct LocalDataItem { + var bookmarkUUID : UUID + var dataItem: OCDataItem +} + +@objc public protocol DataItemDragInteraction: OCDataItem { + func provideDragItems(with context: ClientContext?) -> [UIDragItem]? +} + +@objc public protocol DataItemDropInteraction: OCDataItem { + @objc optional func allowDropOperation(for session: UIDropSession, with context: ClientContext?) -> UICollectionViewDropProposal? + func performDropOperation(of items: [UIDragItem], with context: ClientContext?, handlingCompletion: (_ didSucceed: Bool) -> Void) +} diff --git a/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift b/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift index c11cf7b6e..e478dd409 100644 --- a/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift +++ b/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift @@ -19,6 +19,7 @@ import UIKit import ownCloudSDK import ownCloudApp +import UniformTypeIdentifiers // MARK: - Selection > Open extension OCItem : DataItemSelectionInteraction { @@ -123,3 +124,214 @@ extension OCItem : DataItemContextMenuInteraction { return actionMenuActions } } + +// MARK: - Drag +extension OCItem : DataItemDragInteraction { + public func provideDragItems(with context: ClientContext?) -> [UIDragItem]? { + guard !DisplaySettings.shared.preventDraggingFiles, let context = context, let core = context.core, let itemLocation = location else { + return nil + } + let bookmark: OCBookmark = core.bookmark + let item: OCItem = self + let localObject: LocalDataItem = LocalDataItem(bookmarkUUID: bookmark.uuid, dataItem: item) + + let itemProvider = NSItemProvider() + + // Add suggested name + itemProvider.suggestedName = item.name + + // All items: register data representation to provide OCLocationData + itemProvider.registerDataRepresentation(forTypeIdentifier: OCLocationDataTypeIdentifier, visibility: .ownProcess) { (completionHandler) -> Progress? in + guard let data = itemLocation.data else { return nil } + + completionHandler(data, nil) + + return nil + } + + // For files: register file representation to provide actual file + if item.type == .file { + guard let itemMimeType = item.mimeType else { return nil } + guard let itemUTI = UTType(mimeType: itemMimeType)?.identifier else { return nil } + + itemProvider.suggestedName = item.name + + itemProvider.registerFileRepresentation(forTypeIdentifier: itemUTI, fileOptions: [], visibility: .all, loadHandler: { [weak core] (completionHandler) -> Progress? in + var progress : Progress? + + guard let core = core else { + completionHandler(nil, false, NSError(domain: OCErrorDomain, code: Int(OCError.internal.rawValue), userInfo: nil)) + return nil + } + + if let localFileURL = core.localCopy(of: item) { + // Provide local copies directly + completionHandler(localFileURL, true, nil) + } else { + // Otherwise download the file and provide it when done + progress = core.downloadItem(item, options: [ + .returnImmediatelyIfOfflineOrUnavailable : true, + .addTemporaryClaimForPurpose : OCCoreClaimPurpose.view.rawValue + ], resultHandler: { (error, core, item, file) in + guard error == nil, let fileURL = file?.url else { + completionHandler(nil, false, error) + return + } + + completionHandler(fileURL, true, nil) + + if let claim = file?.claim, let item = item { + core.remove(claim, on: item, afterDeallocationOf: [fileURL]) + } + }) + } + + return progress + }) + } + + // Create dragItem from itemProvider and localObject + let dragItem = UIDragItem(itemProvider: itemProvider) + dragItem.localObject = localObject + + return [dragItem] + } +} + +// MARK: - Drop +extension OCItem : DataItemDropInteraction { + public func allowDropOperation(for session: UIDropSession, with context: ClientContext?) -> UICollectionViewDropProposal? { + if session.localDragSession != nil { + if type == .collection { + return UICollectionViewDropProposal(operation: .move, intent: .insertIntoDestinationIndexPath) + } else { + return UICollectionViewDropProposal(operation: .move) + } + } else { + return UICollectionViewDropProposal(operation: .copy) + } + } + + public func performDropOperation(of droppedItems: [UIDragItem], with context: ClientContext?, handlingCompletion: (_ didSucceed: Bool) -> Void) { + guard let core = context?.core, type == .collection else { + handlingCompletion(false) + return + } + let targetItem = self + var allSuccessful : Bool = true + + for droppedItem in droppedItems { + if let localDataItem = droppedItem.localObject as? LocalDataItem, let item = localDataItem.dataItem as? OCItem { + if localDataItem.bookmarkUUID == context?.core?.bookmark.uuid { + // Move item within same account + if let itemName = item.name, + let progress = core.move(item, to: targetItem, withName: itemName, options: nil, resultHandler: { (error, _, _, _) in + if error != nil { + Log.log("Error \(String(describing: error)) moving \(String(describing: item.path))") + } + }) { + context?.progressSummarizer?.startTracking(progress: progress) + } else { + allSuccessful = false + } + } else { + // Copy item from other account + if let sourceBookmark = OCBookmarkManager.shared.bookmark(for: localDataItem.bookmarkUUID) { + OCCoreManager.shared.requestCore(for: sourceBookmark, setup: nil) { [weak core] (srcCore, error) in + if error == nil { + srcCore?.downloadItem(item, options: nil, resultHandler: { (error, _, srcItem, _) in + if error == nil, let srcItem = srcItem, let localURL = srcCore?.localCopy(of: srcItem) { + core?.importItemNamed(srcItem.name, at: targetItem, from: localURL, isSecurityScoped: false, options: nil, placeholderCompletionHandler: nil) { (_, _, _, _) in + } + } + }) + } + } + } else { + allSuccessful = false + } + } + } else { + // Import item from other sources + let typeIdentifiers = droppedItem.itemProvider.registeredTypeIdentifiers + let preferredUTIs : [UTType] = [ + .image, + .movie, + .pdf, + .text, + .rtf, + .html, + .plainText + ] + var useUTI : String? + var useIndex : Int = Int.max + + for typeIdentifier in typeIdentifiers { + if typeIdentifier != OCLocationDataTypeIdentifier, !typeIdentifier.hasPrefix("dyn."), let typeIdentifierUTI = UTType(typeIdentifier) { + for preferredUTI in preferredUTIs { + let conforms = typeIdentifierUTI.conforms(to: preferredUTI) + + // Log.log("\(preferredUTI) vs \(typeIdentifier) -> \(conforms)") + + if conforms { + if let utiIndex = preferredUTIs.firstIndex(of: preferredUTI), utiIndex < useIndex { + useUTI = typeIdentifier + useIndex = utiIndex + } + } + } + } + } + + if useUTI == nil, typeIdentifiers.count == 1 { + useUTI = typeIdentifiers.first + } + + if useUTI == nil { + useUTI = UTType.data.identifier + } + + var fileName: String? + + droppedItem.itemProvider.loadFileRepresentation(forTypeIdentifier: useUTI!) { (itemURL, _ error) in + guard let url = itemURL else { return } + + let fileNameMaxLength = 16 + + if useUTI == UTType.utf8PlainText.identifier { + fileName = try? String(String(contentsOf: url, encoding: .utf8).prefix(fileNameMaxLength) + ".txt") + } + + if useUTI == UTType.rtf.identifier { + let options = [NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.rtf] + fileName = try? String(NSAttributedString(url: url, options: options, documentAttributes: nil).string.prefix(fileNameMaxLength) + ".rtf") + } + + fileName = fileName? + .trimmingCharacters(in: .illegalCharacters) + .trimmingCharacters(in: .whitespaces) + .trimmingCharacters(in: .newlines) + .filter({ $0.isASCII }) + + if fileName == nil { + fileName = url.lastPathComponent + } + + guard let name = fileName else { return } + + if let progress = core.importItemNamed(name, at: targetItem, from: url, isSecurityScoped: false, options: nil, placeholderCompletionHandler: nil, resultHandler: { (error, _ core, _ item, _) in + if error != nil { + Log.debug("Error uploading \(Log.mask(name)) file to \(Log.mask(targetItem.path))") + } else { + Log.debug("Success uploading \(Log.mask(name)) file to \(Log.mask(targetItem.path))") + } + }) { + context?.progressSummarizer?.startTracking(progress: progress) + } + } + } + } + + handlingCompletion(allSuccessful) + } +} From ffe536c0f8bb7cf38504f7ff368dda003ba50a25 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Tue, 21 Jun 2022 10:44:53 +0200 Subject: [PATCH 042/328] - Action: add new .dropAction location and add relevant actions there - ActionCell: switch from selectedBackgroundView to backgroundConfiguration and add support for drop target highlighting - DriveHeaderCell: - increase contrast of background view - make header cell slimmer if no background image is set - ClientContext: - add DropTargetsProvider that can provide drop targets when items are dragged onto a view - add support for multiple permission handlers - CollectionViewController: - move drag and drop code from CollectionViewController+DragDropSupport into CollectionViewController to allow subclassing - move actions bar support from ClientItemViewController into CollectionViewController - add support for DropTargetsProvider - ClientItemViewController - add permission handler to prevent drags and context-menus when in multi-selection mode - remove search code to re-add it later - OCAction+Interactions: add drop support to trigger action run - OCItem+Interactions: adapt DataItemDropInteraction to prevent drops of items onto themselves or their existing location --- KNOWN_ISSUES.md | 2 +- ownCloud.xcodeproj/project.pbxproj | 4 - .../Actions+Extensions/CopyAction.swift | 4 +- .../Actions+Extensions/CutAction.swift | 4 +- .../Actions+Extensions/DeleteAction.swift | 4 +- .../Actions+Extensions/DuplicateAction.swift | 4 +- .../Actions+Extensions/MoveAction.swift | 4 +- .../Actions+Extensions/OpenInAction.swift | 4 +- ownCloudAppShared/Client/Actions/Action.swift | 3 +- .../Client/Actions/CreateFolderAction.swift | 2 +- .../Collection Views/Cells/ActionCell.swift | 24 ++- .../Cells/DriveHeaderCell.swift | 29 ++- .../Cells/DriveListCell.swift | 4 + ...ectionViewController+DragDropSupport.swift | 69 ------- .../CollectionViewController.swift | 170 ++++++++++++++++-- .../ClientItemViewController.swift | 152 ++++++++-------- .../Client/Context/ClientContext.swift | 41 ++++- .../OCAction+Interactions.swift | 16 ++ .../OCDataItem+InteractionProtocols.swift | 2 +- .../OCItem+Interactions.swift | 17 +- 20 files changed, 363 insertions(+), 196 deletions(-) delete mode 100644 ownCloudAppShared/Client/Collection Views/CollectionViewController+DragDropSupport.swift diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md index cee4bd327..d7304312c 100644 --- a/KNOWN_ISSUES.md +++ b/KNOWN_ISSUES.md @@ -7,7 +7,6 @@ It should only be used with dedicated test servers, test data - and test devices ## App - in the new browsing experience, some features are not yet available: - - drag and drop actions - search - a grid view - breadcrumb title @@ -20,6 +19,7 @@ It should only be used with dedicated test servers, test data - and test devices - Copy & Paste allows copying a folder into a subfolder of its own / itself, leading to an infinite cycle - handling of detached drives with user data in them (see OCVault.detachedDrives) - sync actions that are actually complete are not always cleared from the Status tab until a logout/login +- dropping an item into its source/origin folder (same view controller) triggers a MOVE that fails ## File Provider - dragging an entire space on top of another starts a full copy of the space, which eventually fails halfway through diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index e8b8a1b4c..10b09ecee 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -402,7 +402,6 @@ DCB5D60B25FC14B6004C52D9 /* OCIssue+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCB5D60A25FC14B6004C52D9 /* OCIssue+Extension.swift */; }; DCB6C4D72453A6CA00C1EAE1 /* ClientAuthenticationUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCB6C4D62453A6CA00C1EAE1 /* ClientAuthenticationUpdater.swift */; }; DCB6C4DE24559B1600C1EAE1 /* ClientAuthenticationUpdaterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCB6C4DD24559B1600C1EAE1 /* ClientAuthenticationUpdaterViewController.swift */; }; - DCBA96132857594E00AF333C /* CollectionViewController+DragDropSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCBA96122857594D00AF333C /* CollectionViewController+DragDropSupport.swift */; }; DCBD8EA824B3751900D92E1F /* OCItem+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 397754E123279EED00119FCB /* OCItem+Extension.swift */; }; DCC085512293ED52008CC05C /* DisplaySettingsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC085502293ED52008CC05C /* DisplaySettingsSection.swift */; }; DCC085652293F1FD008CC05C /* ownCloudApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCC0855C2293F1FD008CC05C /* ownCloudApp.framework */; }; @@ -1417,7 +1416,6 @@ DCB5D60A25FC14B6004C52D9 /* OCIssue+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCIssue+Extension.swift"; sourceTree = ""; }; DCB6C4D62453A6CA00C1EAE1 /* ClientAuthenticationUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientAuthenticationUpdater.swift; sourceTree = ""; }; DCB6C4DD24559B1600C1EAE1 /* ClientAuthenticationUpdaterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientAuthenticationUpdaterViewController.swift; sourceTree = ""; }; - DCBA96122857594D00AF333C /* CollectionViewController+DragDropSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "CollectionViewController+DragDropSupport.swift"; path = "ownCloudAppShared/Client/Collection Views/CollectionViewController+DragDropSupport.swift"; sourceTree = SOURCE_ROOT; }; DCBD8EA924B3755B00D92E1F /* OCItem+AppExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OCItem+AppExtension.swift"; sourceTree = ""; }; DCC085502293ED52008CC05C /* DisplaySettingsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplaySettingsSection.swift; sourceTree = ""; }; DCC0855C2293F1FD008CC05C /* ownCloudApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ownCloudApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -3130,7 +3128,6 @@ DCFC9ED2280023BB005D9144 /* CollectionViewCellProvider+StandardImplementations.swift */, DCFC9ECB28002303005D9144 /* CollectionViewSection.swift */, DC04FFC727F5B79000F22569 /* CollectionViewController.swift */, - DCBA96122857594D00AF333C /* CollectionViewController+DragDropSupport.swift */, DC3AB1932808C2DE00789435 /* View Controllers */, ); path = "Collection Views"; @@ -4404,7 +4401,6 @@ DC24B2AB25BA316D005783E2 /* Branding+App.swift in Sources */, 0234EF0E2515138B00AE921A /* PasscodeSetupCoordinator.swift in Sources */, DCA35DA724D309B600DBE2B0 /* OCFileProviderServiceSession+UploadByFileProvider.swift in Sources */, - DCBA96132857594E00AF333C /* CollectionViewController+DragDropSupport.swift in Sources */, 39BE385D23435AFE0062A2FE /* String+Extension.swift in Sources */, DC0A357A24C0E43700FB58FC /* CardViewController.swift in Sources */, DC46F3D1284546F000038880 /* OCItem+Interactions.swift in Sources */, diff --git a/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift b/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift index d4c7559bb..54d35fa52 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift @@ -63,7 +63,7 @@ class CopyAction : Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.copy") } override class var category : ActionCategory? { return .normal } override class var name : String? { return "Copy".localized } - override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .moreFolder, .multiSelection, .keyboardShortcut, .contextMenuItem] } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .moreFolder, .multiSelection, .dropAction, .keyboardShortcut, .contextMenuItem] } override class var keyCommand : String? { return "C" } override class var keyModifierFlags: UIKeyModifierFlags? { return [.command] } @@ -104,7 +104,7 @@ class CopyAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection { + if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection || location == .dropAction { return UIImage(named: "copy-file")?.withRenderingMode(.alwaysTemplate) } diff --git a/ownCloud/Client/Actions/Actions+Extensions/CutAction.swift b/ownCloud/Client/Actions/Actions+Extensions/CutAction.swift index f33bff6d9..b3050c5a3 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/CutAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/CutAction.swift @@ -25,7 +25,7 @@ class CutAction : Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.cutpasteboard") } override class var category : ActionCategory? { return .normal } override class var name : String? { return "Cut".localized } - override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .moreFolder, .multiSelection, .keyboardShortcut, .contextMenuItem] } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .moreFolder, .multiSelection, .dropAction, .keyboardShortcut, .contextMenuItem] } override class var keyCommand : String? { return "X" } override class var keyModifierFlags: UIKeyModifierFlags? { return [.command] } @@ -91,7 +91,7 @@ class CutAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection { + if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection || location == .dropAction { return UIImage(systemName: "scissors")?.withRenderingMode(.alwaysTemplate) } diff --git a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift index 84c97a397..cc3be1501 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift @@ -23,7 +23,7 @@ class DeleteAction : Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.delete") } override class var category : ActionCategory? { return .destructive } override class var name : String? { return "Delete".localized } - override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .tableRow, .moreFolder, .multiSelection, .keyboardShortcut, .contextMenuItem] } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .tableRow, .moreFolder, .multiSelection, .dropAction, .keyboardShortcut, .contextMenuItem] } override class var keyCommand : String? { return "\u{08}" } override class var keyModifierFlags: UIKeyModifierFlags? { return [.command] } @@ -97,7 +97,7 @@ class DeleteAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection { + if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection || location == .dropAction { return UIImage(named: "trash")?.withRenderingMode(.alwaysTemplate) } diff --git a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift index ad363de6a..565e8ec0f 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift @@ -25,7 +25,7 @@ class DuplicateAction : Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.duplicate") } override class var category : ActionCategory? { return .normal } override class var name : String? { return "Duplicate".localized } - override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .moreFolder, .multiSelection, .keyboardShortcut, .contextMenuItem] } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .moreFolder, .multiSelection, .dropAction, .keyboardShortcut, .contextMenuItem] } override class var keyCommand : String? { return "D" } override class var keyModifierFlags: UIKeyModifierFlags? { return [.command] } @@ -77,7 +77,7 @@ class DuplicateAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection { + if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection || location == .dropAction { return UIImage(named: "duplicate-file")?.withRenderingMode(.alwaysTemplate) } diff --git a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift index 0a582f3e3..8fce48af9 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift @@ -23,7 +23,7 @@ class MoveAction : Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.move") } override class var category : ActionCategory? { return .normal } override class var name : String? { return "Move".localized } - override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .moreFolder, .multiSelection, .keyboardShortcut, .contextMenuItem] } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .moreFolder, .multiSelection, .dropAction, .keyboardShortcut, .contextMenuItem] } override class var keyCommand : String? { return "V" } override class var keyModifierFlags: UIKeyModifierFlags? { return [.command, .alternate] } @@ -77,7 +77,7 @@ class MoveAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection { + if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection || location == .dropAction { return UIImage(named: "folder")?.withRenderingMode(.alwaysTemplate) } diff --git a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift index 35152b617..03a918c7d 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift @@ -23,7 +23,7 @@ class OpenInAction: Action { override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.openin") } override class var category : ActionCategory? { return .normal } override class var name : String { return "Open in".localized } - override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .multiSelection, .keyboardShortcut, .contextMenuItem] } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .multiSelection, .dropAction, .keyboardShortcut, .contextMenuItem] } override class var keyCommand : String? { return "O" } override class var keyModifierFlags: UIKeyModifierFlags? { return [.command] } @@ -169,7 +169,7 @@ class OpenInAction: Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection { + if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection || location == .dropAction { return UIImage(systemName: "square.and.arrow.up")?.withRenderingMode(.alwaysTemplate) } diff --git a/ownCloudAppShared/Client/Actions/Action.swift b/ownCloudAppShared/Client/Actions/Action.swift index d3b4d1cff..217f7bb01 100644 --- a/ownCloudAppShared/Client/Actions/Action.swift +++ b/ownCloudAppShared/Client/Actions/Action.swift @@ -62,6 +62,7 @@ public extension OCExtensionLocationIdentifier { static let moreFolder: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("moreFolder") //!< Present in "more" options for a whole folder static let emptyFolder: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("emptyFolder") //!< Present in "more" options for a whole folder static let multiSelection: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("multiSelection") //!< Present as action when selecting multiple items + static let dropAction: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("dropAction") //!< Present action as drop target when items are dragged static let folderAction: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("folderAction") //!< Present in the alert sheet when the folder action bar button is pressed static let keyboardShortcut: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("keyboardShortcut") //!< Currently used for UIKeyCommand static let contextMenuItem: OCExtensionLocationIdentifier = OCExtensionLocationIdentifier("contextMenuItem") //!< Used in UIMenu @@ -476,7 +477,7 @@ open class Action : NSObject { return alertAction } - open func provideOCAction(singleVersion: Bool = false, with additionalCompletionHandler: (() -> Void)? = nil) -> OCAction? { + open func provideOCAction(singleVersion: Bool = false, with additionalCompletionHandler: (() -> Void)? = nil) -> OCAction { let icon = self.icon?.paddedTo(width: 36, height: nil) var name = actionExtension.name diff --git a/ownCloudAppShared/Client/Actions/CreateFolderAction.swift b/ownCloudAppShared/Client/Actions/CreateFolderAction.swift index e60184ce4..276e40d2f 100644 --- a/ownCloudAppShared/Client/Actions/CreateFolderAction.swift +++ b/ownCloudAppShared/Client/Actions/CreateFolderAction.swift @@ -106,7 +106,7 @@ open class CreateFolderAction : Action { } override open class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .multiSelection || location == .folderAction || location == .contextMenuItem || location == .emptyFolder || location == .multiSelection { + if location == .keyboardShortcut || location == .folderAction || location == .emptyFolder { return Theme.shared.image(for: "folder-create", size: CGSize(width: 30.0, height: 30.0))!.withRenderingMode(.alwaysTemplate) } diff --git a/ownCloudAppShared/Client/Collection Views/Cells/ActionCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/ActionCell.swift index a02910c72..26feb99b5 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/ActionCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/ActionCell.swift @@ -85,7 +85,8 @@ class ActionCell: ThemeableCollectionViewCell { iconView.setContentHuggingPriority(.required, for: .horizontal) - selectedBackgroundView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 10)) + let backgroundConfig = UIBackgroundConfiguration.clear() + backgroundConfiguration = backgroundConfig } func configureLayout() { @@ -135,17 +136,28 @@ class ActionCell: ThemeableCollectionViewCell { } } + override func updateConfiguration(using state: UICellConfigurationState) { + let collection = Theme.shared.activeCollection + var backgroundConfig = backgroundConfiguration?.updated(for: state) + + if state.isHighlighted || state.isSelected || (state.cellDropState == .targeted) { + backgroundConfig?.backgroundColor = (type == .destructive) ? collection.destructiveColors.highlighted.background : UIColor(white: 0, alpha: 0.10) + } else { + backgroundConfig?.backgroundColor = (type == .destructive) ? collection.destructiveColors.normal.background : UIColor(white: 0, alpha: 0.05) + } + + backgroundConfig?.cornerRadius = 8 + + backgroundConfiguration = backgroundConfig + } + override func applyThemeCollectionToCellContents(theme: Theme, collection: ThemeCollection, state: ThemeItemState) { super.applyThemeCollectionToCellContents(theme: theme, collection: collection, state: state) titleLabel.textColor = (type == .destructive) ? collection.destructiveColors.normal.foreground : collection.tintColor iconView.tintColor = (type == .destructive) ? collection.destructiveColors.normal.foreground : collection.tintColor - backgroundColor = (type == .destructive) ? collection.destructiveColors.normal.background : UIColor(white: 0, alpha: 0.05) - selectedBackgroundView?.backgroundColor = (type == .destructive) ? collection.destructiveColors.highlighted.background : UIColor(white: 0, alpha: 0.10) - - layer.cornerRadius = 8 - selectedBackgroundView?.layer.cornerRadius = 8 + setNeedsUpdateConfiguration() } } diff --git a/ownCloudAppShared/Client/Collection Views/Cells/DriveHeaderCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/DriveHeaderCell.swift index 59e0dcdd3..9ab37ecde 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/DriveHeaderCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/DriveHeaderCell.swift @@ -22,6 +22,14 @@ class DriveHeaderCell: DriveListCell { let darkBackgroundView = UIView() var coverObservation : NSKeyValueObservation? + var isRequestingCoverImage : Bool = true { + didSet { + recomputeHeight() + } + } + + weak var collectionViewController: CollectionViewController? + var collectionItemRef: CollectionViewController.ItemRef? deinit { Theme.shared.unregister(client: self) @@ -30,7 +38,7 @@ class DriveHeaderCell: DriveListCell { override func configure() { darkBackgroundView.translatesAutoresizingMaskIntoConstraints = false - darkBackgroundView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.3) + darkBackgroundView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.4) contentView.clipsToBounds = true @@ -45,6 +53,7 @@ class DriveHeaderCell: DriveListCell { textOuterSpacing = 16 coverImageResourceView.fallbackView = nil + coverImageHeightConstraint = coverImageResourceView.heightAnchor.constraint(greaterThanOrEqualToConstant: 160) contentView.insertSubview(darkBackgroundView, belowSubview: titleLabel) @@ -52,9 +61,26 @@ class DriveHeaderCell: DriveListCell { coverObservation = coverImageResourceView.observe(\ResourceViewHost.contentStatus, options: [.initial], changeHandler: { [weak self] viewHost, _ in self?.darkBackgroundView.isHidden = (viewHost.contentStatus != .fromResource) + self?.recomputeHeight() }) } + func recomputeHeight() { + var newHeight : CGFloat = 160 + + if !isRequestingCoverImage && (coverImageResourceView.contentStatus == .none) { + newHeight = 80 + } + + if let constantHeight = coverImageHeightConstraint?.constant, constantHeight != newHeight { + coverImageHeightConstraint?.constant = newHeight + + if let collectionViewController = collectionViewController, let collectionItemRef = collectionItemRef { + collectionViewController.collectionViewDataSource.requestReconfigurationOfItems([collectionItemRef], animated: false) + } + } + } + override func applyThemeCollectionToCellContents(theme: Theme, collection: ThemeCollection, state: ThemeItemState) { coverImageResourceView.backgroundColor = collection.lightBrandColor @@ -66,7 +92,6 @@ class DriveHeaderCell: DriveListCell { } override func configureLayout() { - coverImageHeightConstraint = coverImageResourceView.heightAnchor.constraint(greaterThanOrEqualToConstant: 160) NSLayoutConstraint.activate([ coverImageHeightConstraint!, diff --git a/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift index ccaac9a69..9b5905e6b 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift @@ -172,6 +172,10 @@ extension DriveListCell { cell.subtitle = subtitle cell.coverImageResourceView.request = coverImageRequest + cell.isRequestingCoverImage = (coverImageRequest != nil) + + cell.collectionItemRef = collectionItemRef + cell.collectionViewController = collectionItemRef.ocCellConfiguration?.hostViewController if let coverImageRequest = coverImageRequest { resourceManager?.start(coverImageRequest) diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewController+DragDropSupport.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewController+DragDropSupport.swift deleted file mode 100644 index 5391d4d40..000000000 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewController+DragDropSupport.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// CollectionViewController+DragDropSupport.swift -// ownCloudAppShared -// -// Created by Felix Schwarz on 13.06.22. -// Copyright © 2022 ownCloud GmbH. All rights reserved. -// - -/* - * Copyright (C) 2022, ownCloud GmbH. - * - * This code is covered by the GNU Public License Version 3. - * - * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ - * You should have received a copy of this license along with this program. If not, see . - * - */ - -import UIKit -import ownCloudApp -import ownCloudSDK - -// MARK: - Drag and drop support -extension CollectionViewController : UICollectionViewDragDelegate { - public func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { - if let item = targetedDataItem(for: indexPath, interaction: .drag), - let dragInteraction = item as? DataItemDragInteraction { - if let dragItems = dragInteraction.provideDragItems(with: clientContext) { - return dragItems - } - } - - return [] - } - - public func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] { - if let item = targetedDataItem(for: indexPath, interaction: .drag), - let dragInteraction = item as? DataItemDragInteraction { - if let dragItems = dragInteraction.provideDragItems(with: clientContext) { - return dragItems - } - } - - return [] - } -} - -extension CollectionViewController : UICollectionViewDropDelegate { - public func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal { - if let item = targetedDataItem(for: destinationIndexPath, interaction: .acceptDrop), - let dropInteraction = item as? DataItemDropInteraction { - if let dropProposal = dropInteraction.allowDropOperation?(for: session, with: clientContext) { - return dropProposal - } - } - - return UICollectionViewDropProposal(operation: .forbidden, intent: .unspecified) - } - - public func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) { - if let item = targetedDataItem(for: coordinator.destinationIndexPath, interaction: .acceptDrop), - let dropInteraction = item as? DataItemDropInteraction { - let dragItems = coordinator.items.compactMap { collectionViewDropItem in collectionViewDropItem.dragItem } - - dropInteraction.performDropOperation(of: dragItems, with: clientContext, handlingCompletion: { didSucceed in - }) - } - } -} diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift index 01e5eec68..6cd782287 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift @@ -20,7 +20,7 @@ import UIKit import ownCloudApp import ownCloudSDK -public class CollectionViewController: UIViewController, UICollectionViewDelegate, Themeable { +open class CollectionViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDragDelegate, UICollectionViewDropDelegate, Themeable { public var clientContext: ClientContext? public var supportsHierarchicContent: Bool @@ -46,7 +46,7 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat } } - required init?(coder: NSCoder) { + required public init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -96,6 +96,7 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat public func addStacked(child viewController: UIViewController, position: StackedPosition, relativeTo: UIView? = nil) { if !usesStackViewRoot { + Log.error("Adding stacked view controllers requires a stackView root. Initialize with useStackedViewRoot:true.") return } @@ -124,6 +125,7 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat public func removeStacked(child viewController: UIViewController) { if !usesStackViewRoot { + Log.error("Removing stacked view controllers requires a stackView root. Initialize with useStackedViewRoot:true.") return } @@ -475,7 +477,48 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat return nil } - // MARK: - Drag and drop support + // MARK: - Actions Bar + open weak var actionsBarViewControllerSection: CollectionViewSection? + open var actionsBarViewController: CollectionViewController? { + willSet { + if let actionsBarViewController = actionsBarViewController { + removeStacked(child: actionsBarViewController) + } + } + + didSet { + if let actionsBarViewController = actionsBarViewController { + addStacked(child: actionsBarViewController, position: .bottom) + } + } + } + + public func showActionsBar(with datasource: OCDataSource, context: ClientContext? = nil) { + if actionsBarViewController == nil { + let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(48), heightDimension: .fractionalHeight(1)) + let item = NSCollectionLayoutItem(layoutSize: itemSize) + let actionSection = CollectionViewSection(identifier: "actions", dataSource: datasource, cellStyle: .gridCell, cellLayout: .sideways(item: item, groupSize: itemSize, edgeSpacing: NSCollectionLayoutEdgeSpacing(leading: .fixed(10), top: .fixed(0), trailing: .fixed(10), bottom: .fixed(0)), contentInsets: NSDirectionalEdgeInsets(top: 10, leading: 0, bottom: 10, trailing: 0), orthogonalScrollingBehaviour: .continuous), clientContext: clientContext) + actionSection.animateDifferences = false + let actionsViewController = CollectionViewController(context: context, sections: [ + actionSection + ]) + actionsBarViewControllerSection = actionSection + + actionsViewController.view.translatesAutoresizingMaskIntoConstraints = false + actionsViewController.view.heightAnchor.constraint(equalToConstant: 72).isActive = true + (actionsViewController.view as? UICollectionView)?.showsVerticalScrollIndicator = false + (actionsViewController.view as? UICollectionView)?.alwaysBounceVertical = false + (actionsViewController.view as? UICollectionView)?.isScrollEnabled = false + + actionsBarViewController = actionsViewController + } + } + + public func closeActionsBar() { + actionsBarViewController = nil + } + + // MARK: - Data item target redirection / re-routing public func targetedDataItem(for indexPath: IndexPath?, interaction: ClientItemInteraction) -> OCDataItem? { var item : OCDataItem? @@ -496,6 +539,116 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat return item } + // MARK: - DropTargets variables + public var dropTargetsDataSource : OCDataSource? + + // MARK: - Drag delegate + public func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { + if let item = targetedDataItem(for: indexPath, interaction: .drag), + let dragInteraction = item as? DataItemDragInteraction { + if let dragItems = dragInteraction.provideDragItems(with: clientContext) { + return dragItems + } + } + + return [] + } + + public func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] { + if let item = targetedDataItem(for: indexPath, interaction: .drag), + let dragInteraction = item as? DataItemDragInteraction { + if let dragItems = dragInteraction.provideDragItems(with: clientContext) { + return dragItems + } + } + + return [] + } + + // MARK: - Drop delegate + public func collectionView(_ collectionView: UICollectionView, canHandle session: UIDropSession) -> Bool { + if let dropTargetsProvider = clientContext?.dropTargetsProvider { + return dropTargetsProvider.canProvideDropTargets(for: session, target: collectionView) + } + + return true + } + + public func updateDropTargetsFor(_ collectionView: UICollectionView, dropSession: UIDropSession) { + if let dropTargetsProvider = clientContext?.dropTargetsProvider { + let targets = dropTargetsProvider.provideDropTargets(for: dropSession, target: collectionView) + + if let targets = targets, targets.count > 0 { + if dropTargetsDataSource == nil, actionsBarViewController == nil { + // Initialize dropTargetsDataSource, but only if actionsBarViewController == nil (=> no existing usage of actions bar) + let targetsDataSource = OCDataSourceArray() + + targetsDataSource.setVersionedItems(targets) + + dropTargetsDataSource = targetsDataSource + showActionsBar(with: targetsDataSource, context: ClientContext(with: clientContext, modifier: { context in + context.dropTargetsProvider = nil + })) + } else if let targetsDataSource = dropTargetsDataSource as? OCDataSourceArray { + // Update existing targets data source + targetsDataSource.setVersionedItems(targets) + } + } + } + } + + var lastDropProposalDestinationIndexPath : IndexPath? + var lastDropProposalDestinationIndexPathValid : Bool = false + + public func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal { + updateDropTargetsFor(collectionView, dropSession: session) + + Log.debug("Destination index path: \(String(describing: destinationIndexPath))") + + if let item = targetedDataItem(for: destinationIndexPath, interaction: .acceptDrop), + let dropInteraction = item as? DataItemDropInteraction { + if let dropProposal = dropInteraction.allowDropOperation?(for: session, with: clientContext) { + // Save last requested indexPath because UICollectionViewDropCoordinator.destinationIndexPath will only return the last hit-tested one, + // so that dropping into a cell-less region of the collection view will have UICollectionViewDropCoordinator.destinationIndexPath return + // the last hit-tested cell's indexPath - rather than (the accurate) nil + lastDropProposalDestinationIndexPath = destinationIndexPath + lastDropProposalDestinationIndexPathValid = true + return dropProposal + } + } + + lastDropProposalDestinationIndexPathValid = false + + return UICollectionViewDropProposal(operation: .forbidden, intent: .unspecified) + } + + public func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) { + if let item = targetedDataItem(for: (lastDropProposalDestinationIndexPathValid ? lastDropProposalDestinationIndexPath : coordinator.destinationIndexPath), interaction: .acceptDrop), + let dropInteraction = item as? DataItemDropInteraction { + let dragItems = coordinator.items.compactMap { collectionViewDropItem in collectionViewDropItem.dragItem } + + dropInteraction.performDropOperation(of: dragItems, with: clientContext, handlingCompletion: { didSucceed in + }) + } + } + + public func collectionView(_ collectionView: UICollectionView, dropSessionDidEnter session: UIDropSession) { + if actionsBarViewController == nil { + updateDropTargetsFor(collectionView, dropSession: session) + } + } + + public func collectionView(_ collectionView: UICollectionView, dropSessionDidEnd session: UIDropSession) { + if let dropTargetsProvider = clientContext?.dropTargetsProvider { + dropTargetsProvider.cleanupDropTargets?(for: session, target: collectionView) + + if dropTargetsDataSource != nil { + closeActionsBar() + dropTargetsDataSource = nil + } + } + } + // MARK: - Themeing public func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { if event != .initial { @@ -506,17 +659,6 @@ public class CollectionViewController: UIViewController, UICollectionViewDelegat public extension CollectionViewController { func relayout(cell: UICollectionViewCell) { -// collectionView.setCollectionViewLayout(collectionView.collectionViewLayout, animated: true, completion: nil) - collectionViewDataSource.apply(collectionViewDataSource.snapshot(), animatingDifferences: true) - -// collectionView.setNeedsLayout() -// collectionView.layoutIfNeeded() - -// if let indexPath = collectionView.indexPath(for: cell) { -// let invalidationContext = UICollectionViewLayoutInvalidationContext() -// invalidationContext.invalidateItems(at: collectionView.indexPathsForVisibleItems) -// collectionView.collectionViewLayout.invalidateLayout(with: invalidationContext) -// } } } diff --git a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift index d89b9912a..c1cc5f776 100644 --- a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift @@ -20,8 +20,7 @@ import UIKit import ownCloudSDK import ownCloudApp -public class ClientItemViewController: CollectionViewController, SortBarDelegate { - // UISearchControllerDelegate, UISearchResultsUpdating { +open class ClientItemViewController: CollectionViewController, SortBarDelegate, DropTargetsProvider { public enum ContentState : String, CaseIterable { case loading @@ -60,7 +59,8 @@ public class ClientItemViewController: CollectionViewController, SortBarDelegate var sections : [ CollectionViewSection ] = [] let itemControllerContext = ClientContext(with: inContext, modifier: { context in - context.permissionHandler = { (context, record, interaction) in + // Add permission handler limiting interactions for specific items and scenarios + context.add(permissionHandler: { (context, record, interaction) in switch interaction { case .selection: if record?.type == .drive { @@ -78,10 +78,18 @@ public class ClientItemViewController: CollectionViewController, SortBarDelegate return false + case .drag: + // Do not allow drags when in multi-selection mode + return (context?.originatingViewController as? ClientItemViewController)?.isMultiSelecting == false + + case .contextMenu: + // Do not allow context menus when in multi-selection mode + return (context?.originatingViewController as? ClientItemViewController)?.isMultiSelecting == false + default: return true } - } + }) }) itemControllerContext.postInitializationModifier = { (owner, context) in if context.openItemHandler == nil { @@ -90,6 +98,9 @@ public class ClientItemViewController: CollectionViewController, SortBarDelegate if context.moreItemHandler == nil { context.moreItemHandler = owner as? MoreItemAction } + if context.dropTargetsProvider == nil { + context.dropTargetsProvider = owner as? DropTargetsProvider + } context.query = (owner as? ClientItemViewController)?.query @@ -174,7 +185,7 @@ public class ClientItemViewController: CollectionViewController, SortBarDelegate } } - required init?(coder: NSCoder) { + required public init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -301,13 +312,7 @@ public class ClientItemViewController: CollectionViewController, SortBarDelegate let actionContext = ActionContext(viewController: originatingViewController, core: core, query: context.query, items: [item], location: actionsLocation, sender: self) let emptyFolderActions = Action.sortedApplicableActions(for: actionContext) - var actions : [OCAction] = [] - - for emptyFolderAction in emptyFolderActions { - if let action = emptyFolderAction.provideOCAction() { - actions.append(action) - } - } + let actions = emptyFolderActions.map({ action in action.provideOCAction() }) return (actions.count > 0) ? actions : nil } @@ -498,9 +503,7 @@ public class ClientItemViewController: CollectionViewController, SortBarDelegate for action in actions { action.completionHandler = actionCompletionHandler - if let ocAction = action.provideOCAction(singleVersion: true) { - actionItems.append(ocAction) - } + actionItems.append(action.provideOCAction(singleVersion: true)) } } @@ -550,88 +553,77 @@ public class ClientItemViewController: CollectionViewController, SortBarDelegate return dataItem } - // MARK: - Actions - open weak var actionsBarViewControllerSection: CollectionViewSection? - open var actionsBarViewController: CollectionViewController? { - willSet { - if let actionsBarViewController = actionsBarViewController { - removeStacked(child: actionsBarViewController) + // MARK: Drop Targets + var dropTargetsActionContext: ActionContext? + + public func canProvideDropTargets(for dropSession: UIDropSession, target: UIView) -> Bool { + for item in dropSession.items { + if item.localObject == nil, item.itemProvider.hasItemConformingToTypeIdentifier("public.folder") { + // folders can't be imported from other apps + return false + } else if let localDataItem = item.localObject as? LocalDataItem, + clientContext?.core?.bookmark.uuid != localDataItem.bookmarkUUID, + (localDataItem.dataItem as? OCItem)?.type == .collection { + // folders from other accounts can't be dropped + return false } } - didSet { - if let actionsBarViewController = actionsBarViewController { - addStacked(child: actionsBarViewController, position: .bottom) + if dropSession.localDragSession != nil { + if provideDropItems(from: dropSession, target: target).count == 0 { + return false } } + + return true } - func showActionsBar(with datasource: OCDataSource, context: ClientContext? = nil) { - if actionsBarViewController == nil { - let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(48), heightDimension: .fractionalHeight(1)) - let item = NSCollectionLayoutItem(layoutSize: itemSize) - let actionSection = CollectionViewSection(identifier: "actions", dataSource: datasource, cellStyle: .gridCell, cellLayout: .sideways(item: item, groupSize: itemSize, edgeSpacing: NSCollectionLayoutEdgeSpacing(leading: .fixed(10), top: .fixed(0), trailing: .fixed(10), bottom: .fixed(0)), contentInsets: NSDirectionalEdgeInsets(top: 10, leading: 0, bottom: 10, trailing: 0), orthogonalScrollingBehaviour: .continuous), clientContext: clientContext) - actionSection.animateDifferences = false - let actionsViewController = CollectionViewController(context: context, sections: [ - actionSection - ]) - actionsBarViewControllerSection = actionSection + public func provideDropItems(from dropSession: UIDropSession, target view: UIView) -> [OCItem] { + var items : [OCItem] = [] + var allItemsFromSameAccount = true - actionsViewController.view.translatesAutoresizingMaskIntoConstraints = false - actionsViewController.view.heightAnchor.constraint(equalToConstant: 72).isActive = true -// actionsViewController.view.widthAnchor.constraint(greaterThanOrEqualToConstant: 256).isActive = true - (actionsViewController.view as? UICollectionView)?.showsVerticalScrollIndicator = false - (actionsViewController.view as? UICollectionView)?.alwaysBounceVertical = false - (actionsViewController.view as? UICollectionView)?.isScrollEnabled = false + if let bookmarkUUID = clientContext?.core?.bookmark.uuid { + for dragItem in dropSession.items { + if let localDataItem = dragItem.localObject as? LocalDataItem { + if localDataItem.bookmarkUUID != bookmarkUUID { + allItemsFromSameAccount = false + break + } else { + if let item = localDataItem.dataItem as? OCItem { + items.append(item) + } + } + } else { + allItemsFromSameAccount = false + break + } + } + } - actionsBarViewController = actionsViewController + if !allItemsFromSameAccount { + items.removeAll() } - } - func closeActionsBar() { - actionsBarViewController = nil + return items } - // MARK: - Search - open var searchController: UISearchController? + public func provideDropTargets(for dropSession: UIDropSession, target view: UIView) -> [OCDataItem & OCDataItemVersioning]? { + let items = provideDropItems(from: dropSession, target: view) - // MARK: - Search: UISearchResultsUpdating Delegate - open func updateSearchResults(for searchController: UISearchController) { -// let searchText = searchController.searchBar.text ?? "" + if items.count > 0, let core = clientContext?.core { + dropTargetsActionContext = ActionContext(viewController: self, core: core, items: items, location: OCExtensionLocation(ofType: .action, identifier: .dropAction)) -// applySearchFilter(for: (searchText == "") ? nil : searchText, to: query) - } + if let dropTargetsActionContext = dropTargetsActionContext { + let actions = Action.sortedApplicableActions(for: dropTargetsActionContext) - open func willPresentSearchController(_ searchController: UISearchController) { -// self.sortBar?.showSelectButton = false - } + return actions.map { action in action.provideOCAction(singleVersion: true) } + } + } - open func willDismissSearchController(_ searchController: UISearchController) { -// self.sortBar?.showSelectButton = true + return nil } - open func applySearchFilter(for searchText: String?, to query: OCQuery) { - if let searchText = searchText { - let queryCondition = OCQueryCondition.fromSearchTerm(searchText) - let filterHandler: OCQueryFilterHandler = { (_, _, item) -> Bool in - if let item = item, let queryCondition = queryCondition { - return queryCondition.fulfilled(by: item) - } - return false - } - - if let filter = query.filter(withIdentifier: "text-search") { - query.updateFilter(filter, applyChanges: { filterToChange in - (filterToChange as? OCQueryFilter)?.filterHandler = filterHandler - }) - } else { - query.addFilter(OCQueryFilter.init(handler: filterHandler), withIdentifier: "text-search") - } - } else { - if let filter = query.filter(withIdentifier: "text-search") { - query.removeFilter(filter) - } - } - } + public func cleanupDropTargets(for dropSession: UIDropSession, target view: UIView) { + dropTargetsActionContext = nil + } } - diff --git a/ownCloudAppShared/Client/Context/ClientContext.swift b/ownCloudAppShared/Client/Context/ClientContext.swift index 484c97707..ee9610aae 100644 --- a/ownCloudAppShared/Client/Context/ClientContext.swift +++ b/ownCloudAppShared/Client/Context/ClientContext.swift @@ -59,6 +59,20 @@ public protocol InlineMessageCenter : AnyObject { func showInlineMessageFor(item: OCItem) } +//extension ClientContext { +// public enum DropSessionStage : CaseIterable { +// case begin +// case updated +// case end +// } +//} + +@objc public protocol DropTargetsProvider : AnyObject { + func canProvideDropTargets(for dropSession: UIDropSession, target view: UIView) -> Bool + func provideDropTargets(for dropSession: UIDropSession, target view: UIView) -> [OCDataItem & OCDataItemVersioning]? + @objc optional func cleanupDropTargets(for: UIDropSession, target view: UIView) +} + public enum ClientItemInteraction { case selection case multiselection @@ -100,8 +114,9 @@ public class ClientContext: NSObject { public weak var contextMenuProvider: ContextMenuProvider? public weak var swipeActionsProvider: SwipeActionsProvider? public weak var inlineMessageCenter: InlineMessageCenter? + public weak var dropTargetsProvider: DropTargetsProvider? - public var permissionHandler : PermissionHandler? + public var permissionHandlers : [PermissionHandler]? public var permissions : [ClientItemInteraction]? // MARK: - Post Initialization Modifier @@ -133,9 +148,10 @@ public class ClientContext: NSObject { contextMenuProvider = inParent?.contextMenuProvider swipeActionsProvider = inParent?.swipeActionsProvider inlineMessageCenter = inParent?.inlineMessageCenter + dropTargetsProvider = inParent?.dropTargetsProvider permissions = inParent?.permissions - permissionHandler = inParent?.permissionHandler + permissionHandlers = inParent?.permissionHandlers modifier?(self) } @@ -155,6 +171,14 @@ public class ClientContext: NSObject { return true } + public func add(permissionHandler: @escaping PermissionHandler) { + if permissionHandlers == nil { + permissionHandlers = [] + } + + permissionHandlers?.append(permissionHandler) + } + public func validate(interaction: ClientItemInteraction, for record: OCDataItemRecord) -> Bool { if let permissions = permissions { if !permissions.contains(interaction) { @@ -162,8 +186,17 @@ public class ClientContext: NSObject { } } - if let permissionHandler = permissionHandler { - return permissionHandler(self, record, interaction) + if let permissionHandlers = permissionHandlers { + var allowed = true + + for permissionHandler in permissionHandlers { + if !permissionHandler(self, record, interaction) { + allowed = false + break + } + } + + return allowed } return true diff --git a/ownCloudAppShared/Client/Data Item Interactions/OCAction+Interactions.swift b/ownCloudAppShared/Client/Data Item Interactions/OCAction+Interactions.swift index f10d14bb1..ccce0928f 100644 --- a/ownCloudAppShared/Client/Data Item Interactions/OCAction+Interactions.swift +++ b/ownCloudAppShared/Client/Data Item Interactions/OCAction+Interactions.swift @@ -28,3 +28,19 @@ extension OCAction : DataItemSelectionInteraction { return true } } + +extension OCAction : DataItemDropInteraction { + public func allowDropOperation(for session: UIDropSession, with context: ClientContext?) -> UICollectionViewDropProposal? { + if session.localDragSession == nil { + return nil + } + + return UICollectionViewDropProposal(operation: .move, intent: .insertIntoDestinationIndexPath) + } + + public func performDropOperation(of items: [UIDragItem], with context: ClientContext?, handlingCompletion: @escaping (Bool) -> Void) { + run(options: nil, completionHandler: { error in + handlingCompletion(error == nil) + }) + } +} diff --git a/ownCloudAppShared/Client/Data Item Interactions/OCDataItem+InteractionProtocols.swift b/ownCloudAppShared/Client/Data Item Interactions/OCDataItem+InteractionProtocols.swift index c108077a5..78647dd14 100644 --- a/ownCloudAppShared/Client/Data Item Interactions/OCDataItem+InteractionProtocols.swift +++ b/ownCloudAppShared/Client/Data Item Interactions/OCDataItem+InteractionProtocols.swift @@ -51,5 +51,5 @@ public struct LocalDataItem { @objc public protocol DataItemDropInteraction: OCDataItem { @objc optional func allowDropOperation(for session: UIDropSession, with context: ClientContext?) -> UICollectionViewDropProposal? - func performDropOperation(of items: [UIDragItem], with context: ClientContext?, handlingCompletion: (_ didSucceed: Bool) -> Void) + func performDropOperation(of items: [UIDragItem], with context: ClientContext?, handlingCompletion: @escaping (_ didSucceed: Bool) -> Void) } diff --git a/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift b/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift index e478dd409..1a08cbdb4 100644 --- a/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift +++ b/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift @@ -202,17 +202,32 @@ extension OCItem : DataItemDragInteraction { extension OCItem : DataItemDropInteraction { public func allowDropOperation(for session: UIDropSession, with context: ClientContext?) -> UICollectionViewDropProposal? { if session.localDragSession != nil { + // Prevent drop of items onto themselves - or in their existing location + if let dragItems = session.localDragSession?.items, let bookmarkUUID = context?.core?.bookmark.uuid { + for dragItem in dragItems { + if let localDataItem = dragItem.localObject as? LocalDataItem { + if let item = localDataItem.dataItem as? OCItem, localDataItem.bookmarkUUID == bookmarkUUID, item.driveID == driveID, let itemLocation = item.location { + if (item.path == path) || (itemLocation.parent.path == path) { + return UICollectionViewDropProposal(operation: .cancel, intent: .unspecified) + } + } + } + } + } + + // Return drop proposal based on item type if type == .collection { return UICollectionViewDropProposal(operation: .move, intent: .insertIntoDestinationIndexPath) } else { return UICollectionViewDropProposal(operation: .move) } } else { + // External items from other apps can only be copied into the app return UICollectionViewDropProposal(operation: .copy) } } - public func performDropOperation(of droppedItems: [UIDragItem], with context: ClientContext?, handlingCompletion: (_ didSucceed: Bool) -> Void) { + public func performDropOperation(of droppedItems: [UIDragItem], with context: ClientContext?, handlingCompletion: @escaping (_ didSucceed: Bool) -> Void) { guard let core = context?.core, type == .collection else { handlingCompletion(false) return From 152ad2f272f84e33c657a05e8da88db54c8f951c Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Tue, 28 Jun 2022 09:40:02 +0200 Subject: [PATCH 043/328] - Search - datasource-based approach - SearchViewController provides and manages the UI around search - SearchScope: - implements the actual search - provides results as datasource - can reuse existing OCQuery or its own - can provide its own cell style for search results - SortBar: rename SearchScope to SortBarSearchScope (pending removal) - SortDescriptor: encapsulate both SortMethod and SortDirection into a single, extensible object - ItemListCell: - improve support for showing/hiding more and reveal buttons - add extension to CollectionViewCellStyle for setting more/reveal button status - CollectionViewCellStyle: - refactor into a class that can - store options and type - allows basic cloning with modifications - CollectionViewController: - add item highlighting support - add support for hiding sections - add method for updating and reloading sections - ClientItemViewController: - add support for revealing items - add search support - ClientContext: - add support for property observation (with sample implementation for sortMethod and sortDirection) for var types that aren't KVO-observable - add sortDescriptor property - OCDataItem+InteractionProtocols: - add optional revealItem() method to DataItemSelectionInteraction protocol --- ios-sdk | 2 +- ownCloud.xcodeproj/project.pbxproj | 24 ++ .../Collection Views/Cells/ActionCell.swift | 2 +- .../Cells/DriveListCell.swift | 2 +- .../Collection Views/Cells/ItemListCell.swift | 58 ++++- .../CollectionViewCellConfiguration.swift | 41 ++- .../CollectionViewController.swift | 76 +++++- .../CollectionViewSection.swift | 37 ++- .../ClientItemViewController.swift | 150 +++++++++-- .../Client/Context/ClientContext.swift | 62 ++++- .../OCDataItem+InteractionProtocols.swift | 5 +- .../OCDrive+Interactions.swift | 2 +- .../OCItem+Interactions.swift | 28 ++- .../ClientQueryViewController.swift | 6 +- .../QueryFileListTableViewController.swift | 4 +- .../Client/Search/Scopes/SearchScope.swift | 218 ++++++++++++++++ .../Client/Search/SearchViewController.swift | 234 ++++++++++++++++++ .../Client/User Interface/SortBar.swift | 16 +- .../Client/User Interface/SortMethod.swift | 17 +- 19 files changed, 907 insertions(+), 77 deletions(-) create mode 100644 ownCloudAppShared/Client/Search/Scopes/SearchScope.swift create mode 100644 ownCloudAppShared/Client/Search/SearchViewController.swift diff --git a/ios-sdk b/ios-sdk index 7b70fcca7..babd83ef7 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit 7b70fcca75f513c2601cb49bba840fd7b2098d15 +Subproject commit babd83ef7d0a2592c284267315343319b380f6b8 diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 10b09ecee..70566d2e2 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -399,6 +399,8 @@ DCB458ED2604A7D4006A02AB /* OCQueryCondition+SearchSegmenter.h in Headers */ = {isa = PBXBuildFile; fileRef = DCB458EB2604A7D4006A02AB /* OCQueryCondition+SearchSegmenter.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCB458EE2604A7D4006A02AB /* OCQueryCondition+SearchSegmenter.m in Sources */ = {isa = PBXBuildFile; fileRef = DCB458EC2604A7D4006A02AB /* OCQueryCondition+SearchSegmenter.m */; }; DCB459052604AD2A006A02AB /* SearchSegmentationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DCB459042604AD2A006A02AB /* SearchSegmentationTests.m */; }; + DCB5D56B2861BEBE004AF425 /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCB5D56A2861BEBE004AF425 /* SearchViewController.swift */; }; + DCB5D5A728632C17004AF425 /* SearchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCB5D5A628632C17004AF425 /* SearchScope.swift */; }; DCB5D60B25FC14B6004C52D9 /* OCIssue+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCB5D60A25FC14B6004C52D9 /* OCIssue+Extension.swift */; }; DCB6C4D72453A6CA00C1EAE1 /* ClientAuthenticationUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCB6C4D62453A6CA00C1EAE1 /* ClientAuthenticationUpdater.swift */; }; DCB6C4DE24559B1600C1EAE1 /* ClientAuthenticationUpdaterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCB6C4DD24559B1600C1EAE1 /* ClientAuthenticationUpdaterViewController.swift */; }; @@ -1413,6 +1415,8 @@ DCB458EC2604A7D4006A02AB /* OCQueryCondition+SearchSegmenter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCQueryCondition+SearchSegmenter.m"; sourceTree = ""; }; DCB459042604AD2A006A02AB /* SearchSegmentationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SearchSegmentationTests.m; sourceTree = ""; }; DCB504D7221EF07E007638BE /* status-flash.tvg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "status-flash.tvg"; path = "img/filetypes-tvg/status-flash.tvg"; sourceTree = SOURCE_ROOT; }; + DCB5D56A2861BEBE004AF425 /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = ""; }; + DCB5D5A628632C17004AF425 /* SearchScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchScope.swift; sourceTree = ""; }; DCB5D60A25FC14B6004C52D9 /* OCIssue+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCIssue+Extension.swift"; sourceTree = ""; }; DCB6C4D62453A6CA00C1EAE1 /* ClientAuthenticationUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientAuthenticationUpdater.swift; sourceTree = ""; }; DCB6C4DD24559B1600C1EAE1 /* ClientAuthenticationUpdaterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientAuthenticationUpdaterViewController.swift; sourceTree = ""; }; @@ -1877,6 +1881,7 @@ DC46F3CF284546DA00038880 /* Data Item Interactions */, DC82664028168DAA00F91F7D /* Context */, DCEAF0842808250E00980B6D /* Collection Views */, + DCB5D5692861BE9A004AF425 /* Search */, DCA2EDDB279B0E5D001F04E6 /* Resource Sources */, DCE4E43424C199860051722F /* Actions */, DCE4E42D24C1961D0051722F /* File Lists */, @@ -2763,6 +2768,23 @@ path = templates; sourceTree = ""; }; + DCB5D5692861BE9A004AF425 /* Search */ = { + isa = PBXGroup; + children = ( + DCB5D56A2861BEBE004AF425 /* SearchViewController.swift */, + DCB5D5A828632C1B004AF425 /* Scopes */, + ); + path = Search; + sourceTree = ""; + }; + DCB5D5A828632C1B004AF425 /* Scopes */ = { + isa = PBXGroup; + children = ( + DCB5D5A628632C17004AF425 /* SearchScope.swift */, + ); + path = Scopes; + sourceTree = ""; + }; DCBF0A5E2280393A00465530 /* PDF */ = { isa = PBXGroup; children = ( @@ -4426,6 +4448,7 @@ DCE4E45124C1E4430051722F /* UIBarButtonItem+Extension.swift in Sources */, DCA2EDE2279B16F1001F04E6 /* ResourceSourceItemIcons.swift in Sources */, DCEAF08A2808254800980B6D /* DriveListCell.swift in Sources */, + DCB5D5A728632C17004AF425 /* SearchScope.swift in Sources */, DC01AF1C28411C0B00903101 /* MessageCell.swift in Sources */, DC0A357724C0E43200FB58FC /* ProgressSummarizer.swift in Sources */, 392CFEB72705831700631D2B /* LAContext+Extension.swift in Sources */, @@ -4471,6 +4494,7 @@ DC0A358724C0E44600FB58FC /* ThemeImage.swift in Sources */, DC0A355824C0E35B00FB58FC /* Synchronized.swift in Sources */, DCA2EDE4279B1789001F04E6 /* ResourceItemIcon.swift in Sources */, + DCB5D56B2861BEBE004AF425 /* SearchViewController.swift in Sources */, 0287DD7D249131E000C912CA /* AppStatistics.swift in Sources */, DC49C22128524D6C00BAA910 /* ThemeableCollectionViewCell.swift in Sources */, DC0A359924C0E6FE00FB58FC /* VendorServices.swift in Sources */, diff --git a/ownCloudAppShared/Client/Collection Views/Cells/ActionCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/ActionCell.swift index 26feb99b5..49045571d 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/ActionCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/ActionCell.swift @@ -186,7 +186,7 @@ extension ActionCell { } CollectionViewCellProvider.register(CollectionViewCellProvider(for: .action, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in - switch cellConfiguration?.style { + switch cellConfiguration?.style.type { case .gridCell: return collectionView.dequeueConfiguredReusableCell(using: gridActionCellRegistration, for: indexPath, item: itemRef) diff --git a/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift index 9b5905e6b..7ced88c96 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift @@ -183,7 +183,7 @@ extension DriveListCell { } CollectionViewCellProvider.register(CollectionViewCellProvider(for: .drive, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in - switch cellConfiguration?.style { + switch cellConfiguration?.style.type { case .header: return collectionView.dequeueConfiguredReusableCell(using: driveHeaderCellRegistration, for: indexPath, item: itemRef) diff --git a/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift index 0dfea4a26..805669d70 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift @@ -360,7 +360,7 @@ open class ItemListCell: ThemeableCollectionViewListCell { self.updateLabels(with: item) self.iconView.alpha = item.isPlaceholder ? 0.5 : 1.0 - self.moreButton.isHidden = (item.isPlaceholder || (progressView != nil)) ? true : false + self.moreButton.isHidden = (item.isPlaceholder || (progressView != nil)) ? true : !showMoreButton self.moreButton.accessibilityLabel = "Actions".localized self.moreButton.accessibilityIdentifier = (item.name != nil) ? (item.name! + " " + "Actions".localized) : "Actions".localized @@ -499,7 +499,7 @@ open class ItemListCell: ThemeableCollectionViewListCell { moreButton.isHidden = true messageButton.isHidden = true } else { - moreButton.isHidden = hasMessageForItem + moreButton.isHidden = hasMessageForItem || !showMoreButton messageButton.isHidden = !hasMessageForItem progressView?.removeFromSuperview() @@ -537,7 +537,15 @@ open class ItemListCell: ThemeableCollectionViewListCell { } // MARK: - Editing mode - open func setMoreButton(hidden:Bool, animated: Bool = false) { + var showMoreButton: Bool = true { + didSet { + if showMoreButton != oldValue { + setMoreButton(hidden: !showMoreButton, animated: false) + } + } + } + + open func setMoreButton(hidden: Bool, animated: Bool = false) { if hidden || isMoreButtonPermanentlyHidden { moreButtonWidthConstraint?.constant = 0 } else { @@ -553,10 +561,10 @@ open class ItemListCell: ThemeableCollectionViewListCell { } } - var showRevealButton : Bool = false { + var showRevealButton: Bool = false { didSet { if showRevealButton != oldValue { - self.setRevealButton(hidden: !showRevealButton, animated: false) + setRevealButton(hidden: !showRevealButton, animated: false) } } } @@ -615,6 +623,33 @@ open class ItemListCell: ThemeableCollectionViewListCell { } } +public extension CollectionViewCellStyle.StyleOptionKey { + static let showRevealButton = CollectionViewCellStyle.StyleOptionKey(rawValue: "showRevealButton") + static let showMoreButton = CollectionViewCellStyle.StyleOptionKey(rawValue: "showMoreButton") +} + +public extension CollectionViewCellStyle { + var showRevealButton : Bool { + get { + return options[.showRevealButton] as? Bool ?? false + } + + set { + options[.showRevealButton] = newValue + } + } + + var showMoreButton : Bool { + get { + return options[.showMoreButton] as? Bool ?? true + } + + set { + options[.showMoreButton] = newValue + } + } +} + extension ItemListCell { static func registerCellProvider() { let itemListCellRegistration = UICollectionView.CellRegistration { (cell, indexPath, collectionItemRef) in @@ -626,15 +661,24 @@ extension ItemListCell { cell.item = ocItem } + cell.showRevealButton = cellConfiguration.style.showRevealButton + cell.showMoreButton = cellConfiguration.style.showMoreButton + cell.accessories = [ .multiselect(), - .customView(configuration: UICellAccessory.CustomViewConfiguration(customView: cell.actionViewContainer /* UIButton(configuration: .borderedTinted()) */, placement: .trailing(displayed: .whenNotEditing))) + .customView(configuration: UICellAccessory.CustomViewConfiguration(customView: cell.actionViewContainer, placement: .trailing(displayed: .whenNotEditing))) ] }) } CollectionViewCellProvider.register(CollectionViewCellProvider(for: .item, with: { collectionView, cellConfiguration, itemRecord, itemRef, indexPath in - return collectionView.dequeueConfiguredReusableCell(using: itemListCellRegistration, for: indexPath, item: itemRef) + let cell = collectionView.dequeueConfiguredReusableCell(using: itemListCellRegistration, for: indexPath, item: itemRef) + + if cellConfiguration?.highlight == true { + cell.isHighlighted = true + } + + return cell })) } } diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift index 428eac7ea..d6f7584dd 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift @@ -19,12 +19,34 @@ import UIKit import ownCloudSDK -public enum CollectionViewCellStyle { - case header - case footer - case tableCell - case gridCell - case fillSpace +open class CollectionViewCellStyle: NSObject { + public enum StyleType { + case header + case footer + case tableCell + case gridCell + case fillSpace + } + + public struct StyleOptionKey : Hashable { + var rawValue: String + } + + public var type: StyleType + public var options: [StyleOptionKey : Any] = [:] + + public init(with type: StyleType) { + self.type = type + super.init() + } + + public convenience init(from style: CollectionViewCellStyle, changing: (CollectionViewCellStyle) -> Void) { + self.init(with: style.type) + + self.options = style.options + + changing(self) + } } public class CollectionViewCellConfiguration: NSObject { @@ -37,10 +59,13 @@ public class CollectionViewCellConfiguration: NSObject { public weak var hostViewController: CollectionViewController? public weak var clientContext: ClientContext? - public var style : CollectionViewCellStyle + public var style: CollectionViewCellStyle + + public var highlight: Bool = false - public init(source: OCDataSource? = nil, core: OCCore? = nil, collectionItemRef: CollectionViewController.ItemRef? = nil, record: OCDataItemRecord? = nil, hostViewController: CollectionViewController?, style: CollectionViewCellStyle = .tableCell, clientContext: ClientContext? = nil) { + public init(source: OCDataSource? = nil, core: OCCore? = nil, collectionItemRef: CollectionViewController.ItemRef? = nil, record: OCDataItemRecord? = nil, hostViewController: CollectionViewController?, style: CollectionViewCellStyle = .init(with: .tableCell), highlight: Bool = false, clientContext: ClientContext? = nil) { self.style = style + self.highlight = highlight super.init() diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift index 6cd782287..31eae3ef6 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift @@ -26,15 +26,21 @@ open class CollectionViewController: UIViewController, UICollectionViewDelegate, public var supportsHierarchicContent: Bool public var usesStackViewRoot: Bool - public init(context inContext: ClientContext?, sections inSections: [CollectionViewSection]?, useStackViewRoot: Bool = false, hierarchic: Bool = false) { + var highlightItemReference: OCDataItemReference? + var didHighlightItemReference: Bool = false + + public init(context inContext: ClientContext?, sections inSections: [CollectionViewSection]?, useStackViewRoot: Bool = false, hierarchic: Bool = false, highlightItemReference: OCDataItemReference? = nil) { supportsHierarchicContent = hierarchic usesStackViewRoot = useStackViewRoot + self.highlightItemReference = highlightItemReference super.init(nibName: nil, bundle: nil) inContext?.postInitialize(owner: self) - clientContext = inContext + clientContext = ClientContext(with: inContext, modifier: { context in + context.originatingViewController = self + }) if let core = clientContext?.core { self.navigationItem.title = core.bookmark.shortName @@ -240,22 +246,65 @@ open class CollectionViewController: UIViewController, UICollectionViewDelegate, var snapshot = NSDiffableDataSourceSnapshot() for section in sections { - snapshot.appendSections([section.identifier]) - - section.populate(snapshot: &snapshot) + if !section.hidden { + snapshot.appendSections([section.identifier]) + section.populate(snapshot: &snapshot) + } } collectionViewDataSource.apply(snapshot, animatingDifferences: animatingDifferences) } - public func section(at index: Int) -> CollectionViewSection? { - if (index >= 0) && (index < sections.count) { - return sections[index] + public func section(at targetIndex: Int) -> CollectionViewSection? { + if (targetIndex >= 0) && (targetIndex < sections.count) { + var index : Int = 0 + + for section in sections { + if !section.hidden { + if index == targetIndex { + return section + } + + index += 1 + } + } } return nil } + public func index(of findSection: CollectionViewSection) -> Int? { + var index : Int = 0 + + for section in sections { + if !section.hidden { + if section == findSection { + return index + } + + index += 1 + } + } + + return nil + } + + public func updateSections(with block: (_ sections: [CollectionViewSection]) -> Void, animated: Bool) { + block(sections) + updateSource(animatingDifferences: animated) + } + + public func reload(sections: [CollectionViewSection], animated: Bool) { + let reloadSectionIDs = sections.map({ section in return section.identifier }) + + if reloadSectionIDs.count > 0 { + var snapshot = collectionViewDataSource.snapshot() + snapshot.reloadSections(reloadSectionIDs) + + collectionViewDataSource.apply(snapshot, animatingDifferences: animated) + } + } + // MARK: - Item references public typealias ItemRef = NSObject public class WrappedItem : NSObject { @@ -429,7 +478,7 @@ open class CollectionViewController: UIViewController, UICollectionViewDelegate, } // Then try opening - if selectionInteraction.openItem?(in: self, with: clientContext, animated: true, pushViewController: true, completion: { [weak self] success in + if selectionInteraction.openItem?(from: self, with: clientContext, animated: true, pushViewController: true, completion: { [weak self] success in self?.collectionView.deselectItem(at: indexPath, animated: true) }) != nil { return true @@ -477,6 +526,13 @@ open class CollectionViewController: UIViewController, UICollectionViewDelegate, return nil } + // MARK: - Highlighting + public func highlight(itemRef: OCDataItemReference, animated: Bool) { + if let itemIndexPath = collectionViewDataSource.indexPath(for: itemRef) { + collectionView.scrollToItem(at: itemIndexPath, at: .centeredVertically, animated: animated) + } + } + // MARK: - Actions Bar open weak var actionsBarViewControllerSection: CollectionViewSection? open var actionsBarViewController: CollectionViewController? { @@ -497,7 +553,7 @@ open class CollectionViewController: UIViewController, UICollectionViewDelegate, if actionsBarViewController == nil { let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(48), heightDimension: .fractionalHeight(1)) let item = NSCollectionLayoutItem(layoutSize: itemSize) - let actionSection = CollectionViewSection(identifier: "actions", dataSource: datasource, cellStyle: .gridCell, cellLayout: .sideways(item: item, groupSize: itemSize, edgeSpacing: NSCollectionLayoutEdgeSpacing(leading: .fixed(10), top: .fixed(0), trailing: .fixed(10), bottom: .fixed(0)), contentInsets: NSDirectionalEdgeInsets(top: 10, leading: 0, bottom: 10, trailing: 0), orthogonalScrollingBehaviour: .continuous), clientContext: clientContext) + let actionSection = CollectionViewSection(identifier: "actions", dataSource: datasource, cellStyle: .init(with: .gridCell), cellLayout: .sideways(item: item, groupSize: itemSize, edgeSpacing: NSCollectionLayoutEdgeSpacing(leading: .fixed(10), top: .fixed(0), trailing: .fixed(10), bottom: .fixed(0)), contentInsets: NSDirectionalEdgeInsets(top: 10, leading: 0, bottom: 10, trailing: 0), orthogonalScrollingBehaviour: .continuous), clientContext: clientContext) actionSection.animateDifferences = false let actionsViewController = CollectionViewController(context: context, sections: [ actionSection diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift index 687a4a274..140d4c5a1 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift @@ -182,17 +182,30 @@ public class CollectionViewSection: NSObject { weak public var collectionViewController : CollectionViewController? - public var cellStyle : CollectionViewCellStyle //!< Use .cellConfigurationCustomizer for per-cell styling + private var _cellStyle : CollectionViewCellStyle + public var cellStyle : CollectionViewCellStyle { //!< Use .cellConfigurationCustomizer for per-cell styling + get { + return _cellStyle + } + set { + _cellStyle = newValue + + OnMainThread { + self.collectionViewController?.reload(sections: [self], animated: false) + } + } + } public var clientContext: ClientContext? public var cellConfigurationCustomizer : CellConfigurationCustomizer? public var animateDifferences: Bool? //!< If not specified, falls back to collectionViewController.animateDifferences + public var hidden : Bool = false public var cellLayout: CellLayout - public init(identifier: SectionIdentifier, dataSource inDataSource: OCDataSource?, cellStyle : CollectionViewCellStyle = .tableCell, cellLayout: CellLayout = .list(appearance: .plain), clientContext: ClientContext? = nil ) { + public init(identifier: SectionIdentifier, dataSource inDataSource: OCDataSource?, cellStyle : CollectionViewCellStyle = .init(with:.tableCell), cellLayout: CellLayout = .list(appearance: .plain), clientContext: ClientContext? = nil ) { self.identifier = identifier - self.cellStyle = cellStyle + _cellStyle = cellStyle self.cellLayout = cellLayout super.init() @@ -222,6 +235,16 @@ public class CollectionViewSection: NSObject { // MARK: - Item provider func populate(snapshot: inout NSDiffableDataSourceSnapshot) { if let datasourceSnapshot = dataSourceSubscription?.snapshotResettingChangeTracking(true) { + if let collectionViewController = collectionViewController, let highlightItemReference = collectionViewController.highlightItemReference, collectionViewController.didHighlightItemReference == false { + if datasourceSnapshot.items.contains(highlightItemReference) { + collectionViewController.didHighlightItemReference = true + + OnMainThread(after: 0.1) { + collectionViewController.highlight(itemRef: highlightItemReference, animated: true) + } + } + } + if let wrappedItems = collectionViewController?.wrap(references: datasourceSnapshot.items, forSection: identifier) { snapshot.appendItems(wrappedItems, toSection: identifier) } @@ -237,7 +260,9 @@ public class CollectionViewSection: NSObject { func provideReusableCell(for collectionView: UICollectionView, collectionItemRef: CollectionViewController.ItemRef, indexPath: IndexPath) -> UICollectionViewCell { var cell: UICollectionViewCell? - if let (dataItemRef, _) = collectionViewController?.unwrap(collectionItemRef) { + if let collectionViewController = collectionViewController { + let (dataItemRef, _) = collectionViewController.unwrap(collectionItemRef) + if let itemRecord = try? dataSource?.record(forItemRef: dataItemRef) { var cellProvider = CollectionViewCellProvider.providerFor(itemRecord) @@ -245,8 +270,10 @@ public class CollectionViewSection: NSObject { cellProvider = CollectionViewCellProvider.providerFor(.presentable) } + let doHighlight = collectionViewController.highlightItemReference == dataItemRef + if let cellProvider = cellProvider, let dataSource = dataSource { - let cellConfiguration = CollectionViewCellConfiguration(source: dataSource, core: collectionViewController?.clientContext?.core, collectionItemRef: collectionItemRef, record: itemRecord, hostViewController: collectionViewController, style: cellStyle, clientContext: clientContext) + let cellConfiguration = CollectionViewCellConfiguration(source: dataSource, core: collectionViewController.clientContext?.core, collectionItemRef: collectionItemRef, record: itemRecord, hostViewController: collectionViewController, style: cellStyle, highlight: doHighlight, clientContext: clientContext) if let cellConfigurationCustomizer = cellConfigurationCustomizer { cellConfigurationCustomizer(collectionView, cellConfiguration, itemRecord, collectionItemRef, indexPath) diff --git a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift index c1cc5f776..f23c8bd88 100644 --- a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift @@ -19,8 +19,9 @@ import UIKit import ownCloudSDK import ownCloudApp +import Intents -open class ClientItemViewController: CollectionViewController, SortBarDelegate, DropTargetsProvider { +open class ClientItemViewController: CollectionViewController, SortBarDelegate, DropTargetsProvider, SearchViewControllerDelegate, RevealItemAction { public enum ContentState : String, CaseIterable { case loading @@ -49,11 +50,12 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, public var emptySection: CollectionViewSection? public var loadingListItem : OCDataItemPresentable? + public var emptySearchResultsItem: OCDataItemPresentable? private var stateObservation : NSKeyValueObservation? private var queryRootItemObservation : NSKeyValueObservation? - public init(context inContext: ClientContext?, query inQuery: OCQuery, reveal inItem: OCItem? = nil) { + public init(context inContext: ClientContext?, query inQuery: OCQuery, highlightItemReference: OCDataItemReference? = nil) { query = inQuery var sections : [ CollectionViewSection ] = [] @@ -98,11 +100,19 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, if context.moreItemHandler == nil { context.moreItemHandler = owner as? MoreItemAction } + if context.revealItemHandler == nil { + context.revealItemHandler = owner as? RevealItemAction + } if context.dropTargetsProvider == nil { context.dropTargetsProvider = owner as? DropTargetsProvider } context.query = (owner as? ClientItemViewController)?.query + if let sortMethod = (owner as? ClientItemViewController)?.sortMethod, + let sortDirection = (owner as? ClientItemViewController)?.sortDirection { + // Set default sort descriptor + context.sortDescriptor = SortDescriptor(method: sortMethod, direction: sortDirection) + } context.originatingViewController = owner as? UIViewController } @@ -127,7 +137,7 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, driveSectionDataSource = OCDataSourceComposition(sources: [ singleDriveDatasource!, driveAdditionalItemsDataSource ]) // Create drive section from combined data source - driveSection = CollectionViewSection(identifier: "drive", dataSource: driveSectionDataSource, cellStyle: .header, cellLayout: .list(appearance: .plain)) + driveSection = CollectionViewSection(identifier: "drive", dataSource: driveSectionDataSource, cellStyle: .init(with: .header), cellLayout: .list(appearance: .plain)) } itemSectionDataSource = OCDataSourceComposition(sources: [itemsLeadInDataSource, queryResultsDatasource, itemsTrailingDataSource]) @@ -142,10 +152,10 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, } } - emptySection = CollectionViewSection(identifier: "empty", dataSource: emptyItemListDataSource, cellStyle: .fillSpace, cellLayout: .fullWidth(itemHeightDimension: .estimated(54), groupHeightDimension: .estimated(54), edgeSpacing: NSCollectionLayoutEdgeSpacing(leading: .fixed(0), top: .fixed(10), trailing: .fixed(0), bottom: .fixed(10)), contentInsets: NSDirectionalEdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20)), clientContext: itemControllerContext) + emptySection = CollectionViewSection(identifier: "empty", dataSource: emptyItemListDataSource, cellStyle: .init(with: .fillSpace), cellLayout: .fullWidth(itemHeightDimension: .estimated(54), groupHeightDimension: .estimated(54), edgeSpacing: NSCollectionLayoutEdgeSpacing(leading: .fixed(0), top: .fixed(10), trailing: .fixed(0), bottom: .fixed(10)), contentInsets: NSDirectionalEdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20)), clientContext: itemControllerContext) sections.append(emptySection!) - super.init(context: itemControllerContext, sections: sections, useStackViewRoot: true) + super.init(context: itemControllerContext, sections: sections, useStackViewRoot: true, highlightItemReference: highlightItemReference) // Track query state and recompute content state when it changes stateObservation = itemsQueryDataSource?.observe(\OCDataSource.state, options: [], changeHandler: { [weak self] query, change in @@ -178,7 +188,8 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, }, on: .main, trackDifferences: false, performIntialUpdate: true) } - query?.sortComparator = SortMethod.alphabetically.comparator(direction: .ascendant) + // Initialize sort method + handleSortMethodChange() if let navigationTitle = query?.queryLocation?.isRoot == true ? clientContext?.drive?.name : query?.queryLocation?.lastPathComponent { navigationItem.title = navigationTitle @@ -233,6 +244,10 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, viewActionButtons.append(plusBarButton) } + // Add search button + let searchButton = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(startSearch)) + viewActionButtons.append(searchButton) + self.navigationItem.rightBarButtonItems = viewActionButtons // Setup sort bar @@ -240,8 +255,8 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, sortBar?.translatesAutoresizingMaskIntoConstraints = false sortBar?.heightAnchor.constraint(equalToConstant: 40).isActive = true sortBar?.delegate = self - sortBar?.sortMethod = self.sortMethod - sortBar?.searchScope = self.searchScope + sortBar?.sortMethod = sortMethod + sortBar?.searchScope = searchScope sortBar?.showSelectButton = true itemsLeadInDataSource.setVersionedItems([ sortBar! ]) @@ -249,17 +264,6 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, // Setup multiselect collectionView.allowsSelectionDuringEditing = true collectionView.allowsMultipleSelectionDuringEditing = true - - // Setup search controller -// searchController = UISearchController(searchResultsController: nil) -// searchController?.searchResultsUpdater = self -// searchController?.obscuresBackgroundDuringPresentation = false -// searchController?.hidesNavigationBarDuringPresentation = true -// searchController?.searchBar.applyThemeCollection(Theme.shared.activeCollection) -// searchController?.delegate = self -// -// navigationItem.searchController = searchController -// navigationItem.hidesSearchBarWhenScrolling = false } public override func viewWillAppear(_ animated: Bool) { @@ -395,6 +399,7 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, open var sortMethod: SortMethod { set { UserDefaults.standard.setValue(newValue.rawValue, forKey: "sort-method") + handleSortMethodChange() } get { @@ -402,11 +407,7 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, return sort } } - open var searchScope: SearchScope = .local { - didSet { -// updateSearchPlaceholder() - } - } + open var searchScope: SortBarSearchScope = .local // only for SortBarDelegate protocol conformance open var sortDirection: SortDirection { set { UserDefaults.standard.setValue(newValue.rawValue, forKey: "sort-direction") @@ -417,6 +418,12 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, return direction } } + open func handleSortMethodChange() { + let sortDescriptor = SortDescriptor(method: sortMethod, direction: sortDirection) + + clientContext?.sortDescriptor = sortDescriptor + query?.sortComparator = sortDescriptor.comparator + } public func sortBar(_ sortBar: SortBar, didUpdateSortMethod: SortMethod) { sortMethod = didUpdateSortMethod @@ -431,7 +438,8 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, // } } - public func sortBar(_ sortBar: SortBar, didUpdateSearchScope: SearchScope) { + public func sortBar(_ sortBar: SortBar, didUpdateSearchScope: SortBarSearchScope) { + // only for SortBarDelegate protocol conformance } public func sortBar(_ sortBar: SortBar, presentViewController: UIViewController, animated: Bool, completionHandler: (() -> Void)?) { @@ -626,4 +634,96 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, public func cleanupDropTargets(for dropSession: UIDropSession, target view: UIView) { dropTargetsActionContext = nil } + + // MARK: - Reveal + public func reveal(item: OCDataItem, context: ClientContext, sender: AnyObject?) -> Bool { + if let revealInteraction = item as? DataItemSelectionInteraction { + if revealInteraction.revealItem?(from: self, with: clientContext, animated: true, pushViewController: true, completion: nil) != nil { + return true + } + } + return false + } + + // MARK: - Search + open var searchController: UISearchController? + var searchViewController: SearchViewController? + + @objc open func startSearch() { + if searchViewController == nil { + if let clientContext = clientContext, let cellStyle = itemSection?.cellStyle { + searchViewController = SearchViewController(with: clientContext, scopes: [ + // In this folder + .modifyingQuery(with: clientContext, localizedName: "Folder".localized), + + // + Folder and subfolders + // + This space + + // Account + .globalSearch(with: clientContext, cellStyle: cellStyle, localizedName: "Account".localized) + ], delegate: self) + + if let searchViewController = searchViewController { + self.addStacked(child: searchViewController, position: .top) + } + } + } + } + + func endSearch() { + if let searchViewController = searchViewController { + self.removeStacked(child: searchViewController) + } + searchResultsDataSource = nil + searchViewController = nil + } + + // MARK: - SearchViewControllerDelegate + var searchResultsDataSource: OCDataSource? { + willSet { + if let oldDataSource = searchResultsDataSource, let itemsQueryDataSource = itemsQueryDataSource, oldDataSource != itemsQueryDataSource { + itemSectionDataSource?.removeSources([ oldDataSource ]) + itemSectionDataSource?.setInclude(true, for: itemsQueryDataSource) + } + } + + didSet { + if let newDataSource = searchResultsDataSource, let itemsQueryDataSource = itemsQueryDataSource, newDataSource != itemsQueryDataSource { + itemSectionDataSource?.setInclude(false, for: itemsQueryDataSource) + itemSectionDataSource?.insertSources([ newDataSource ], after: itemsQueryDataSource) + } + } + } + + private var preSearchCellStyle : CollectionViewCellStyle? + + public func searchBegan(for viewController: SearchViewController) { + preSearchCellStyle = itemSection?.cellStyle + + updateSections(with: { sections in + self.driveSection?.hidden = true + }, animated: true) + } + + public func search(for viewController: SearchViewController, withResults resultsDataSource: OCDataSource?, style: CollectionViewCellStyle?) { + if searchResultsDataSource != resultsDataSource { + searchResultsDataSource = resultsDataSource + } + + if let style = style ?? preSearchCellStyle, style != itemSection?.cellStyle { + itemSection?.cellStyle = style + } + } + + public func searchEnded(for viewController: SearchViewController) { + updateSections(with: { sections in + self.driveSection?.hidden = false + }, animated: true) + + if let preSearchCellStyle = preSearchCellStyle { + itemSection?.cellStyle = preSearchCellStyle + } + + endSearch() + } } diff --git a/ownCloudAppShared/Client/Context/ClientContext.swift b/ownCloudAppShared/Client/Context/ClientContext.swift index ee9610aae..4f0d026a5 100644 --- a/ownCloudAppShared/Client/Context/ClientContext.swift +++ b/ownCloudAppShared/Client/Context/ClientContext.swift @@ -42,7 +42,6 @@ public protocol ActionProgressHandlerProvider : AnyObject { public protocol RevealItemAction : AnyObject { @discardableResult func reveal(item: OCDataItem, context: ClientContext, sender: AnyObject?) -> Bool - func showReveal(at path: IndexPath) -> Bool } public protocol ContextMenuProvider : AnyObject { @@ -95,7 +94,7 @@ public class ClientContext: NSObject { public var drive: OCDrive? public weak var query: OCQuery? - // MARK: - Item + // MARK: - Items public var rootItem : OCDataItem? // MARK: - UI objects @@ -116,9 +115,25 @@ public class ClientContext: NSObject { public weak var inlineMessageCenter: InlineMessageCenter? public weak var dropTargetsProvider: DropTargetsProvider? + // MARK: - Permissions public var permissionHandlers : [PermissionHandler]? public var permissions : [ClientItemInteraction]? + // MARK: - Display options + @objc public dynamic var sortDescriptor: SortDescriptor? + /* + public var sortMethod : SortMethod? { + didSet { + notifyObservers(ofChanged: .sortMethod) + } + } + public var sortDirection: SortDirection? { + didSet { + notifyObservers(ofChanged: .sortDirection) + } + } + */ + // MARK: - Post Initialization Modifier // allows postponing of a client context passed into another object until the object it is passed into is initialized and can be referenced public typealias PostInitializationModifier = (_ owner: Any?, _ context: ClientContext) -> Void @@ -150,6 +165,8 @@ public class ClientContext: NSObject { inlineMessageCenter = inParent?.inlineMessageCenter dropTargetsProvider = inParent?.dropTargetsProvider + sortDescriptor = inParent?.sortDescriptor + permissions = inParent?.permissions permissionHandlers = inParent?.permissionHandlers @@ -161,6 +178,47 @@ public class ClientContext: NSObject { postInitializationModifier = nil } + // MARK: - Change observation + // (for properties that aren't KVO-observable) + public enum Property : CaseIterable { + case sortMethod + case sortDirection + } + public typealias PropertyChangeHandler = (_ context: ClientContext, _ property: Property) -> Void + public typealias PropertyObserverUUID = UUID + struct PropertyObserver { + var properties: [Property] + var changeHandler: PropertyChangeHandler + var uuid: PropertyObserverUUID + } + var propertyObservers : [PropertyObserver] = [] + public func addObserver(for properties: [Property], initial: Bool = false, with handler: @escaping PropertyChangeHandler) -> PropertyObserverUUID { + let observer = PropertyObserver(properties: properties, changeHandler: handler, uuid: UUID()) + + if initial { + for property in properties { + observer.changeHandler(self, property) + } + } + + propertyObservers.append(observer) + + return observer.uuid + } + public func removeObserver(with uuid: PropertyObserverUUID?) { + if let uuid = uuid { + propertyObservers.removeAll(where: { observer in (observer.uuid == uuid) }) + } + } + public func notifyObservers(ofChanged property: Property) { + for observer in propertyObservers { + if observer.properties.contains(property) { + observer.changeHandler(self, property) + } + } + } + + // MARK: - Permissions public func hasPermission(for interaction: ClientItemInteraction) -> Bool { if let permissions = permissions { if !permissions.contains(interaction) { diff --git a/ownCloudAppShared/Client/Data Item Interactions/OCDataItem+InteractionProtocols.swift b/ownCloudAppShared/Client/Data Item Interactions/OCDataItem+InteractionProtocols.swift index 78647dd14..8f6f04e48 100644 --- a/ownCloudAppShared/Client/Data Item Interactions/OCDataItem+InteractionProtocols.swift +++ b/ownCloudAppShared/Client/Data Item Interactions/OCDataItem+InteractionProtocols.swift @@ -25,7 +25,10 @@ import ownCloudSDK @objc optional func handleSelection(in viewController: UIViewController?, with context: ClientContext?, completion: ((_ success: Bool) -> Void)?) -> Bool // "Open" the item: suitable when pushing view controllers that should be restorable - @objc optional func openItem(in viewController: UIViewController?, with context: ClientContext?, animated: Bool, pushViewController: Bool, completion: ((_ success: Bool) -> Void)?) -> UIViewController? + @objc optional func openItem(from viewController: UIViewController?, with context: ClientContext?, animated: Bool, pushViewController: Bool, completion: ((_ success: Bool) -> Void)?) -> UIViewController? + + // "Reveal" the item + @objc @discardableResult optional func revealItem(from viewController: UIViewController?, with context: ClientContext?, animated: Bool, pushViewController: Bool, completion: ((_ success: Bool) -> Void)?) -> UIViewController? } // MARK: - Swipe Actions diff --git a/ownCloudAppShared/Client/Data Item Interactions/OCDrive+Interactions.swift b/ownCloudAppShared/Client/Data Item Interactions/OCDrive+Interactions.swift index 64d3f9e69..e92261aac 100644 --- a/ownCloudAppShared/Client/Data Item Interactions/OCDrive+Interactions.swift +++ b/ownCloudAppShared/Client/Data Item Interactions/OCDrive+Interactions.swift @@ -22,7 +22,7 @@ import ownCloudApp // MARK: - Selection > Open extension OCDrive : DataItemSelectionInteraction { - public func openItem(in viewController: UIViewController?, with context: ClientContext?, animated: Bool, pushViewController: Bool, completion: ((Bool) -> Void)?) -> UIViewController? { + public func openItem(from viewController: UIViewController?, with context: ClientContext?, animated: Bool, pushViewController: Bool, completion: ((Bool) -> Void)?) -> UIViewController? { let driveContext = ClientContext(with: context, modifier: { context in context.drive = self }) diff --git a/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift b/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift index 1a08cbdb4..efb9646d8 100644 --- a/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift +++ b/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift @@ -23,7 +23,7 @@ import UniformTypeIdentifiers // MARK: - Selection > Open extension OCItem : DataItemSelectionInteraction { - public func openItem(in viewController: UIViewController?, with context: ClientContext?, animated: Bool, pushViewController: Bool, completion: ((Bool) -> Void)?) -> UIViewController? { + public func openItem(from viewController: UIViewController?, with context: ClientContext?, animated: Bool, pushViewController: Bool, completion: ((Bool) -> Void)?) -> UIViewController? { if let context = context, let core = context.core { let item = self @@ -63,6 +63,32 @@ extension OCItem : DataItemSelectionInteraction { return nil } + + public func revealItem(from viewController: UIViewController?, with context: ClientContext?, animated: Bool, pushViewController: Bool, completion: ((_ success: Bool) -> Void)?) -> UIViewController? { + if let context = context, let core = context.core { + let activity = OpenItemUserActivity(detailItem: self, detailBookmark: core.bookmark) + viewController?.view.window?.windowScene?.userActivity = activity.openItemUserActivity + + if let parentLocation = location?.parent { + let query = OCQuery(for: parentLocation) + DisplaySettings.shared.updateQuery(withDisplaySettings: query) + + let queryViewController = ClientItemViewController(context: context, query: query, highlightItemReference: self.dataItemReference) + + if pushViewController { + context.navigationController?.pushViewController(queryViewController, animated: animated) + } + + completion?(true) + + return queryViewController + } + } + + completion?(false) + + return nil + } } // MARK: - Swipe diff --git a/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift b/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift index 61f3b8c7e..7e210e1c4 100644 --- a/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift +++ b/ownCloudAppShared/Client/File Lists/ClientQueryViewController.swift @@ -80,13 +80,13 @@ open class ClientQueryViewController: QueryFileListTableViewController, UIDropIn return customSearchQuery?.queryResults?.count ?? 0 > 0 } - open override var searchScope: SearchScope { + open override var searchScope: SortBarSearchScope { set { UserDefaults.standard.setValue(newValue.rawValue, forKey: "search-scope") } get { - let scope = SearchScope(rawValue: UserDefaults.standard.integer(forKey: "search-scope")) ?? SearchScope.local + let scope = SortBarSearchScope(rawValue: UserDefaults.standard.integer(forKey: "search-scope")) ?? SortBarSearchScope.local return scope } } @@ -186,7 +186,7 @@ open class ClientQueryViewController: QueryFileListTableViewController, UIDropIn updateCustomSearchQuery() } - open override func sortBar(_ sortBar: SortBar, didUpdateSearchScope: SearchScope) { + open override func sortBar(_ sortBar: SortBar, didUpdateSearchScope: SortBarSearchScope) { updateCustomSearchQuery() } diff --git a/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift b/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift index 8c4f99c9c..1a4787e10 100644 --- a/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift +++ b/ownCloudAppShared/Client/File Lists/QueryFileListTableViewController.swift @@ -117,7 +117,7 @@ open class QueryFileListTableViewController: FileListTableViewController, SortBa return sort } } - open var searchScope: SearchScope = .local { + open var searchScope: SortBarSearchScope = .local { didSet { updateSearchPlaceholder() } @@ -512,7 +512,7 @@ open class QueryFileListTableViewController: FileListTableViewController, SortBa return 0 } - open func sortBar(_ sortBar: SortBar, didUpdateSearchScope: SearchScope) { + open func sortBar(_ sortBar: SortBar, didUpdateSearchScope: SortBarSearchScope) { } open func toggleSelectMode() { diff --git a/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift b/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift new file mode 100644 index 000000000..d574ff5dd --- /dev/null +++ b/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift @@ -0,0 +1,218 @@ +// +// SearchScope.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 22.06.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK + +open class SearchScope: NSObject { + public var localizedName : String + + @objc public dynamic var results: OCDataSource? + @objc public dynamic var resultsCellStyle: CollectionViewCellStyle? + + public var isSelected: Bool = false + + public var clientContext: ClientContext + + static public func modifyingQuery(with context: ClientContext, localizedName: String) -> SearchScope { + return QueryModifyingSearchScope(with: context, cellStyle: nil, localizedName: localizedName) + } + + static public func globalSearch(with context: ClientContext, cellStyle: CollectionViewCellStyle, localizedName: String) -> SearchScope { + let revealCellStyle = CollectionViewCellStyle(from: cellStyle, changing: { cellStyle in + cellStyle.showRevealButton = true + cellStyle.showMoreButton = false + }) + + return CustomQuerySearchScope(with: context, cellStyle: revealCellStyle, localizedName: localizedName) + } + + public init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String) { + clientContext = context + localizedName = name + + super.init() + + resultsCellStyle = cellStyle + } + + open func updateForSearchTerm(_ term: String?) { + + } +} + +open class ItemSearchScope : SearchScope { + // private var sortMethodPropertyObserver: ClientContext.PropertyObserverUUID? + private var sortDescriptorObserver: NSKeyValueObservation? + + public override init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String) { + super.init(with: context, cellStyle: cellStyle, localizedName: name) + + sortDescriptorObserver = context.observe(\.sortDescriptor, changeHandler: { [weak self] context, change in + self?.sortDescriptorChanged(to: context.sortDescriptor) + }) + +// sortMethodPropertyObserver = context.addObserver(for: [.sortMethod], with: { [weak self] context, property in +// self?.sortMethodChanged(to: context.sortMethod) +// }) + } + + deinit { + sortDescriptorObserver?.invalidate() +// clientContext.removeObserver(with: sortMethodPropertyObserver) + } + + open func sortDescriptorChanged(to sortDescriptor: SortDescriptor?) { + } + + open var queryCondition: OCQueryCondition? + + open override var isSelected: Bool { + didSet { + if !isSelected { + queryCondition = nil + results = nil + } + } + } + + open var searchTerm: String? + + open override func updateForSearchTerm(_ term: String?) { + if isSelected { + searchTerm = term + + if let searchText = term { + queryCondition = OCQueryCondition.fromSearchTerm(searchText) + } else { + queryCondition = nil + } + } + } +} + +open class QueryModifyingSearchScope : ItemSearchScope { + public override var isSelected: Bool { + didSet { + if let query = clientContext.query { + if isSelected { + // Modify existing query provided via clientContext + results = query.queryResultsDataSource + } + } + } + } + + open override var queryCondition: OCQueryCondition? { + didSet { + let queryCondition = queryCondition + + if let query = clientContext.query { + if queryCondition != nil { + let filterHandler: OCQueryFilterHandler = { (_, _, item) -> Bool in + if let item = item, let queryCondition = queryCondition { + return queryCondition.fulfilled(by: item) + } + return false + } + + if let filter = query.filter(withIdentifier: "text-search") { + query.updateFilter(filter, applyChanges: { filterToChange in + (filterToChange as? OCQueryFilter)?.filterHandler = filterHandler + }) + } else { + query.addFilter(OCQueryFilter(handler: filterHandler), withIdentifier: "text-search") + } + } else { + if let filter = query.filter(withIdentifier: "text-search") { + query.removeFilter(filter) + } + } + } + } + } +} + +open class CustomQuerySearchScope : ItemSearchScope { + private let maxResultCountDefault = 100 // Maximum number of results to return from database (default) + private var maxResultCount = 100 // Maximum number of results to return from database (flexible) + + public override var isSelected: Bool { + didSet { + if isSelected { + // Modify existing query provided via clientContext + results = customQuery?.queryResultsDataSource + } + } + } + + public var customQuery: OCQuery? { + willSet { + if let core = clientContext.core, let oldQuery = customQuery { + core.stop(oldQuery) + } + } + + didSet { + if let core = clientContext.core, let newQuery = customQuery { + core.start(newQuery) + + results = newQuery.queryResultsDataSource + } else { + results = nil + } + } + } + + private var lastSearchTerm : String? + private var scrollToTopWithNextRefresh : Bool = false + + public func updateCustomSearchQuery() { + if lastSearchTerm != searchTerm { + // Reset max result count when search text changes + maxResultCount = maxResultCountDefault + lastSearchTerm = searchTerm + + // Scroll to top when search text changes + scrollToTopWithNextRefresh = true + } + + if let condition = queryCondition { + if let sortDescriptor = clientContext.sortDescriptor { + condition.sortBy = sortDescriptor.method.sortPropertyName + condition.sortAscending = sortDescriptor.direction != .ascendant + } + + condition.maxResultCount = NSNumber(value: maxResultCount) + + customQuery = OCQuery(condition:condition, inputFilter: nil) + } else { + customQuery = nil + } + } + + open override var queryCondition: OCQueryCondition? { + didSet { + updateCustomSearchQuery() + } + } + + open override func sortDescriptorChanged(to sortDescriptor: SortDescriptor?) { + updateCustomSearchQuery() + } +} diff --git a/ownCloudAppShared/Client/Search/SearchViewController.swift b/ownCloudAppShared/Client/Search/SearchViewController.swift new file mode 100644 index 000000000..e11d7fc9f --- /dev/null +++ b/ownCloudAppShared/Client/Search/SearchViewController.swift @@ -0,0 +1,234 @@ +// +// SearchViewController.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 21.06.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK + +public protocol SearchViewControllerDelegate : AnyObject { + func searchBegan(for viewController: SearchViewController) + func search(for viewController: SearchViewController, withResults: OCDataSource?, style: CollectionViewCellStyle?) + func searchEnded(for viewController: SearchViewController) +} + +open class SearchViewController: UIViewController, UITextFieldDelegate, Themeable { + private var resultsSourceObservation: NSKeyValueObservation? + private var resultsCellStyleObservation: NSKeyValueObservation? + + open var clientContext: ClientContext + + open weak var delegate: SearchViewControllerDelegate? + + open var scopes: [SearchScope]? { + didSet { + updateSegmentsFromScopes() + } + } + + open var activeScope: SearchScope? { + willSet { + if activeScope != newValue { + activeScope?.isSelected = false + + resultsSourceObservation?.invalidate() + resultsSourceObservation = nil + + resultsCellStyleObservation?.invalidate() + resultsCellStyleObservation = nil + } + } + + didSet { + if let activeScope = activeScope, let scopeIndex = scopes?.firstIndex(of: activeScope) { + OnMainThread { + self.scopeView.selectedSegmentIndex = scopeIndex + } + } + + if activeScope != oldValue { + activeScope?.isSelected = true + + resultsSourceObservation = activeScope?.observe(\.results, options: .initial, changeHandler: { [weak self] (scope, change) in + if let self = self { + self.delegate?.search(for: self, withResults: self.activeScope?.results, style: self.activeScope?.resultsCellStyle) + } + }) + + resultsCellStyleObservation = activeScope?.observe(\.resultsCellStyle, changeHandler: { [weak self] (scope, change) in + if let self = self { + self.delegate?.search(for: self, withResults: self.activeScope?.results, style: self.activeScope?.resultsCellStyle) + } + }) + + sendSearchFieldContentsToActiveScope() + } + } + } + + private func updateSegmentsFromScopes() { + scopeView.removeAllSegments() + + if let scopes = scopes { + for scope in scopes { + scopeView.insertSegment(withTitle: scope.localizedName, at: scopeView.numberOfSegments, animated: false) + } + } + } + + init(with clientContext: ClientContext, scopes: [SearchScope]?, targetNavigationItem: UINavigationItem? = nil, delegate: SearchViewControllerDelegate?) { + self.clientContext = clientContext + + super.init(nibName: nil, bundle: nil) + + self.targetNavigationItem = targetNavigationItem ?? clientContext.originatingViewController?.navigationItem + self.delegate = delegate + + self.scopes = scopes + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + Theme.shared.unregister(client: self) + } + + // MARK: - Views + var searchField: UISearchTextField = UISearchTextField() + var scopeView: UISegmentedControl = UISegmentedControl() + + open override func loadView() { + let rootView = UIView() + + scopeView.translatesAutoresizingMaskIntoConstraints = false + scopeView.addAction(UIAction(handler: { [weak self] action in + guard let self = self, let scopes = self.scopes else { + return + } + + let selectedIndex = self.scopeView.selectedSegmentIndex + + if selectedIndex >= 0, selectedIndex < scopes.count { + self.activeScope = scopes[selectedIndex] + } + }), for: .valueChanged) + rootView.addSubview(scopeView) + + NSLayoutConstraint.activate([ + scopeView.topAnchor.constraint(equalTo: rootView.safeAreaLayoutGuide.topAnchor, constant: 5), + scopeView.leadingAnchor.constraint(equalTo: rootView.safeAreaLayoutGuide.leadingAnchor, constant: 10), + scopeView.trailingAnchor.constraint(equalTo: rootView.safeAreaLayoutGuide.trailingAnchor, constant: -10), + scopeView.bottomAnchor.constraint(equalTo: rootView.safeAreaLayoutGuide.bottomAnchor, constant: -10), + + searchField.widthAnchor.constraint(equalToConstant: 10000).with(priority: .defaultHigh) // maximize width of searchField in UINavigationBar + ]) + + view = rootView + } + + open override func viewDidLoad() { + super.viewDidLoad() + Theme.shared.register(client: self, applyImmediately: true) + + searchField.translatesAutoresizingMaskIntoConstraints = false + searchField.addTarget(self, action: #selector(searchFieldContentsChanged), for: .editingChanged) + searchField.delegate = self + + injectIntoNavigationItem() + + updateSegmentsFromScopes() + + delegate?.searchBegan(for: self) + + if activeScope == nil { + activeScope = scopes?.first + } + } + + open override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + OnMainThread { + self.searchField.becomeFirstResponder() + } + } + + // MARK: - Navigation item modification + var targetNavigationItem: UINavigationItem? + + var niInjected: Bool = false + var niTitleView: UIView? + var niRightBarButtonItems: [UIBarButtonItem]? + var niHidesBackButton: Bool = false + + func injectIntoNavigationItem() { + if !niInjected, let targetNavigationItem = targetNavigationItem { + // Store content + niTitleView = targetNavigationItem.titleView + niRightBarButtonItems = targetNavigationItem.rightBarButtonItems + niHidesBackButton = targetNavigationItem.hidesBackButton + + // Overwrite content + targetNavigationItem.titleView = searchField + + let cancelToolbarButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(endSearch)) + targetNavigationItem.rightBarButtonItems = [ cancelToolbarButton ] + targetNavigationItem.hidesBackButton = true + + niInjected = true + } + } + + func restoreNavigationItem() { + if niInjected, let targetNavigationItem = targetNavigationItem { + // Restore content + targetNavigationItem.titleView = niTitleView + targetNavigationItem.rightBarButtonItems = niRightBarButtonItems + targetNavigationItem.hidesBackButton = niHidesBackButton + niInjected = false + } + } + + // MARK: - Input handling + public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + return true + } + + @objc func searchFieldContentsChanged() { + sendSearchFieldContentsToActiveScope() + } + + func sendSearchFieldContentsToActiveScope() { + let searchText = searchField.text + self.activeScope?.updateForSearchTerm((searchText != "") ? searchText : nil) + } + + // MARK: - End search + @objc func endSearch() { + activeScope = nil + + restoreNavigationItem() + + delegate?.search(for: self, withResults: nil, style: nil) + delegate?.searchEnded(for: self) + } + + // MARK: - Theme support + public func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { + self.view.backgroundColor = collection.navigationBarColors.backgroundColor + } +} diff --git a/ownCloudAppShared/Client/User Interface/SortBar.swift b/ownCloudAppShared/Client/User Interface/SortBar.swift index eb5e813ea..042c23326 100644 --- a/ownCloudAppShared/Client/User Interface/SortBar.swift +++ b/ownCloudAppShared/Client/User Interface/SortBar.swift @@ -35,7 +35,7 @@ public class SegmentedControl: UISegmentedControl { } } -public enum SearchScope : Int, CaseIterable { +public enum SortBarSearchScope : Int, CaseIterable { case global case local @@ -55,11 +55,11 @@ public protocol SortBarDelegate: AnyObject { var sortDirection: SortDirection { get set } var sortMethod: SortMethod { get set } - var searchScope: SearchScope { get set } + var searchScope: SortBarSearchScope { get set } func sortBar(_ sortBar: SortBar, didUpdateSortMethod: SortMethod) - func sortBar(_ sortBar: SortBar, didUpdateSearchScope: SearchScope) + func sortBar(_ sortBar: SortBar, didUpdateSearchScope: SortBarSearchScope) func sortBar(_ sortBar: SortBar, presentViewController: UIViewController, animated: Bool, completionHandler: (() -> Void)?) @@ -75,7 +75,7 @@ public class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate } // MARK: - Constants - let sideButtonsSize: CGSize = CGSize(width: 44.0, height: 44.0) + let sideButtonsSize: CGSize = CGSize(width: 40.0, height: 40.0) let leftPadding: CGFloat = 16.0 let rightPadding: CGFloat = 20.0 let rightSelectButtonPadding: CGFloat = 8.0 @@ -117,7 +117,7 @@ public class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate // Woraround for Accessibility: remove all elements, when element is hidden, otherwise the elements are still available for accessibility if oldValue == false { - for scope in SearchScope.allCases { + for scope in SortBarSearchScope.allCases { searchScopeSegmentedControl?.insertSegment(withTitle: scope.label, at: scope.rawValue, animated: false) } searchScopeSegmentedControl?.selectedSegmentIndex = searchScope.rawValue @@ -155,7 +155,7 @@ public class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate } } - public var searchScope : SearchScope { + public var searchScope : SortBarSearchScope { didSet { delegate?.searchScope = searchScope searchScopeSegmentedControl?.selectedSegmentIndex = searchScope.rawValue @@ -164,7 +164,7 @@ public class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate // MARK: - Init & Deinit - public init(frame: CGRect = .zero, sortMethod: SortMethod, searchScope: SearchScope = .local) { + public init(frame: CGRect = .zero, sortMethod: SortMethod, searchScope: SortBarSearchScope = .local) { selectButton = UIButton() sortButton = UIButton(type: .system) searchScopeSegmentedControl = SegmentedControl() @@ -292,7 +292,7 @@ public class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate // MARK: - Actions @objc private func searchScopeValueChanged() { if let selectedIndex = searchScopeSegmentedControl?.selectedSegmentIndex { - self.searchScope = SearchScope(rawValue: selectedIndex)! + self.searchScope = SortBarSearchScope(rawValue: selectedIndex)! delegate?.sortBar(self, didUpdateSearchScope: self.searchScope) } } diff --git a/ownCloudAppShared/Client/User Interface/SortMethod.swift b/ownCloudAppShared/Client/User Interface/SortMethod.swift index 1dc5e0ca3..7f6a4cfa3 100644 --- a/ownCloudAppShared/Client/User Interface/SortMethod.swift +++ b/ownCloudAppShared/Client/User Interface/SortMethod.swift @@ -28,7 +28,6 @@ public enum SortDirection: Int { } public enum SortMethod: Int { - case alphabetically = 0 case kind = 1 case size = 2 @@ -212,3 +211,19 @@ public enum SortMethod: Int { return combinedComparator ?? comparator } } + +public class SortDescriptor: NSObject { + public var method: SortMethod + public var direction: SortDirection + + public init(method inMethod: SortMethod, direction inDirection: SortDirection) { + method = inMethod + direction = inDirection + + super.init() + } + + public var comparator: OCSort { + return method.comparator(direction: direction) + } +} From ea172622053c0a65706941beef90e1aae43e974c Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Wed, 29 Jun 2022 12:10:03 +0200 Subject: [PATCH 044/328] - ItemListCell: - use backgroundConfiguration instead of backgroundView - turn reveal button into a proper accessory for proper layout - cleanup layout and animation code - add support for reveal highlighting - SearchScope - add "Show more items" functionality to CustomQuerySearchScope --- ios-sdk | 2 +- .../Collection Views/Cells/ItemListCell.swift | 145 +++++++----------- .../Client/Search/Scopes/SearchScope.swift | 57 +++++-- 3 files changed, 100 insertions(+), 104 deletions(-) diff --git a/ios-sdk b/ios-sdk index babd83ef7..adea1a552 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit babd83ef7d0a2592c284267315343319b380f6b8 +Subproject commit adea1a55209069502d392b80019bfec908c5161e diff --git a/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift index 805669d70..c7a0092a4 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift @@ -29,8 +29,7 @@ open class ItemListCell: ThemeableCollectionViewListCell { private let smallSpacing : CGFloat = 2 private let iconViewWidth : CGFloat = 40 private let detailIconViewHeight : CGFloat = 15 - private let moreButtonWidth : CGFloat = 60 - private let revealButtonWidth : CGFloat = 35 + private let accessoryButtonWidth : CGFloat = 35 private let verticalLabelMarginFromCenter : CGFloat = 2 private let iconSize : CGSize = CGSize(width: 40, height: 40) private let thumbnailSize : CGSize = CGSize(width: 60, height: 60) // when changing size, also update .iconView/fallbackSize @@ -49,13 +48,14 @@ open class ItemListCell: ThemeableCollectionViewListCell { open var publicLinkStatusIconView : UIImageView = UIImageView() open var actionViewContainer : UIView = UIView() + open var actionAccessory: UICellAccessory? open var moreButton : UIButton = UIButton() open var messageButton : UIButton = UIButton() - open var revealButton : UIButton = UIButton() open var progressView : ProgressView? - open var moreButtonWidthConstraint : NSLayoutConstraint? - open var revealButtonWidthConstraint : NSLayoutConstraint? + open var revealButtonContainer: UIView = UIView() + open var revealButton : UIButton = UIButton(frame: CGRect(x: 0, y: 0, width: 40, height: 40)) + open var revealButtonAccessory: UICellAccessory? open var sharedStatusIconViewZeroWidthConstraint : NSLayoutConstraint? open var publicLinkStatusIconViewZeroWidthConstraint : NSLayoutConstraint? @@ -71,11 +71,7 @@ open class ItemListCell: ThemeableCollectionViewListCell { open var isMoreButtonPermanentlyHidden = false { didSet { - if isMoreButtonPermanentlyHidden { - moreButtonWidthConstraint?.constant = 0 - } else { - moreButtonWidthConstraint?.constant = showRevealButton ? revealButtonWidth : moreButtonWidth - } + updateAccessories() } } @@ -95,16 +91,6 @@ open class ItemListCell: ThemeableCollectionViewListCell { super.init(frame: frame) prepareViewAndConstraints() - //! - /* - self.multipleSelectionBackgroundView = { - let blankView = UIView(frame: CGRect.zero) - blankView.backgroundColor = UIColor.clear - blankView.layer.masksToBounds = true - return blankView - }() - */ - NotificationCenter.default.addObserver(self, selector: #selector(updateAvailableOfflineStatus(_:)), name: .OCCoreItemPoliciesChanged, object: OCItemPolicyKind.availableOffline) NotificationCenter.default.addObserver(self, selector: #selector(updateHasMessage(_:)), name: .ClientSyncRecordIDsWithMessagesChanged, object: nil) @@ -126,7 +112,6 @@ open class ItemListCell: ThemeableCollectionViewListCell { } func prepareViewAndConstraints() { - // cell.content setup titleLabel.translatesAutoresizingMaskIntoConstraints = false detailLabel.translatesAutoresizingMaskIntoConstraints = false @@ -227,9 +212,10 @@ open class ItemListCell: ThemeableCollectionViewListCell { messageButton.translatesAutoresizingMaskIntoConstraints = false actionViewContainer.addSubview(moreButton) - actionViewContainer.addSubview(revealButton) actionViewContainer.addSubview(messageButton) + revealButtonContainer.addSubview(revealButton) + moreButton.setImage(UIImage(named: "more-dots"), for: .normal) moreButton.contentMode = .center moreButton.isPointerInteractionEnabled = true @@ -237,7 +223,6 @@ open class ItemListCell: ThemeableCollectionViewListCell { revealButton.setImage(UIImage(systemName: "arrow.right.circle.fill"), for: .normal) revealButton.isPointerInteractionEnabled = true revealButton.contentMode = .center - revealButton.isHidden = !showRevealButton revealButton.accessibilityLabel = "Reveal in folder".localized messageButton.setTitle("⚠️", for: .normal) @@ -249,21 +234,20 @@ open class ItemListCell: ThemeableCollectionViewListCell { revealButton.addTarget(self, action: #selector(revealButtonTapped), for: .touchUpInside) messageButton.addTarget(self, action: #selector(messageButtonTapped), for: .touchUpInside) - moreButtonWidthConstraint = moreButton.widthAnchor.constraint(equalToConstant: showRevealButton ? revealButtonWidth : moreButtonWidth) - revealButtonWidthConstraint = revealButton.widthAnchor.constraint(equalToConstant: showRevealButton ? revealButtonWidth : 0) - NSLayoutConstraint.activate([ + revealButton.topAnchor.constraint(equalTo: revealButtonContainer.topAnchor), + revealButton.bottomAnchor.constraint(equalTo: revealButtonContainer.bottomAnchor), + revealButton.leadingAnchor.constraint(equalTo: revealButtonContainer.leadingAnchor), + revealButton.trailingAnchor.constraint(equalTo: revealButtonContainer.trailingAnchor), + + revealButton.widthAnchor.constraint(equalToConstant: accessoryButtonWidth), + moreButton.topAnchor.constraint(equalTo: actionViewContainer.topAnchor), moreButton.bottomAnchor.constraint(equalTo: actionViewContainer.bottomAnchor), moreButton.leadingAnchor.constraint(equalTo: actionViewContainer.leadingAnchor), - moreButton.trailingAnchor.constraint(equalTo: revealButton.leadingAnchor), + moreButton.trailingAnchor.constraint(equalTo: actionViewContainer.trailingAnchor), - revealButton.topAnchor.constraint(equalTo: actionViewContainer.topAnchor), - revealButton.bottomAnchor.constraint(equalTo: actionViewContainer.bottomAnchor), - revealButton.trailingAnchor.constraint(equalTo: actionViewContainer.trailingAnchor), - - moreButtonWidthConstraint!, - revealButtonWidthConstraint!, + moreButton.widthAnchor.constraint(equalToConstant: accessoryButtonWidth), messageButton.topAnchor.constraint(equalTo: moreButton.topAnchor), messageButton.bottomAnchor.constraint(equalTo: moreButton.bottomAnchor), @@ -271,7 +255,16 @@ open class ItemListCell: ThemeableCollectionViewListCell { messageButton.trailingAnchor.constraint(equalTo: moreButton.trailingAnchor) ]) + let backgroundConfig = UIBackgroundConfiguration.listPlainCell() + backgroundConfiguration = backgroundConfig + self.accessibilityElements = [titleLabel, detailLabel, moreButton, revealButton, messageButton] + + actionAccessory = .customView(configuration: UICellAccessory.CustomViewConfiguration(customView: actionViewContainer, placement: .trailing(displayed: .whenNotEditing))) + + revealButtonAccessory = .customView(configuration: UICellAccessory.CustomViewConfiguration(customView: revealButtonContainer, placement: .trailing(displayed: .whenNotEditing))) + + updateAccessories() } // MARK: - Present item @@ -510,12 +503,21 @@ open class ItemListCell: ThemeableCollectionViewListCell { // MARK: - Themeing open var revealHighlight : Bool = false { didSet { - if revealHighlight { - Log.debug("Highlighted!") - } + setNeedsUpdateConfiguration() + } + } + + open override func updateConfiguration(using state: UICellConfigurationState) { + let collection = Theme.shared.activeCollection + var backgroundConfig = backgroundConfiguration?.updated(for: state) - applyThemeCollectionToCellContents(theme: Theme.shared, collection: Theme.shared.activeCollection, state: ThemeItemState(selected: isSelected)) + if state.isHighlighted || state.isSelected || (state.cellDropState == .targeted) || revealHighlight { + backgroundConfig?.backgroundColor = collection.tableRowHighlightColors.backgroundColor?.withAlphaComponent(0.5) + } else { + backgroundConfig?.backgroundColor = collection.tableBackgroundColor } + + backgroundConfiguration = backgroundConfig } open override func applyThemeCollectionToCellContents(theme: Theme, collection: ThemeCollection, state: ThemeItemState) { @@ -529,71 +531,37 @@ open class ItemListCell: ThemeableCollectionViewListCell { moreButton.tintColor = collection.tableRowColors.secondaryLabelColor - if revealHighlight { - self.backgroundView?.backgroundColor = collection.tableRowHighlightColors.backgroundColor?.withAlphaComponent(0.5) - } else { - self.backgroundView?.backgroundColor = collection.tableBackgroundColor - } + setNeedsUpdateConfiguration() } // MARK: - Editing mode var showMoreButton: Bool = true { didSet { - if showMoreButton != oldValue { - setMoreButton(hidden: !showMoreButton, animated: false) - } - } - } - - open func setMoreButton(hidden: Bool, animated: Bool = false) { - if hidden || isMoreButtonPermanentlyHidden { - moreButtonWidthConstraint?.constant = 0 - } else { - moreButtonWidthConstraint?.constant = showRevealButton ? revealButtonWidth : moreButtonWidth - } - moreButton.isHidden = ((item?.isPlaceholder == true) || (progressView != nil)) ? true : hidden - if animated { - UIView.animate(withDuration: 0.25) { - self.contentView.layoutIfNeeded() - } - } else { - self.contentView.layoutIfNeeded() + updateAccessories() } } var showRevealButton: Bool = false { didSet { - if showRevealButton != oldValue { - setRevealButton(hidden: !showRevealButton, animated: false) - } + updateAccessories() } } - open func setRevealButton(hidden:Bool, animated: Bool = false) { - if hidden { - revealButtonWidthConstraint?.constant = 0 - } else { - revealButtonWidthConstraint?.constant = revealButtonWidth - } - revealButton.isHidden = hidden - if animated { - UIView.animate(withDuration: 0.25) { - self.contentView.layoutIfNeeded() - } - } else { - self.contentView.layoutIfNeeded() + func updateAccessories() { + var updatedAccessories : [UICellAccessory] = [ + .multiselect() + ] + + if (showMoreButton || (item?.isPlaceholder == true) || (progressView != nil)) && !isMoreButtonPermanentlyHidden, let actionAccessory = actionAccessory { + updatedAccessories.append(actionAccessory) } - } - //!! - /* - override open func setEditing(_ editing: Bool, animated: Bool) { - super.setEditing(editing, animated: animated) + if showRevealButton, let revealButtonAccessory = revealButtonAccessory { + updatedAccessories.append(revealButtonAccessory) + } - setMoreButton(hidden: editing, animated: animated) - setRevealButton(hidden: editing ? true : !showRevealButton, animated: animated) + accessories = updatedAccessories } - */ // MARK: - Actions @objc open func moreButtonTapped() { @@ -663,11 +631,6 @@ extension ItemListCell { cell.showRevealButton = cellConfiguration.style.showRevealButton cell.showMoreButton = cellConfiguration.style.showMoreButton - - cell.accessories = [ - .multiselect(), - .customView(configuration: UICellAccessory.CustomViewConfiguration(customView: cell.actionViewContainer, placement: .trailing(displayed: .whenNotEditing))) - ] }) } @@ -675,7 +638,7 @@ extension ItemListCell { let cell = collectionView.dequeueConfiguredReusableCell(using: itemListCellRegistration, for: indexPath, item: itemRef) if cellConfiguration?.highlight == true { - cell.isHighlighted = true + cell.revealHighlight = true } return cell diff --git a/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift b/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift index d574ff5dd..38d9c9a49 100644 --- a/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift +++ b/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift @@ -36,7 +36,6 @@ open class SearchScope: NSObject { static public func globalSearch(with context: ClientContext, cellStyle: CollectionViewCellStyle, localizedName: String) -> SearchScope { let revealCellStyle = CollectionViewCellStyle(from: cellStyle, changing: { cellStyle in cellStyle.showRevealButton = true - cellStyle.showMoreButton = false }) return CustomQuerySearchScope(with: context, cellStyle: revealCellStyle, localizedName: localizedName) @@ -57,7 +56,6 @@ open class SearchScope: NSObject { } open class ItemSearchScope : SearchScope { - // private var sortMethodPropertyObserver: ClientContext.PropertyObserverUUID? private var sortDescriptorObserver: NSKeyValueObservation? public override init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String) { @@ -66,15 +64,10 @@ open class ItemSearchScope : SearchScope { sortDescriptorObserver = context.observe(\.sortDescriptor, changeHandler: { [weak self] context, change in self?.sortDescriptorChanged(to: context.sortDescriptor) }) - -// sortMethodPropertyObserver = context.addObserver(for: [.sortMethod], with: { [weak self] context, property in -// self?.sortMethodChanged(to: context.sortMethod) -// }) } deinit { sortDescriptorObserver?.invalidate() -// clientContext.removeObserver(with: sortMethodPropertyObserver) } open func sortDescriptorChanged(to sortDescriptor: SortDescriptor?) { @@ -149,18 +142,53 @@ open class QueryModifyingSearchScope : ItemSearchScope { } open class CustomQuerySearchScope : ItemSearchScope { - private let maxResultCountDefault = 100 // Maximum number of results to return from database (default) - private var maxResultCount = 100 // Maximum number of results to return from database (flexible) + private let maxResultCountDefault = 2 // Maximum number of results to return from database (default) + private var maxResultCount = 2 // Maximum number of results to return from database (flexible) public override var isSelected: Bool { didSet { if isSelected { - // Modify existing query provided via clientContext - results = customQuery?.queryResultsDataSource + resultActionSource.setItems([ + OCAction(title: "Show more results".localized, icon: nil, action: { [weak self] action, options, completion in + self?.showMoreResults() + completion(nil) + }) + ], updated: nil) + composeResultsDataSource() } } } + public var resultActionSource: OCDataSourceArray = OCDataSourceArray() + + var resultsSubscription: OCDataSourceSubscription? + + func composeResultsDataSource() { + if let queryResultsSource = customQuery?.queryResultsDataSource { + let composedResults = OCDataSourceComposition(sources: [ + queryResultsSource, + resultActionSource + ]) + + let maxResultCount = maxResultCount + let resultActionSource = resultActionSource + + resultsSubscription = queryResultsSource.subscribe(updateHandler: { [weak composedResults, weak resultActionSource] (subscription) in + let snapshot = subscription.snapshotResettingChangeTracking(true) + + if let resultActionSource = resultActionSource { + OnMainThread { + composedResults?.setInclude((snapshot.numberOfItems >= maxResultCount), for: resultActionSource) + } + } + }, on: .main, trackDifferences: false, performIntialUpdate: true) + + results = composedResults + } else { + results = nil + } + } + public var customQuery: OCQuery? { willSet { if let core = clientContext.core, let oldQuery = customQuery { @@ -172,7 +200,7 @@ open class CustomQuerySearchScope : ItemSearchScope { if let core = clientContext.core, let newQuery = customQuery { core.start(newQuery) - results = newQuery.queryResultsDataSource + composeResultsDataSource() } else { results = nil } @@ -206,6 +234,11 @@ open class CustomQuerySearchScope : ItemSearchScope { } } + func showMoreResults() { + maxResultCount += maxResultCountDefault + updateCustomSearchQuery() + } + open override var queryCondition: OCQueryCondition? { didSet { updateCustomSearchQuery() From edfd33907a228ab6d7230346cdd20efc3c67a76f Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Wed, 29 Jun 2022 12:10:39 +0200 Subject: [PATCH 045/328] - SearchScope: set result count default from 2 (debug value) to 100 (previous value) --- ownCloudAppShared/Client/Search/Scopes/SearchScope.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift b/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift index 38d9c9a49..5034be31c 100644 --- a/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift +++ b/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift @@ -142,8 +142,8 @@ open class QueryModifyingSearchScope : ItemSearchScope { } open class CustomQuerySearchScope : ItemSearchScope { - private let maxResultCountDefault = 2 // Maximum number of results to return from database (default) - private var maxResultCount = 2 // Maximum number of results to return from database (flexible) + private let maxResultCountDefault = 100 // Maximum number of results to return from database (default) + private var maxResultCount = 100 // Maximum number of results to return from database (flexible) public override var isSelected: Bool { didSet { From b5ea3d45fd1faae6730f29b462543e0c00d352cd Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Thu, 30 Jun 2022 12:57:13 +0200 Subject: [PATCH 046/328] - update known issues, add new ideas regarding location picker, collection view updates - ready for preliminary code review to merge into milestone/12.0 --- KNOWN_ISSUES.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md index d7304312c..b903767bb 100644 --- a/KNOWN_ISSUES.md +++ b/KNOWN_ISSUES.md @@ -7,7 +7,6 @@ It should only be used with dedicated test servers, test data - and test devices ## App - in the new browsing experience, some features are not yet available: - - search - a grid view - breadcrumb title - item / folder / usage info at the bottom of lists @@ -29,6 +28,20 @@ It should only be used with dedicated test servers, test data - and test devices - pre-population of accounts using infinite PROPFIND is not supported # Evolution roadmap +- collection views + - support sidebars / hierarchies, including expanded state, with dynamic updates from data sources + +- location picker replaces folder picker + - supports picking + - accounts + - spaces + - folders + - returns an OCLocation + - allow passing "quick locations" to present on top in a group + - track and re-offer last-picked / recent locations (via account's KVS) + - quick access to personal and other spaces + - integrate favorites as group + - make sync smarter, f.ex.: - a file that is updated locally multiple times only should be uploaded once, not once for every update - a file or folder that is scheduled for upload / creation - and then deleted, should not be uploaded then deleted From 42e612a733706d04f237d5ff73ab5351516c0c3f Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Tue, 5 Jul 2022 10:23:23 +0200 Subject: [PATCH 047/328] - MessageGroupCell: change order of "Apply to all" label and switch, make the label truncating if not enough space is available (as reported by @mmattel) - add idea for evolution of the accounts view --- KNOWN_ISSUES.md | 4 ++++ ownCloud/Messages/MessageGroupCell.swift | 19 ++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md index b903767bb..576976d90 100644 --- a/KNOWN_ISSUES.md +++ b/KNOWN_ISSUES.md @@ -42,6 +42,10 @@ It should only be used with dedicated test servers, test data - and test devices - quick access to personal and other spaces - integrate favorites as group +- account list + - allow grouping accounts (i.e. Home / Work) + - replace simple list with modern CollectionViewController-based UI + - make sync smarter, f.ex.: - a file that is updated locally multiple times only should be uploaded once, not once for every update - a file or folder that is scheduled for upload / creation - and then deleted, should not be uploaded then deleted diff --git a/ownCloud/Messages/MessageGroupCell.swift b/ownCloud/Messages/MessageGroupCell.swift index f8000d8ac..ad856ee53 100644 --- a/ownCloud/Messages/MessageGroupCell.swift +++ b/ownCloud/Messages/MessageGroupCell.swift @@ -133,24 +133,25 @@ class MessageGroupCell: ThemeTableViewCell { setupLayout = true } - if applyAllSwitch == nil { - applyAllSwitch = UISwitch() - applyAllSwitch?.translatesAutoresizingMaskIntoConstraints = false - applyAllSwitch?.accessibilityLabel = "Apply choice to all similar issues".localized - applyAllSwitch?.addTarget(self, action: #selector(applyAllSwitchChanged), for: .primaryActionTriggered) - - applyAllContainer?.addSubview(applyAllSwitch!) - } - if applyAllLabel == nil { applyAllLabel = UILabel() applyAllLabel?.translatesAutoresizingMaskIntoConstraints = false applyAllLabel?.font = UIFont.systemFont(ofSize: UIFont.systemFontSize) applyAllLabel?.text = "Apply to all".localized + applyAllLabel?.lineBreakMode = .byTruncatingTail applyAllContainer?.addSubview(applyAllLabel!) } + if applyAllSwitch == nil { + applyAllSwitch = UISwitch() + applyAllSwitch?.translatesAutoresizingMaskIntoConstraints = false + applyAllSwitch?.accessibilityLabel = "Apply choice to all similar issues".localized + applyAllSwitch?.addTarget(self, action: #selector(applyAllSwitchChanged), for: .primaryActionTriggered) + + applyAllContainer?.addSubview(applyAllSwitch!) + } + if badgeLabel == nil { badgeLabel = RoundedLabel(text: "", style: .token) badgeLabel?.translatesAutoresizingMaskIntoConstraints = false From b59c62b4c01c973f87098f5ea5649060f5bdd6a9 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Fri, 29 Jul 2022 16:36:57 +0200 Subject: [PATCH 048/328] Fix code review findings from #1101. --- ownCloud.xcodeproj/project.pbxproj | 4 -- .../Actions+Extensions/CopyAction.swift | 6 +- .../Actions+Extensions/CutAction.swift | 6 +- .../Actions+Extensions/DeleteAction.swift | 6 +- .../DisplayExifMetadataAction.swift | 5 +- .../DocumentEditingAction.swift | 6 +- .../Actions+Extensions/DuplicateAction.swift | 6 +- .../Actions+Extensions/FavoriteAction.swift | 6 +- .../ImportPasteboardAction.swift | 6 +- .../MakeAvailableOfflineAction.swift | 6 +- .../MakeUnavailableOfflineAction.swift | 6 +- .../Actions+Extensions/MoveAction.swift | 6 +- .../Actions+Extensions/OpenInAction.swift | 6 +- .../PDFGotoPageAction.swift | 6 +- .../PresentationModeAction.swift | 6 +- .../Actions+Extensions/RenameAction.swift | 6 +- .../Actions+Extensions/UnfavoriteAction.swift | 6 +- .../Actions+Extensions/UnshareAction.swift | 6 +- .../UploadCameraMediaAction.swift | 7 +-- .../Actions+Extensions/UploadFileAction.swift | 8 +-- .../UploadMediaAction.swift | 8 +-- .../Actions/EditDocumentViewController.swift | 33 +++------- .../Client/Actions/Scanner/ScanAction.swift | 6 +- .../Client/ClientRootViewController.swift | 63 ------------------- .../Client/Viewer/DisplayViewController.swift | 2 +- .../Resources/en.lproj/Localizable.strings | 1 + .../Client/Actions/CreateFolderAction.swift | 6 +- .../Collection Views/Cells/MessageCell.swift | 25 -------- .../UIKit Extension/LAContext+Extension.swift | 12 ++-- ...llectionViewDiffableDataSource+Tools.swift | 10 +++ .../ProgressIndicatorViewController.swift | 6 +- .../Metadata/MetadataDocumentationTests.swift | 20 +++--- 32 files changed, 59 insertions(+), 253 deletions(-) delete mode 100644 ownCloudAppShared/Client/Collection Views/Cells/MessageCell.swift diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 70566d2e2..1c8a63896 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -198,7 +198,6 @@ DC0030C22350B1CE00BB8570 /* NSData+Encoding.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0030C02350B1CE00BB8570 /* NSData+Encoding.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC0030CB2350B75000BB8570 /* ScanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1AC7CF2319ADAE002B7892 /* ScanViewController.swift */; }; DC0196AB20F7690C00C41B78 /* OCBookmark+FileProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DC27A1A420CBEF85008ACB6C /* OCBookmark+FileProvider.m */; }; - DC01AF1C28411C0B00903101 /* MessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC01AF1B28411C0B00903101 /* MessageCell.swift */; }; DC01AF2128411D8400903101 /* ThemeableCollectionViewListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC01AF2028411D8400903101 /* ThemeableCollectionViewListCell.swift */; }; DC01CDCC212EDDF600FC8E38 /* TextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC01CDCB212EDDF600FC8E38 /* TextViewController.swift */; }; DC049156258C00C400DEDC27 /* OCFileProviderServiceStandby.h in Headers */ = {isa = PBXBuildFile; fileRef = DC049154258C00C400DEDC27 /* OCFileProviderServiceStandby.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1234,7 +1233,6 @@ DC0030BF2350B1CE00BB8570 /* NSData+Encoding.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+Encoding.m"; sourceTree = ""; }; DC0030C02350B1CE00BB8570 /* NSData+Encoding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+Encoding.h"; sourceTree = ""; }; DC018F8B20A1060A00135198 /* ProgressHUDViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressHUDViewController.swift; sourceTree = ""; }; - DC01AF1B28411C0B00903101 /* MessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCell.swift; sourceTree = ""; }; DC01AF2028411D8400903101 /* ThemeableCollectionViewListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeableCollectionViewListCell.swift; sourceTree = ""; }; DC01CDCB212EDDF600FC8E38 /* TextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewController.swift; sourceTree = ""; }; DC049154258C00C400DEDC27 /* OCFileProviderServiceStandby.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCFileProviderServiceStandby.h; sourceTree = ""; }; @@ -3163,7 +3161,6 @@ DCEAF0892808254800980B6D /* DriveListCell.swift */, DC3AB2472810A10300789435 /* ExpandableResourceCell.swift */, DC3AB23D280FFE3400789435 /* ItemListCell.swift */, - DC01AF1B28411C0B00903101 /* MessageCell.swift */, DC49C22028524D6C00BAA910 /* ThemeableCollectionViewCell.swift */, DC01AF2028411D8400903101 /* ThemeableCollectionViewListCell.swift */, DC46F3CB2844A8EA00038880 /* ViewCell.swift */, @@ -4449,7 +4446,6 @@ DCA2EDE2279B16F1001F04E6 /* ResourceSourceItemIcons.swift in Sources */, DCEAF08A2808254800980B6D /* DriveListCell.swift in Sources */, DCB5D5A728632C17004AF425 /* SearchScope.swift in Sources */, - DC01AF1C28411C0B00903101 /* MessageCell.swift in Sources */, DC0A357724C0E43200FB58FC /* ProgressSummarizer.swift in Sources */, 392CFEB72705831700631D2B /* LAContext+Extension.swift in Sources */, DCE4E48724C1F9F50051722F /* CreateFolderAction.swift in Sources */, diff --git a/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift b/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift index 54d35fa52..179735c0d 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift @@ -104,11 +104,7 @@ class CopyAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection || location == .dropAction { - return UIImage(named: "copy-file")?.withRenderingMode(.alwaysTemplate) - } - - return nil + return UIImage(named: "copy-file")?.withRenderingMode(.alwaysTemplate) } func showDirectoryPicker() { diff --git a/ownCloud/Client/Actions/Actions+Extensions/CutAction.swift b/ownCloud/Client/Actions/Actions+Extensions/CutAction.swift index b3050c5a3..b949db4dd 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/CutAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/CutAction.swift @@ -91,10 +91,6 @@ class CutAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection || location == .dropAction { - return UIImage(systemName: "scissors")?.withRenderingMode(.alwaysTemplate) - } - - return nil + return UIImage(systemName: "scissors")?.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift index cc3be1501..5e9875ea9 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift @@ -97,10 +97,6 @@ class DeleteAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection || location == .dropAction { - return UIImage(named: "trash")?.withRenderingMode(.alwaysTemplate) - } - - return nil + return UIImage(named: "trash")?.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/DisplayExifMetadataAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DisplayExifMetadataAction.swift index 8d4d737ab..ef555cc10 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DisplayExifMetadataAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DisplayExifMetadataAction.swift @@ -55,10 +55,7 @@ class DisplayExifMetadataAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem { - return UIImage(named: "camera-info")?.withRenderingMode(.alwaysTemplate) - } - return nil + return UIImage(named: "camera-info")?.withRenderingMode(.alwaysTemplate) } // MARK: - Action implementation diff --git a/ownCloud/Client/Actions/Actions+Extensions/DocumentEditingAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DocumentEditingAction.swift index 0f11735b0..4af8b01a9 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DocumentEditingAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DocumentEditingAction.swift @@ -103,10 +103,6 @@ class DocumentEditingAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem { - return UIImage(systemName: "pencil.tip.crop.circle")?.withRenderingMode(.alwaysTemplate) - } - - return nil + return UIImage(systemName: "pencil.tip.crop.circle")?.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift index 565e8ec0f..d17c2e3dc 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift @@ -77,10 +77,6 @@ class DuplicateAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection || location == .dropAction { - return UIImage(named: "duplicate-file")?.withRenderingMode(.alwaysTemplate) - } - - return nil + return UIImage(named: "duplicate-file")?.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/FavoriteAction.swift b/ownCloud/Client/Actions/Actions+Extensions/FavoriteAction.swift index 4a2825d1a..1fbf0cd6c 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/FavoriteAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/FavoriteAction.swift @@ -55,10 +55,6 @@ class FavoriteAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .contextMenuItem { - return UIImage(systemName: "star")?.withRenderingMode(.alwaysTemplate) - } - - return nil + return UIImage(systemName: "star")?.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/ImportPasteboardAction.swift b/ownCloud/Client/Actions/Actions+Extensions/ImportPasteboardAction.swift index 4ee90ecdb..ca9c7e421 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/ImportPasteboardAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/ImportPasteboardAction.swift @@ -244,10 +244,6 @@ class ImportPasteboardAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreFolder { - return UIImage(systemName: "doc.on.clipboard")?.withRenderingMode(.alwaysTemplate) - } - - return nil + return UIImage(systemName: "doc.on.clipboard")?.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/MakeAvailableOfflineAction.swift b/ownCloud/Client/Actions/Actions+Extensions/MakeAvailableOfflineAction.swift index a74557b50..62a257d17 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/MakeAvailableOfflineAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/MakeAvailableOfflineAction.swift @@ -64,10 +64,6 @@ class MakeAvailableOfflineAction: Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem { - return UIImage(named: "available-offline")?.withRenderingMode(.alwaysTemplate) - } - - return nil + return UIImage(named: "available-offline")?.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/MakeUnavailableOfflineAction.swift b/ownCloud/Client/Actions/Actions+Extensions/MakeUnavailableOfflineAction.swift index edd917329..f0e11a8b8 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/MakeUnavailableOfflineAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/MakeUnavailableOfflineAction.swift @@ -80,10 +80,6 @@ class MakeUnavailableOfflineAction: Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem { - return UIImage(named: "available-offline")?.withRenderingMode(.alwaysTemplate) - } - - return nil + return UIImage(named: "available-offline")?.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift index 8fce48af9..8770399d2 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift @@ -77,10 +77,6 @@ class MoveAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection || location == .dropAction { - return UIImage(named: "folder")?.withRenderingMode(.alwaysTemplate) - } - - return nil + return UIImage(named: "folder")?.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift index 03a918c7d..671916c03 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift @@ -169,11 +169,7 @@ class OpenInAction: Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem || location == .multiSelection || location == .dropAction { - return UIImage(systemName: "square.and.arrow.up")?.withRenderingMode(.alwaysTemplate) - } - - return nil + return UIImage(systemName: "square.and.arrow.up")?.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/PDFGotoPageAction.swift b/ownCloud/Client/Actions/Actions+Extensions/PDFGotoPageAction.swift index 6c73a05bd..96baa30dc 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/PDFGotoPageAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/PDFGotoPageAction.swift @@ -47,10 +47,6 @@ class PDFGoToPageAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreDetailItem { - return UIImage(systemName: "arrow.up.doc")?.withRenderingMode(.alwaysTemplate) - } - - return nil + return UIImage(systemName: "arrow.up.doc")?.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/PresentationModeAction.swift b/ownCloud/Client/Actions/Actions+Extensions/PresentationModeAction.swift index d3783b0db..0ea946aef 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/PresentationModeAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/PresentationModeAction.swift @@ -81,10 +81,6 @@ class PresentationModeAction: Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreDetailItem { - return UIImage(systemName: "tv")?.withRenderingMode(.alwaysTemplate) - } - - return nil + return UIImage(systemName: "tv")?.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift index 1e34862cc..a66aef851 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift @@ -93,10 +93,6 @@ class RenameAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .contextMenuItem { - return UIImage(systemName: "pencil")?.withRenderingMode(.alwaysTemplate) - } - - return nil + return UIImage(systemName: "pencil")?.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/UnfavoriteAction.swift b/ownCloud/Client/Actions/Actions+Extensions/UnfavoriteAction.swift index 0a777fc63..523ec11ec 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/UnfavoriteAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/UnfavoriteAction.swift @@ -55,10 +55,6 @@ class UnfavoriteAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .contextMenuItem { - return UIImage(named: "star")?.withRenderingMode(.alwaysTemplate) - } - - return nil + return UIImage(named: "star")?.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/UnshareAction.swift b/ownCloud/Client/Actions/Actions+Extensions/UnshareAction.swift index cbfb09b43..61c5fbe0b 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/UnshareAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/UnshareAction.swift @@ -121,10 +121,6 @@ class UnshareAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem || location == .moreDetailItem || location == .moreFolder || location == .multiSelection { - return UIImage(named: "trash")?.withRenderingMode(.alwaysTemplate) - } - - return nil + return UIImage(named: "trash")?.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/UploadCameraMediaAction.swift b/ownCloud/Client/Actions/Actions+Extensions/UploadCameraMediaAction.swift index 7d55f632c..3b5ced69a 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/UploadCameraMediaAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/UploadCameraMediaAction.swift @@ -231,12 +231,7 @@ class UploadCameraMediaAction: UploadBaseAction, UIImagePickerControllerDelegate // MARK: - Action implementation override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .folderAction { - let image = UIImage(named: "camera")?.withRenderingMode(.alwaysTemplate) - return image - } - - return nil + return UIImage(named: "camera")?.withRenderingMode(.alwaysTemplate) } override func run() { diff --git a/ownCloud/Client/Actions/Actions+Extensions/UploadFileAction.swift b/ownCloud/Client/Actions/Actions+Extensions/UploadFileAction.swift index 2d0530dda..dc2d84a6e 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/UploadFileAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/UploadFileAction.swift @@ -53,12 +53,8 @@ class UploadFileAction: UploadBaseAction { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .folderAction || location == .emptyFolder { - Theme.shared.add(tvgResourceFor: "text") - return Theme.shared.image(for: "text", size: CGSize(width: 30.0, height: 30.0))!.withRenderingMode(.alwaysTemplate) - } - - return nil + Theme.shared.add(tvgResourceFor: "text") + return Theme.shared.image(for: "text", size: CGSize(width: 30.0, height: 30.0))!.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloud/Client/Actions/Actions+Extensions/UploadMediaAction.swift b/ownCloud/Client/Actions/Actions+Extensions/UploadMediaAction.swift index 590fb1c26..359b9691b 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/UploadMediaAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/UploadMediaAction.swift @@ -196,11 +196,7 @@ class UploadMediaAction: UploadBaseAction { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .folderAction || location == .emptyFolder { - Theme.shared.add(tvgResourceFor: "image") - return Theme.shared.image(for: "image", size: CGSize(width: 30.0, height: 30.0))!.withRenderingMode(.alwaysTemplate) - } - - return nil + Theme.shared.add(tvgResourceFor: "image") + return Theme.shared.image(for: "image", size: CGSize(width: 30.0, height: 30.0))!.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloud/Client/Actions/EditDocumentViewController.swift b/ownCloud/Client/Actions/EditDocumentViewController.swift index 31e092d31..5cc346fad 100644 --- a/ownCloud/Client/Actions/EditDocumentViewController.swift +++ b/ownCloud/Client/Actions/EditDocumentViewController.swift @@ -125,29 +125,16 @@ class EditDocumentViewController: QLPreviewController, Themeable { @objc func enableEditingMode() { // Activate editing mode by performing the action on pencil icon. Unfortunately that's the only way to do it apparently - // if #available(iOS 15.0, *) { - if self.navigationItem.rightBarButtonItems?.count ?? 0 > 2 { - guard let markupButton = self.navigationItem.rightBarButtonItems?[1] else { return } - _ = markupButton.target?.perform(markupButton.action, with: markupButton) - } else if UIDevice.current.isIpad, self.navigationItem.rightBarButtonItems?.count ?? 0 == 2 { - guard let markupButton = self.navigationItem.rightBarButtonItems?[1] else { return } - _ = markupButton.target?.perform(markupButton.action, with: markupButton) - } else { - guard let markupButton = self.navigationItem.rightBarButtonItems?.first else { return } - _ = markupButton.target?.perform(markupButton.action, with: markupButton) - } - /*} else if #available(iOS 14.0, *) { - if self.navigationItem.rightBarButtonItems?.count ?? 0 > 1 { - guard let markupButton = self.navigationItem.rightBarButtonItems?.last else { return } - _ = markupButton.target?.perform(markupButton.action, with: markupButton) - } else { - guard let markupButton = self.navigationItem.rightBarButtonItems?.first else { return } - _ = markupButton.target?.perform(markupButton.action, with: markupButton) - } - } else { // action and target is nil on iOS 13 - guard let markupButton = self.navigationItem.rightBarButtonItems?.filter({$0.customView != nil}).first?.customView as? UIButton else { return } - markupButton.sendActions(for: .touchUpInside) - } */ + if self.navigationItem.rightBarButtonItems?.count ?? 0 > 2 { + guard let markupButton = self.navigationItem.rightBarButtonItems?[1] else { return } + _ = markupButton.target?.perform(markupButton.action, with: markupButton) + } else if UIDevice.current.isIpad, self.navigationItem.rightBarButtonItems?.count ?? 0 == 2 { + guard let markupButton = self.navigationItem.rightBarButtonItems?[1] else { return } + _ = markupButton.target?.perform(markupButton.action, with: markupButton) + } else { + guard let markupButton = self.navigationItem.rightBarButtonItems?.first else { return } + _ = markupButton.target?.perform(markupButton.action, with: markupButton) + } } @objc func dismissAnimated() { diff --git a/ownCloud/Client/Actions/Scanner/ScanAction.swift b/ownCloud/Client/Actions/Scanner/ScanAction.swift index 42bb80e60..cdc7f7d7a 100644 --- a/ownCloud/Client/Actions/Scanner/ScanAction.swift +++ b/ownCloud/Client/Actions/Scanner/ScanAction.swift @@ -98,10 +98,6 @@ class ScanAction: Action, VNDocumentCameraViewControllerDelegate { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .folderAction || location == .emptyFolder { - return UIImage(systemName: "doc.text.viewfinder", withConfiguration: UIImage.SymbolConfiguration(pointSize: 26, weight: .regular))?.withRenderingMode(.alwaysTemplate) - } - - return nil + return UIImage(systemName: "doc.text.viewfinder", withConfiguration: UIImage.SymbolConfiguration(pointSize: 26, weight: .regular))?.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloud/Client/ClientRootViewController.swift b/ownCloud/Client/ClientRootViewController.swift index 2988f635d..f484e1fbb 100644 --- a/ownCloud/Client/ClientRootViewController.swift +++ b/ownCloud/Client/ClientRootViewController.swift @@ -392,70 +392,7 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa let topLevelViewController : UIViewController? if core.useDrives { -// let composedDataSource = OCDataSourceComposition(sources: [ -// core.hierarchicDrivesDataSource, -// core.projectDrivesDataSource -// ], applyCustomizations: { (composedDataSource) in -// composedDataSource.sortComparator = OCDataSourceComposition.itemComparator(withItemRetrieval: false, fromRecordComparator: { record1, record2 in -// var presentable1 : OCDataItemPresentable? -// var presentable2 : OCDataItemPresentable? -// -// if let item = record1?.item { -// presentable1 = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable -// } -// -// if let item = record2?.item { -// presentable2 = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable -// } -// -// let title1 = presentable1?.title ?? "" -// let title2 = presentable2?.title ?? "" -// -// return title1.localizedCompare(title2) -// }) -// -// composedDataSource.filter = OCDataSourceComposition.itemFilter(recordFilter: { record in -// if let item = record?.item, -// let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable, -// let startsWithA = presentable.title?.starts(with: "A") { -// return startsWithA -// } -// -// return false -// }) - -// composedDataSource.setSortComparator(OCDataSourceComposition.itemComparator(withItemRetrieval: false, fromRecordComparator: { record1, record2 in -// var presentable1 : OCDataItemPresentable? -// var presentable2 : OCDataItemPresentable? -// -// if let item = record1?.item { -// presentable1 = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable -// } -// -// if let item = record2?.item { -// presentable2 = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable -// } -// -// let title1 = presentable1?.title?.last?.lowercased() ?? "" -// let title2 = presentable2?.title?.last?.lowercased() ?? "" -// -// return title1.localizedCompare(title2) -// }), for: core.projectDrivesDataSource) - -// composedDataSource.setFilter(OCDataSourceComposition.itemFilter(withItemRetrieval: false, fromRecordFilter: { (record) in -// if let item = record?.item, -// let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable, -// let startsWithA = presentable.title?.starts(with: "A") { -// return startsWithA -// } -// -// return false -// }), for: core.projectDrivesDataSource) -// }) - topLevelViewController = CollectionViewController(context: ClientContext(with: self.rootContext, navigationController: self.filesNavigationController), sections: [ -// CollectionViewSection(identifier: "composed", dataSource: composedDataSource) - CollectionViewSection(identifier: "top", dataSource: core.hierarchicDrivesDataSource, cellLayout: .list(appearance: .insetGrouped)), CollectionViewSection(identifier: "projects", dataSource: core.projectDrivesDataSource, cellLayout: .list(appearance: .insetGrouped)) ]) diff --git a/ownCloud/Client/Viewer/DisplayViewController.swift b/ownCloud/Client/Viewer/DisplayViewController.swift index 98ba47de2..810e2b53c 100644 --- a/ownCloud/Client/Viewer/DisplayViewController.swift +++ b/ownCloud/Client/Viewer/DisplayViewController.swift @@ -383,7 +383,7 @@ class DisplayViewController: UIViewController, Themeable, OCQueryDelegate { switch updateStrategy { case .ask: OnMainThread { - let alert = UIAlertController(title: NSString(format: "%@ was updated".localized as NSString, item.name ?? "File".localized) as String, message: "Would you like to view the updated version?".localized, preferredStyle: .alert) + let alert = UIAlertController(title: String(format: "%@ was updated".localized, item.name ?? "File".localized), message: "Would you like to view the updated version?".localized, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Show new version".localized, style: .default, handler: { [weak self] (_) in self?.updateStrategy = .ask diff --git a/ownCloud/Resources/en.lproj/Localizable.strings b/ownCloud/Resources/en.lproj/Localizable.strings index f7330dcab..4e8ef97ca 100644 --- a/ownCloud/Resources/en.lproj/Localizable.strings +++ b/ownCloud/Resources/en.lproj/Localizable.strings @@ -143,6 +143,7 @@ /* Client Messages */ "Empty folder" = "Empty folder"; "This folder contains no files or folders." = "This folder contains no files or folders."; +"This folder is empty. Fill it with content:" = "This folder is empty. Fill it with content:"; "Folder removed" = "Folder removed"; "This folder no longer exists on the server." = "This folder no longer exists on the server."; diff --git a/ownCloudAppShared/Client/Actions/CreateFolderAction.swift b/ownCloudAppShared/Client/Actions/CreateFolderAction.swift index 276e40d2f..4b0e6e488 100644 --- a/ownCloudAppShared/Client/Actions/CreateFolderAction.swift +++ b/ownCloudAppShared/Client/Actions/CreateFolderAction.swift @@ -106,10 +106,6 @@ open class CreateFolderAction : Action { } override open class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .keyboardShortcut || location == .folderAction || location == .emptyFolder { - return Theme.shared.image(for: "folder-create", size: CGSize(width: 30.0, height: 30.0))!.withRenderingMode(.alwaysTemplate) - } - - return nil + return Theme.shared.image(for: "folder-create", size: CGSize(width: 30.0, height: 30.0))!.withRenderingMode(.alwaysTemplate) } } diff --git a/ownCloudAppShared/Client/Collection Views/Cells/MessageCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/MessageCell.swift deleted file mode 100644 index 35471ef25..000000000 --- a/ownCloudAppShared/Client/Collection Views/Cells/MessageCell.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// MessageCell.swift -// ownCloudAppShared -// -// Created by Felix Schwarz on 27.05.22. -// Copyright © 2022 ownCloud GmbH. All rights reserved. -// - -/* - * Copyright (C) 2022, ownCloud GmbH. - * - * This code is covered by the GNU Public License Version 3. - * - * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ - * You should have received a copy of this license along with this program. If not, see . - * - */ - -import UIKit -import ownCloudSDK -import ownCloudApp - -class MessageCell : ThemeableCollectionViewListCell { - -} diff --git a/ownCloudAppShared/UIKit Extension/LAContext+Extension.swift b/ownCloudAppShared/UIKit Extension/LAContext+Extension.swift index 44254f894..3961e1d9d 100644 --- a/ownCloudAppShared/UIKit Extension/LAContext+Extension.swift +++ b/ownCloudAppShared/UIKit Extension/LAContext+Extension.swift @@ -35,16 +35,12 @@ extension LAContext { public func biometricsAuthenticationImage() -> UIImage? { if canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) { switch self.biometryType { - case .faceID : if #available(iOSApplicationExtension 13.0, *) { + case .faceID: return UIImage(systemName: "faceid") - } else { - return UIImage(named: "biometrical-faceid") - } - case .touchID: if #available(iOSApplicationExtension 13.0, *) { + + case .touchID: return UIImage(systemName: "touchid") - } else { - return UIImage(named: "biometrical-touchid") - } + case .none: return nil @unknown default: return nil } diff --git a/ownCloudAppShared/UIKit Extension/UICollectionViewDiffableDataSource+Tools.swift b/ownCloudAppShared/UIKit Extension/UICollectionViewDiffableDataSource+Tools.swift index f13304196..54bfc4803 100644 --- a/ownCloudAppShared/UIKit Extension/UICollectionViewDiffableDataSource+Tools.swift +++ b/ownCloudAppShared/UIKit Extension/UICollectionViewDiffableDataSource+Tools.swift @@ -6,6 +6,16 @@ // Copyright © 2022 ownCloud GmbH. All rights reserved. // +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + import UIKit public extension UICollectionViewDiffableDataSource { diff --git a/ownCloudAppShared/User Interface/Progress/ProgressIndicatorViewController.swift b/ownCloudAppShared/User Interface/Progress/ProgressIndicatorViewController.swift index 27ad43ccd..b0260bbe9 100644 --- a/ownCloudAppShared/User Interface/Progress/ProgressIndicatorViewController.swift +++ b/ownCloudAppShared/User Interface/Progress/ProgressIndicatorViewController.swift @@ -68,11 +68,7 @@ open class ProgressIndicatorViewController: UIViewController, Themeable { progressView = UIProgressView(progressViewStyle: .bar) progressView.translatesAutoresizingMaskIntoConstraints = false - if #available(iOS 13, *) { - activityIndicator = UIActivityIndicatorView(style: .large) - } else { - activityIndicator = UIActivityIndicatorView(style: .whiteLarge) - } + activityIndicator = UIActivityIndicatorView(style: .large) activityIndicator.translatesAutoresizingMaskIntoConstraints = false activityIndicator.isHidden = true diff --git a/ownCloudTests/Metadata/MetadataDocumentationTests.swift b/ownCloudTests/Metadata/MetadataDocumentationTests.swift index cefddcc27..053d0c5e9 100644 --- a/ownCloudTests/Metadata/MetadataDocumentationTests.swift +++ b/ownCloudTests/Metadata/MetadataDocumentationTests.swift @@ -29,21 +29,17 @@ class MetadataDocumentationTests: XCTestCase { .externalDocumentationFolders : [ sdkDocsURL ] ]) - if #available(iOS 13, *) { - guard let jsonData = try? JSONSerialization.data(withJSONObject: docDict, options: [.prettyPrinted, .sortedKeys, .fragmentsAllowed, .withoutEscapingSlashes]) else { - XCTFail("Failed encoding documentation dictionary as JSON") - return - } + guard let jsonData = try? JSONSerialization.data(withJSONObject: docDict, options: [.prettyPrinted, .sortedKeys, .fragmentsAllowed, .withoutEscapingSlashes]) else { + XCTFail("Failed encoding documentation dictionary as JSON") + return + } - if let jsonString = String(data: jsonData, encoding: .utf8) { - Log.debug("\(jsonString)") + if let jsonString = String(data: jsonData, encoding: .utf8) { + Log.debug("\(jsonString)") - if let jsonPath = ProcessInfo.processInfo.environment["OC_SETTINGS_DOC_JSON"] { - try? jsonData.write(to: URL(fileURLWithPath: jsonPath), options: .atomicWrite) - } + if let jsonPath = ProcessInfo.processInfo.environment["OC_SETTINGS_DOC_JSON"] { + try? jsonData.write(to: URL(fileURLWithPath: jsonPath), options: .atomicWrite) } - } else { - XCTFail("Test needs to be run on Simulator running iOS 13 or later") } } } From 97aead46e03746032c1a96328efddd2f4325504b Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Wed, 10 Aug 2022 09:36:08 +0200 Subject: [PATCH 049/328] - add DISABLE_PLAIN_HTTP build flag - re-add a reduced version of the BUILD_CUSTOMIZATION.md document to keep track of build flags and add documentation on DISABLE_PLAIN_HTTP to it --- doc/BUILD_CUSTOMIZATION.md | 38 +++++++++++++++++++++++++++++++++++ fastlane/Fastfile | 6 ++++++ ios-sdk | 2 +- ownCloud/Resources/Info.plist | 2 ++ 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 doc/BUILD_CUSTOMIZATION.md diff --git a/doc/BUILD_CUSTOMIZATION.md b/doc/BUILD_CUSTOMIZATION.md new file mode 100644 index 000000000..ce413fb34 --- /dev/null +++ b/doc/BUILD_CUSTOMIZATION.md @@ -0,0 +1,38 @@ +# Build Flags + +## Description + +Build Flags can be used to control the inclusion or exclusion of certain functionality or features at compile time. + +## Usage in Branding + +A **space-separated** list of flags can be specified in the `Branding.plist` with the key `build.flags`, f.ex.: + +```xml +build.flags +DISABLE_BACKGROUND_LOCATION +``` + +## Flags + +The following options can be used as `build.flags`: + +### `DISABLE_BACKGROUND_LOCATION` + +Removes the following from the app: +- the option for location-triggered background uploads from Settings +- the location description keys from the app's `Info.plist` + +Not used by default. + +### `DISABLE_APPSTORE_LICENSING` + +Removes the following from the app: +- App Store integration for OCLicense +- App Store related view controllers and settings section + +### `DISABLE_PLAIN_HTTP` + +Removes the following from the app: +- the `NSAppTransportSecurity` dictionary from the app's `Info.plist` +- including the `NSAllowsArbitraryLoads` key that's needed to allow plain/unsecured HTTP connections diff --git a/fastlane/Fastfile b/fastlane/Fastfile index c951a3056..ca7ed467e 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -547,6 +547,12 @@ end sh "mv ../ownCloud/Resources/Info.plist.mod ../ownCloud/Resources/Info.plist" end + # Special handling for app build flag DISABLE_PLAIN_HTTP (see above why this is needed) + if appBuildFlags.include? "DISABLE_PLAIN_HTTP" + sh "sed '/#ifndef DISABLE_PLAIN_HTTP/,/#endif/d' ../ownCloud/Resources/Info.plist >../ownCloud/Resources/Info.plist.mod" + sh "mv ../ownCloud/Resources/Info.plist.mod ../ownCloud/Resources/Info.plist" + end + # update_url_schemes can't seem to reach the second URL scheme ("oc") for authentication # so using sed and a XML property instead if !appCustomAppScheme.empty? diff --git a/ios-sdk b/ios-sdk index 57729a606..dbc288401 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit 57729a60605cfc810c7e858d1875fc49006ec7f9 +Subproject commit dbc28840190c4a28bc946da5591e0e37dae69a71 diff --git a/ownCloud/Resources/Info.plist b/ownCloud/Resources/Info.plist index ea8d3ca81..b90bb2be0 100644 --- a/ownCloud/Resources/Info.plist +++ b/ownCloud/Resources/Info.plist @@ -75,11 +75,13 @@ LSSupportsOpeningDocumentsInPlace + #ifndef DISABLE_PLAIN_HTTP NSAppTransportSecurity NSAllowsArbitraryLoads + #endif NSAppleMusicUsageDescription This permission is needed for uploading media files to your server. NSCameraUsageDescription From 2cc00fd3f93ed88ca903c141e4b823c51ac47934 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Fri, 12 Aug 2022 12:21:40 +0200 Subject: [PATCH 050/328] - update SDK to include latest documentation changes --- ios-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios-sdk b/ios-sdk index dbc288401..82767d5a3 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit dbc28840190c4a28bc946da5591e0e37dae69a71 +Subproject commit 82767d5a314f582970ee30e4e3adf32f8aab33f1 From f1cf03bbef823a99fa345115695f6dc7b6d986c7 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Mon, 22 Aug 2022 14:47:17 +0200 Subject: [PATCH 051/328] - Action: add new action location "unviewableFileType" for actions to display for unviewable file types - DisplayViewController: - add support for a primary unviewableFileType action - replace usage of UIControl.Event.touchUpInside with .primaryActionTriggered - Scheme: add option for new "extensions.disallowed" MDM option - update SDK for support for "extensions.disallowed" --- ios-sdk | 2 +- .../xcshareddata/xcschemes/ownCloud.xcscheme | 5 ++ .../Actions+Extensions/OpenInAction.swift | 2 +- .../Client/Viewer/DisplayViewController.swift | 47 ++++++++++++++++++- ownCloudAppShared/Client/Actions/Action.swift | 1 + 5 files changed, 53 insertions(+), 4 deletions(-) diff --git a/ios-sdk b/ios-sdk index 57729a606..af4d0edf0 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit 57729a60605cfc810c7e858d1875fc49006ec7f9 +Subproject commit af4d0edf0fe8061947d88ae013fd800fb6276096 diff --git a/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme b/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme index e0dd390a7..b0dcf33f4 100644 --- a/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme +++ b/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme @@ -355,6 +355,11 @@ value = "[com.owncloud.action.copy,com.owncloud.action.move]" isEnabled = "NO"> + + Date: Thu, 25 Aug 2022 12:35:43 +0200 Subject: [PATCH 052/328] - UITextField+Extension: extensions for UITextField and UISearchTextField helping with replacing text and determining the cursor position - SearchViewController: direct input to the scopes tokenizer rather than to the scope directly - SearchTokenizer - taking a modular approach to tokenizing input that can be plugged into SearchScopes - implementation provided for tokenizing custom query search terms into tokens - Search Segments - OCSearchSegment: rich representation of segmented search strings, with information on originating text range, original text, cursor position inside the segment and whether the cursor is currently in the segment at all - NSString+SearchSegmenter: - search segmentation into OCSearchSegments rather than an array of strings, transporting rich metadata alongside the segments - compose and return rich, localized description and symbolName to returned segments - OCQueryCondition+SearchSegmentDescription: uses OCQueryCondition.userInfo to attach additional information to OCQueryConditions generated from a string: - includes symbolName and localizedDescription for UISearchToken generation - saves original segment the OCQueryCondition was created from - can compose a hierarchic structure of OCQueryCondition back into a full search string ready for saving - OCQueryCondition+SearchToken: - utility methods simplifying retrieving relevant query conditions for generating SearchToken - method for generating a SearchToken from an OCQueryCondition, using .symbolName and .localizedDescription by default, trying to extract information from the OCQueryCondition as a fallback --- ios-sdk | 2 +- ownCloud.xcodeproj/project.pbxproj | 32 ++ .../xcshareddata/swiftpm/Package.resolved | 13 +- .../Resources/en.lproj/Localizable.strings | 48 +++ .../OCQueryCondition+SearchSegmenter.h | 18 ++ .../OCQueryCondition+SearchSegmenter.m | 282 ++++++++++++++++-- .../SDK Extensions/OCSearchSegment.h | 35 +++ .../SDK Extensions/OCSearchSegment.m | 36 +++ ownCloudAppFramework/ownCloudApp.h | 1 + .../SearchSegmentationTests.m | 151 ++++++++++ .../Client/Search/Scopes/SearchScope.swift | 22 +- .../Client/Search/SearchViewController.swift | 3 +- .../OCQueryCondition+SearchToken.swift | 111 +++++++ .../Search/Tokenizer/SearchElement.swift | 49 +++ .../Search/Tokenizer/SearchTokenizer.swift | 109 +++++++ .../UITextField+Extension.swift | 47 +++ 16 files changed, 928 insertions(+), 31 deletions(-) create mode 100644 ownCloudAppFramework/SDK Extensions/OCSearchSegment.h create mode 100644 ownCloudAppFramework/SDK Extensions/OCSearchSegment.m create mode 100644 ownCloudAppShared/Client/Search/Tokenizer/OCQueryCondition+SearchToken.swift create mode 100644 ownCloudAppShared/Client/Search/Tokenizer/SearchElement.swift create mode 100644 ownCloudAppShared/Client/Search/Tokenizer/SearchTokenizer.swift create mode 100644 ownCloudAppShared/UIKit Extension/UITextField+Extension.swift diff --git a/ios-sdk b/ios-sdk index adea1a552..781633d39 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit adea1a55209069502d392b80019bfec908c5161e +Subproject commit 781633d391d856128a1c084d6ea17044bbbb2da0 diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 1c8a63896..23846840d 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -287,6 +287,9 @@ DC24B29825BA2A34005783E2 /* Branding.m in Sources */ = {isa = PBXBuildFile; fileRef = DC24B27225B9DF31005783E2 /* Branding.m */; }; DC24B2AB25BA316D005783E2 /* Branding+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC24B2AA25BA316D005783E2 /* Branding+App.swift */; }; DC24B31D25BB6FC4005783E2 /* IssuesCardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC24B31C25BB6FC4005783E2 /* IssuesCardViewController.swift */; }; + DC24E0EA28B36A81002E4F5B /* OCSearchSegment.h in Headers */ = {isa = PBXBuildFile; fileRef = DC24E0E828B36A81002E4F5B /* OCSearchSegment.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC24E0EB28B36A81002E4F5B /* OCSearchSegment.m in Sources */ = {isa = PBXBuildFile; fileRef = DC24E0E928B36A81002E4F5B /* OCSearchSegment.m */; }; + DC24E0F828B41694002E4F5B /* OCQueryCondition+SearchToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC24E0F728B41693002E4F5B /* OCQueryCondition+SearchToken.swift */; }; DC2565EE225F5A1900828AA5 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC2565E8225F5A1900828AA5 /* UserNotifications.framework */; }; DC26ADDE2550C0B20059680D /* MetadataDocumentationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC26ADDD2550C0B20059680D /* MetadataDocumentationTests.swift */; }; DC27A18E20CA9F66008ACB6C /* OCItem+FileProviderItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DC27A18D20CA9F66008ACB6C /* OCItem+FileProviderItem.m */; }; @@ -344,6 +347,9 @@ DC63208321FCAC1E007EC0A8 /* ClientActivityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC63208221FCAC1E007EC0A8 /* ClientActivityViewController.swift */; }; DC63208521FCEBE9007EC0A8 /* ClientActivityCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC63208421FCEBE9007EC0A8 /* ClientActivityCell.swift */; }; DC6428D02081406800493A01 /* CollapsibleProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC6428CF2081406800493A01 /* CollapsibleProgressBar.swift */; }; + DC65590F28A2633C0003D130 /* UITextField+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC65590E28A2633C0003D130 /* UITextField+Extension.swift */; }; + DC65592E28A644E10003D130 /* SearchTokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC65592D28A644E10003D130 /* SearchTokenizer.swift */; }; + DC65593228A648680003D130 /* SearchElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC65593128A648680003D130 /* SearchElement.swift */; }; DC66A9F4279EEBF900792AC8 /* ThemeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B394482385334D00892E8D /* ThemeView.swift */; }; DC66A9F6279EEC3100792AC8 /* ResourceViewHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC66A9F5279EEC3100792AC8 /* ResourceViewHost.swift */; }; DC66A9F8279F467200792AC8 /* UIKeyCommand+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC66A9F7279F467200792AC8 /* UIKeyCommand+Extension.swift */; }; @@ -1264,6 +1270,9 @@ DC24B27225B9DF31005783E2 /* Branding.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Branding.m; sourceTree = ""; }; DC24B2AA25BA316D005783E2 /* Branding+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Branding+App.swift"; sourceTree = ""; }; DC24B31C25BB6FC4005783E2 /* IssuesCardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssuesCardViewController.swift; sourceTree = ""; }; + DC24E0E828B36A81002E4F5B /* OCSearchSegment.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCSearchSegment.h; sourceTree = ""; }; + DC24E0E928B36A81002E4F5B /* OCSearchSegment.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCSearchSegment.m; sourceTree = ""; }; + DC24E0F728B41693002E4F5B /* OCQueryCondition+SearchToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCQueryCondition+SearchToken.swift"; sourceTree = ""; }; DC2565E8225F5A1900828AA5 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; DC26ADDD2550C0B20059680D /* MetadataDocumentationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataDocumentationTests.swift; sourceTree = ""; }; DC27A18C20CA9F66008ACB6C /* OCItem+FileProviderItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCItem+FileProviderItem.h"; sourceTree = ""; }; @@ -1342,6 +1351,9 @@ DC63208421FCEBE9007EC0A8 /* ClientActivityCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientActivityCell.swift; sourceTree = ""; }; DC63208621FCEE5D007EC0A8 /* ProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressView.swift; sourceTree = ""; }; DC6428CF2081406800493A01 /* CollapsibleProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleProgressBar.swift; sourceTree = ""; }; + DC65590E28A2633C0003D130 /* UITextField+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextField+Extension.swift"; sourceTree = ""; }; + DC65592D28A644E10003D130 /* SearchTokenizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTokenizer.swift; sourceTree = ""; }; + DC65593128A648680003D130 /* SearchElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchElement.swift; sourceTree = ""; }; DC66A9F5279EEC3100792AC8 /* ResourceViewHost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceViewHost.swift; sourceTree = ""; }; DC66A9F7279F467200792AC8 /* UIKeyCommand+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIKeyCommand+Extension.swift"; sourceTree = ""; }; DC66F39A239659C000CF4812 /* OCASN1.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCASN1.h; sourceTree = ""; }; @@ -1985,6 +1997,7 @@ DC3AB24328104AA500789435 /* UIFont+Weight.swift */, DC3AB2452810602500789435 /* UILabel+Extension.swift */, DC46F3D62845FCFA00038880 /* UIView+OCDataItem.swift */, + DC65590E28A2633C0003D130 /* UITextField+Extension.swift */, ); path = "UIKit Extension"; sourceTree = ""; @@ -2514,6 +2527,16 @@ path = "Data Item Interactions"; sourceTree = ""; }; + DC65592C28A644B60003D130 /* Tokenizer */ = { + isa = PBXGroup; + children = ( + DC65592D28A644E10003D130 /* SearchTokenizer.swift */, + DC65593128A648680003D130 /* SearchElement.swift */, + DC24E0F728B41693002E4F5B /* OCQueryCondition+SearchToken.swift */, + ); + path = Tokenizer; + sourceTree = ""; + }; DC66F3A723965BE300CF4812 /* Parser Support */ = { isa = PBXGroup; children = ( @@ -2550,6 +2573,8 @@ DC774E6122F44E6D000B11A1 /* OCCore+BundleImport.h */, DC7C100F24B5F81E00227085 /* OCBookmark+AppExtensions.m */, DC7C100E24B5F81E00227085 /* OCBookmark+AppExtensions.h */, + DC24E0E928B36A81002E4F5B /* OCSearchSegment.m */, + DC24E0E828B36A81002E4F5B /* OCSearchSegment.h */, DCB458EC2604A7D4006A02AB /* OCQueryCondition+SearchSegmenter.m */, DCB458EB2604A7D4006A02AB /* OCQueryCondition+SearchSegmenter.h */, ); @@ -2771,6 +2796,7 @@ children = ( DCB5D56A2861BEBE004AF425 /* SearchViewController.swift */, DCB5D5A828632C1B004AF425 /* Scopes */, + DC65592C28A644B60003D130 /* Tokenizer */, ); path = Search; sourceTree = ""; @@ -3414,6 +3440,7 @@ DCF072DC279857A300E0B01D /* OCCircularContentView.h in Headers */, DCFEFE39236877A7009A142F /* OCLicenseFeature.h in Headers */, DC23D1DA238F391200423F62 /* OCLicenseAppStoreReceipt.h in Headers */, + DC24E0EA28B36A81002E4F5B /* OCSearchSegment.h in Headers */, DC70398526128B89009F2DC1 /* NSString+ByteCountParser.h in Headers */, DCF072EC27986CCA00E0B01D /* OCResourceTextPlaceholder+ViewProvider.h in Headers */, DCF2DA8324C83BFB0026D790 /* OCFileProviderService.h in Headers */, @@ -4390,6 +4417,7 @@ DC04FFC827F5B79000F22569 /* CollectionViewController.swift in Sources */, DC3AB2422810404000789435 /* DriveHeaderCell.swift in Sources */, DCE4E44524C1A4260051722F /* FileListTableViewController.swift in Sources */, + DC65590F28A2633C0003D130 /* UITextField+Extension.swift in Sources */, DC46F3D72845FCFA00038880 /* UIView+OCDataItem.swift in Sources */, 399EA75A25E66DB000B6FF11 /* ClientItemResolvingCell.swift in Sources */, DCE4E43524C1999A0051722F /* Action.swift in Sources */, @@ -4471,6 +4499,8 @@ DC0A357824C0E43700FB58FC /* CardPresentationController.swift in Sources */, 393D2B3F23FEB6DC00ED4F8C /* DispatchQueueTools.swift in Sources */, DC66A9F6279EEC3100792AC8 /* ResourceViewHost.swift in Sources */, + DC65592E28A644E10003D130 /* SearchTokenizer.swift in Sources */, + DC24E0F828B41694002E4F5B /* OCQueryCondition+SearchToken.swift in Sources */, DC0A357324C0E42D00FB58FC /* PushTransitionDelegate.swift in Sources */, DCE4E45424C1EC040051722F /* BreadCrumbTableViewController.swift in Sources */, 399EA73A25E656A900B6FF11 /* UITableView+Extension.swift in Sources */, @@ -4497,6 +4527,7 @@ DC0A358324C0E44200FB58FC /* VectorImage.swift in Sources */, DC0A359824C0E68700FB58FC /* OCItem+AppExtension.swift in Sources */, DC0A358424C0E44200FB58FC /* VectorImageView.swift in Sources */, + DC65593228A648680003D130 /* SearchElement.swift in Sources */, DC0A355324C0E2C200FB58FC /* ClientItemCell.swift in Sources */, DC0A357D24C0E43C00FB58FC /* ThemeStyle.swift in Sources */, DC46F3CC2844A8EA00038880 /* ViewCell.swift in Sources */, @@ -4576,6 +4607,7 @@ DCCD778C2604C91B00098573 /* NSDate+ComputedTimes.m in Sources */, DC66F3A623965A1400CF4812 /* NSDate+RFC3339.m in Sources */, DCF575EC2796CBDF003BEBBA /* OCImage+ViewProvider.m in Sources */, + DC24E0EB28B36A81002E4F5B /* OCSearchSegment.m in Sources */, DCF2DA8724C87A330026D790 /* OCCore+FPServices.m in Sources */, DC7C101224B5FD6500227085 /* OCBookmark+AppExtensions.m in Sources */, DC4332012472E1B4002DC0E5 /* OCLicenseEMMProvider.m in Sources */, diff --git a/ownCloud.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ownCloud.xcworkspace/xcshareddata/swiftpm/Package.resolved index b14d7da78..e2b9b6417 100644 --- a/ownCloud.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ownCloud.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,14 @@ { "pins" : [ + { + "identity" : "down", + "kind" : "remoteSourceControl", + "location" : "https://github.com/johnxnguyen/Down", + "state" : { + "branch" : "master", + "revision" : "e754ab1c80920dd51a8e08290c912ac1c2ac8b58" + } + }, { "identity" : "openssl", "kind" : "remoteSourceControl", @@ -14,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/microsoft/plcrashreporter.git", "state" : { - "revision" : "4637a7854de2cc5c354d46fb931d74bdbc2c043e", - "version" : "1.7.0" + "revision" : "81cdec2b3827feb03286cb297f4c501a8eb98df1", + "version" : "1.10.2" } }, { diff --git a/ownCloudAppFramework/Resources/en.lproj/Localizable.strings b/ownCloudAppFramework/Resources/en.lproj/Localizable.strings index df22ef621..6f84b7232 100644 --- a/ownCloudAppFramework/Resources/en.lproj/Localizable.strings +++ b/ownCloudAppFramework/Resources/en.lproj/Localizable.strings @@ -64,3 +64,51 @@ "keyword_w" = "w"; /* short-form for "week" */ "keyword_m" = "m"; /* short-form for "month" */ "keyword_y" = "y"; /* short-form for "year" */ + +/* Search token labels */ +"No folder" = "No folder"; +"Folder" = "Folder"; + +"No file" = "No file"; +"File" = "File"; + +"No image" = "No image"; +"Image" = "Image"; + +"No video" = "No video"; +"Video" = "Video"; + +"Before" = "Before"; +"After" = "After"; + +"Not on" = "Not on"; +"On" = "On"; +"Not" = "Not"; + +"Before today" = "Before today"; +"Before yesterday" = "Before yesterday"; +">%d days ago" = ">%d days ago"; +"Today" = "Today"; +"Since yesterday" = "Since yesterday"; +"Last %d days" = "Last %d days"; + +"Before this week" = "Before this week"; +"Before last week" = "Before last week"; +">%d weeks ago" = ">%d weeks ago"; +"This week" = "This week"; +"Since last week" = "Since last week"; +"Last %d weeks" = "Last %d weeks"; + +"Before this month" = "Before this month"; +"Before last month" = "Before last month"; +"> %d months ago" = "> %d months ago"; +"This month" = "This month"; +"Since last month" = "Since last month"; +"Last %d months" = "Last %d months"; + +"Before this year" = "Before this year"; +"Before last year" = "Before last year"; +"> %d years ago" = "> %d years ago"; +"This year" = "This year"; +"Since last year" = "Since last year"; +"Last %d years" = "Last %d years"; diff --git a/ownCloudAppFramework/SDK Extensions/OCQueryCondition+SearchSegmenter.h b/ownCloudAppFramework/SDK Extensions/OCQueryCondition+SearchSegmenter.h index 03a5d35c0..1cdcc802f 100644 --- a/ownCloudAppFramework/SDK Extensions/OCQueryCondition+SearchSegmenter.h +++ b/ownCloudAppFramework/SDK Extensions/OCQueryCondition+SearchSegmenter.h @@ -17,11 +17,13 @@ */ #import +#import "OCSearchSegment.h" NS_ASSUME_NONNULL_BEGIN @interface NSString (SearchSegmenter) +- (NSArray *)segmentedForSearchWithQuotationMarks:(BOOL)withQuotationMarks cursorPosition:(nullable NSNumber *)inCursorPosition; - (NSArray *)segmentedForSearchWithQuotationMarks:(BOOL)withQuotationMarks; @end @@ -33,4 +35,20 @@ NS_ASSUME_NONNULL_BEGIN @end +@interface OCQueryCondition (SearchSegmentDescription) + +@property(strong,nonatomic,nullable) NSString *symbolName; //!< Optional, name of symbol to use +@property(strong,nonatomic,nullable) NSString *localizedDescription; //!< Optional, localized description +@property(strong,nonatomic,nullable) NSString *searchSegment; //!< Optional, search segment from which this condition was created + +@property(readonly,strong,nullable) NSString *composedSearchTerm; //!< Composes/reassembles a search term from OCQueryConditions returned from the OCQueryCondition-SearchSegmenter. Useful for persisting a query in readable form, allowing to retain its dynamic elements. (f.ex. when converting :today to an OCQueryCondition, it will always contain the day's date as reference point. Converting the term on another day will use a different date (that day's "today") in the converted query condition.) + +- (instancetype)withSymbolName:(nullable NSString *)symbolName localizedDescription:(nullable NSString *)localizedDescription searchSegment:(nullable NSString *)searchSegment; + +@end + +extern OCQueryConditionUserInfoKey OCQueryConditionUserInfoKeySymbolName; +extern OCQueryConditionUserInfoKey OCQueryConditionUserInfoKeyLocalizedDescription; +extern OCQueryConditionUserInfoKey OCQueryConditionUserInfoKeySearchSegment; + NS_ASSUME_NONNULL_END diff --git a/ownCloudAppFramework/SDK Extensions/OCQueryCondition+SearchSegmenter.m b/ownCloudAppFramework/SDK Extensions/OCQueryCondition+SearchSegmenter.m index dd40d0aa0..c6e27f8e4 100644 --- a/ownCloudAppFramework/SDK Extensions/OCQueryCondition+SearchSegmenter.m +++ b/ownCloudAppFramework/SDK Extensions/OCQueryCondition+SearchSegmenter.m @@ -48,9 +48,9 @@ - (BOOL)hasQuotationMarkPrefix return (NO); } -- (NSArray *)segmentedForSearchWithQuotationMarks:(BOOL)withQuotationMarks +- (NSArray *)segmentedForSearchWithQuotationMarks:(BOOL)withQuotationMarks cursorPosition:(NSNumber *)inCursorPosition { - NSMutableArray *segments = [NSMutableArray new]; + NSMutableArray *searchSegments = [NSMutableArray new]; NSArray *terms; if ((terms = [self componentsSeparatedByString:@" "]) != nil) @@ -58,18 +58,39 @@ - (BOOL)hasQuotationMarkPrefix __block NSString *segmentString = nil; __block BOOL segmentOpen = NO; __block BOOL isNegated = NO; + __block NSRange termRange = NSMakeRange(0, 0); + NSUInteger termOffset = 0; void (^SubmitSegment)(void) = ^{ if (segmentString.length > 0) { + OCSearchSegment *segment = [OCSearchSegment new]; + + segment.range = termRange; + segment.originalString = [self substringWithRange:termRange]; + segment.cursorOffset = -1; + + if (inCursorPosition != nil) + { + NSUInteger cursorPosition = inCursorPosition.unsignedIntegerValue; + + if ((cursorPosition > termRange.location) && (cursorPosition <= (termRange.location + termRange.length))) + { + segment.hasCursor = YES; + segment.cursorOffset = cursorPosition - termRange.location; + } + } + if (segmentOpen && withQuotationMarks) { - [segments addObject:[NSString stringWithFormat:@"%@\"%@\"", (isNegated ? @"-" : @""), segmentString]]; + segment.segmentedString = [NSString stringWithFormat:@"%@\"%@\"", (isNegated ? @"-" : @""), segmentString]; } else { - [segments addObject:(isNegated ? [@"-" stringByAppendingString:segmentString] : segmentString)]; + segment.segmentedString = isNegated ? [@"-" stringByAppendingString:segmentString] : segmentString; } + + [searchSegments addObject:segment]; } segmentString = nil; @@ -80,6 +101,9 @@ - (BOOL)hasQuotationMarkPrefix NSString *term = inTerm; BOOL closingSegment = NO; + termRange.location += termOffset; + termRange.length = inTerm.length; + if (!segmentOpen) { isNegated = NO; @@ -123,6 +147,8 @@ - (BOOL)hasQuotationMarkPrefix else { // Append to segment string + termRange.location -= (segmentString.length + 1); + termRange.length += (segmentString.length + 1); segmentString = [segmentString stringByAppendingFormat:@" %@", term]; } @@ -132,12 +158,23 @@ - (BOOL)hasQuotationMarkPrefix SubmitSegment(); segmentOpen = NO; } + + termOffset = termRange.length + 1; } SubmitSegment(); } - return (segments); + return (searchSegments); +} + +- (NSArray *)segmentedForSearchWithQuotationMarks:(BOOL)withQuotationMarks +{ + NSArray *searchSegments = [self segmentedForSearchWithQuotationMarks:withQuotationMarks cursorPosition:nil]; + + return ([searchSegments arrayUsingMapper:^NSString* _Nullable(OCSearchSegment* _Nonnull segment) { + return (segment.segmentedString); + }]); } @end @@ -211,6 +248,7 @@ + (nullable NSString *)normalizeKeyword:(NSString *)keyword + (instancetype)forSearchSegment:(NSString *)segmentString { NSString *segmentStringLowercase = nil; + NSString *searchSegment = segmentString; BOOL negateCondition = NO; BOOL literalSearch = NO; @@ -241,35 +279,35 @@ + (instancetype)forSearchSegment:(NSString *)segmentString { if ([keyword isEqual:@"folder"]) { - return ([OCQueryCondition negating:negateCondition condition:[OCQueryCondition where:OCItemPropertyNameType isEqualTo:@(OCItemTypeCollection)]]); + return ([[OCQueryCondition negating:negateCondition condition:[OCQueryCondition where:OCItemPropertyNameType isEqualTo:@(OCItemTypeCollection)]] withSymbolName:@"folder" localizedDescription:(negateCondition ? OCLocalized(@"No folder") : OCLocalized(@"Folder")) searchSegment:searchSegment]); } else if ([keyword isEqual:@"file"]) { - return ([OCQueryCondition negating:negateCondition condition:[OCQueryCondition where:OCItemPropertyNameType isEqualTo:@(OCItemTypeFile)]]); + return ([[OCQueryCondition negating:negateCondition condition:[OCQueryCondition where:OCItemPropertyNameType isEqualTo:@(OCItemTypeFile)]] withSymbolName:@"doc" localizedDescription:(negateCondition ? OCLocalized(@"No file") : OCLocalized(@"File")) searchSegment:searchSegment]); } else if ([keyword isEqual:@"image"]) { - return ([OCQueryCondition negating:negateCondition condition:[OCQueryCondition where:OCItemPropertyNameMIMEType startsWith:@"image/"]]); + return ([[OCQueryCondition negating:negateCondition condition:[OCQueryCondition where:OCItemPropertyNameMIMEType startsWith:@"image/"]] withSymbolName:@"photo" localizedDescription:(negateCondition ? OCLocalized(@"No image") : OCLocalized(@"Image")) searchSegment:searchSegment]); } else if ([keyword isEqual:@"video"]) { - return ([OCQueryCondition negating:negateCondition condition:[OCQueryCondition where:OCItemPropertyNameMIMEType startsWith:@"video/"]]); + return ([[OCQueryCondition negating:negateCondition condition:[OCQueryCondition where:OCItemPropertyNameMIMEType startsWith:@"video/"]] withSymbolName:@"film" localizedDescription:(negateCondition ? OCLocalized(@"No video") : OCLocalized(@"Video")) searchSegment:searchSegment]); } else if ([keyword isEqual:@"today"]) { - return ([OCQueryCondition negating:negateCondition condition:[OCQueryCondition where:OCItemPropertyNameLastModified isGreaterThan:[NSDate startOfRelativeDay:0]]]); + return ([[OCQueryCondition negating:negateCondition condition:[OCQueryCondition where:OCItemPropertyNameLastModified isGreaterThan:[NSDate startOfRelativeDay:0]]] withSymbolName:@"calendar" localizedDescription:(negateCondition ? OCLocalized(@"Before today") : OCLocalized(@"Today")) searchSegment:searchSegment]); } else if ([keyword isEqual:@"week"]) { - return ([OCQueryCondition negating:negateCondition condition:[OCQueryCondition where:OCItemPropertyNameLastModified isGreaterThan:[NSDate startOfRelativeWeek:0]]]); + return ([[OCQueryCondition negating:negateCondition condition:[OCQueryCondition where:OCItemPropertyNameLastModified isGreaterThan:[NSDate startOfRelativeWeek:0]]] withSymbolName:@"calendar" localizedDescription:(negateCondition ? OCLocalized(@"Before this week") : OCLocalized(@"This week")) searchSegment:searchSegment]); } else if ([keyword isEqual:@"month"]) { - return ([OCQueryCondition negating:negateCondition condition:[OCQueryCondition where:OCItemPropertyNameLastModified isGreaterThan:[NSDate startOfRelativeMonth:0]]]); + return ([[OCQueryCondition negating:negateCondition condition:[OCQueryCondition where:OCItemPropertyNameLastModified isGreaterThan:[NSDate startOfRelativeMonth:0]]] withSymbolName:@"calendar" localizedDescription:(negateCondition ? OCLocalized(@"Before this month") : OCLocalized(@"This month")) searchSegment:searchSegment]); } else if ([keyword isEqual:@"year"]) { - return ([OCQueryCondition negating:negateCondition condition:[OCQueryCondition where:OCItemPropertyNameLastModified isGreaterThan:[NSDate startOfRelativeYear:0]]]); + return ([[OCQueryCondition negating:negateCondition condition:[OCQueryCondition where:OCItemPropertyNameLastModified isGreaterThan:[NSDate startOfRelativeYear:0]]] withSymbolName:@"calendar" localizedDescription:(negateCondition ? OCLocalized(@"Before this year") : OCLocalized(@"This year")) searchSegment:searchSegment]); } } } @@ -287,6 +325,28 @@ + (instancetype)forSearchSegment:(NSString *)segmentString if ((modifierKeyword = [OCQueryCondition normalizeKeyword:modifier]) != nil) { + __block NSString *symbolName = nil; + __block NSString *localizedStart = nil; + __block NSString *localizedParameterDescriptions = nil; + + void (^ComposeAndSetDescription)(OCQueryCondition *condition, NSString *symName, NSString *descStart, NSString *paramDesc) = ^(OCQueryCondition *condition, NSString *symName, NSString *descStart, NSString *paramDesc) { + condition.symbolName = symbolName; + condition.localizedDescription = [NSString stringWithFormat:@"%@%@%@", ((descStart != nil) ? descStart : @""), (((descStart != nil) && (paramDesc != nil)) ? @" " : @""), ((paramDesc != nil) ? paramDesc : @"")]; + }; + + void (^AddDescription)(OCQueryCondition *condition, NSString *symName, NSString *descStart, NSString *paramDesc) = ^(OCQueryCondition *condition, NSString *symName, NSString *descStart, NSString *paramDesc) { + symbolName = symName; + localizedStart = descStart; + + if (localizedParameterDescriptions == nil) { + localizedParameterDescriptions = paramDesc; + } else { + localizedParameterDescriptions = [NSString stringWithFormat:@"%@, %@", localizedParameterDescriptions, paramDesc]; + } + + ComposeAndSetDescription(condition, symName, descStart, paramDesc); + }; + for (NSString *parameter in parameters) { if (parameter.length > 0) @@ -296,6 +356,7 @@ + (instancetype)forSearchSegment:(NSString *)segmentString if ([modifierKeyword isEqual:@"type"]) { condition = [OCQueryCondition where:OCItemPropertyNameName endsWith:[@"." stringByAppendingString:parameter]]; + AddDescription(condition, @"circlebadge.fill", nil, parameter); } else if ([modifierKeyword isEqual:@"after"]) { @@ -304,6 +365,7 @@ + (instancetype)forSearchSegment:(NSString *)segmentString if ((afterDate = [NSDate dateFromKeywordString:parameter]) != nil) { condition = [OCQueryCondition where:OCItemPropertyNameLastModified isGreaterThan:afterDate]; + AddDescription(condition, @"calendar", negateCondition ? OCLocalized(@"Before") : OCLocalized(@"After"), [afterDate localizedStringWithTemplate:@"MMM d, yy" locale:nil]); } } else if ([modifierKeyword isEqual:@"before"]) @@ -313,6 +375,7 @@ + (instancetype)forSearchSegment:(NSString *)segmentString if ((beforeDate = [NSDate dateFromKeywordString:parameter]) != nil) { condition = [OCQueryCondition where:OCItemPropertyNameLastModified isLessThan:beforeDate]; + AddDescription(condition, @"calendar", negateCondition ? OCLocalized(@"After") : OCLocalized(@"Before"), [beforeDate localizedStringWithTemplate:@"MMM d, yy" locale:nil]); } } else if ([modifierKeyword isEqual:@"on"]) @@ -328,6 +391,7 @@ + (instancetype)forSearchSegment:(NSString *)segmentString [OCQueryCondition where:OCItemPropertyNameLastModified isGreaterThan:onStartDate], [OCQueryCondition where:OCItemPropertyNameLastModified isLessThan:onEndDate] ]]; + AddDescription(condition, @"calendar", negateCondition ? OCLocalized(@"Not on") : OCLocalized(@"On"), [onStartDate localizedStringWithTemplate:@"MMM d, yy" locale:nil]); } } else if ([modifierKeyword isEqual:@"smaller"]) @@ -337,6 +401,7 @@ + (instancetype)forSearchSegment:(NSString *)segmentString if (byteCount != nil) { condition = [OCQueryCondition where:OCItemPropertyNameSize isLessThan:byteCount]; + AddDescription(condition, negateCondition ? @"greaterthan.square" : @"lessthan.square", nil, [NSByteCountFormatter stringFromByteCount:byteCount.longLongValue countStyle:NSByteCountFormatterCountStyleFile]); } } else if ([modifierKeyword isEqual:@"greater"]) @@ -346,11 +411,13 @@ + (instancetype)forSearchSegment:(NSString *)segmentString if (byteCount != nil) { condition = [OCQueryCondition where:OCItemPropertyNameSize isGreaterThan:byteCount]; + AddDescription(condition, negateCondition ? @"lessthan.square" : @"greaterthan.square", nil, [NSByteCountFormatter stringFromByteCount:byteCount.longLongValue countStyle:NSByteCountFormatterCountStyleFile]); } } else if ([modifierKeyword isEqual:@"owner"]) { condition = [OCQueryCondition where:OCItemPropertyNameOwnerUserName startsWith:parameter]; + AddDescription(condition, @"person.crop.circle", nil, negateCondition ? [OCLocalized(@"Not") stringByAppendingFormat:@" %@", parameter] : parameter); } else if ([modifier isEqual:@""]) { @@ -366,24 +433,63 @@ + (instancetype)forSearchSegment:(NSString *)segmentString { NSInteger numParam = numString.integerValue; NSString *timeLabel = [parameter substringFromIndex:parameter.length-1].lowercaseString; + NSString *localizedDescription = nil; + NSDate *greaterThanDate = nil; timeLabel = [OCQueryCondition normalizeKeyword:timeLabel]; if ([timeLabel isEqual:@"d"]) { - condition = [OCQueryCondition where:OCItemPropertyNameLastModified isGreaterThan:[NSDate startOfRelativeDay:-numParam]]; + greaterThanDate = [NSDate startOfRelativeDay:-numParam]; + if (negateCondition) + { + localizedDescription = (numParam == 0) ? OCLocalized(@"Before today") : ((numParam == 1) ? OCLocalized(@"Before yesterday") : [NSString stringWithFormat:OCLocalized(@">%d days ago"), numParam]); + } + else + { + localizedDescription = (numParam == 0) ? OCLocalized(@"Today") : ((numParam == 1) ? OCLocalized(@"Since yesterday") : [NSString stringWithFormat:OCLocalized(@"Last %d days"), numParam]); + } } else if ([timeLabel isEqual:@"w"]) { - condition = [OCQueryCondition where:OCItemPropertyNameLastModified isGreaterThan:[NSDate startOfRelativeWeek:-numParam]]; + greaterThanDate = [NSDate startOfRelativeWeek:-numParam]; + if (negateCondition) + { + localizedDescription = (numParam == 0) ? OCLocalized(@"Before this week") : ((numParam == 1) ? OCLocalized(@"Before last week") : [NSString stringWithFormat:OCLocalized(@">%d weeks ago"), numParam]); + } + else + { + localizedDescription = (numParam == 0) ? OCLocalized(@"This week") : ((numParam == 1) ? OCLocalized(@"Since last week") : [NSString stringWithFormat:OCLocalized(@"Last %d weeks"), numParam]); + } } else if ([timeLabel isEqual:@"m"]) { - condition = [OCQueryCondition where:OCItemPropertyNameLastModified isGreaterThan:[NSDate startOfRelativeMonth:-numParam]]; + greaterThanDate = [NSDate startOfRelativeMonth:-numParam]; + if (negateCondition) + { + localizedDescription = (numParam == 0) ? OCLocalized(@"Before this month") : ((numParam == 1) ? OCLocalized(@"Before last month") : [NSString stringWithFormat:OCLocalized(@"> %d months ago"), numParam]); + } + else + { + localizedDescription = (numParam == 0) ? OCLocalized(@"This month") : ((numParam == 1) ? OCLocalized(@"Since last month") : [NSString stringWithFormat:OCLocalized(@"Last %d months"), numParam]); + } } else if ([timeLabel isEqual:@"y"]) { - condition = [OCQueryCondition where:OCItemPropertyNameLastModified isGreaterThan:[NSDate startOfRelativeYear:-numParam]]; + greaterThanDate = [NSDate startOfRelativeYear:-numParam]; + if (negateCondition) + { + localizedDescription = (numParam == 0) ? OCLocalized(@"Before this year") : ((numParam == 1) ? OCLocalized(@"Before last year") : [NSString stringWithFormat:OCLocalized(@"> %d years ago"), numParam]); + } + else + { + localizedDescription = (numParam == 0) ? OCLocalized(@"This year") : ((numParam == 1) ? OCLocalized(@"Since last year") : [NSString stringWithFormat:OCLocalized(@"Last %d years"), numParam]); + } + } + + if (greaterThanDate != nil) + { + condition = [[OCQueryCondition where:OCItemPropertyNameLastModified isGreaterThan:greaterThanDate] withSymbolName:@"calendar" localizedDescription:localizedDescription searchSegment:nil]; } } } @@ -397,11 +503,19 @@ + (instancetype)forSearchSegment:(NSString *)segmentString if (orConditions.count == 1) { - return ([OCQueryCondition negating:negateCondition condition:orConditions.firstObject]); + OCQueryCondition *composedCondition = [OCQueryCondition negating:negateCondition condition:orConditions.firstObject]; + + composedCondition.searchSegment = searchSegment; + return (composedCondition); } else if (orConditions.count > 0) { - return ([OCQueryCondition negating:negateCondition condition:[OCQueryCondition anyOf:orConditions]]); + OCQueryCondition *composedCondition = [OCQueryCondition negating:negateCondition condition:[OCQueryCondition anyOf:orConditions]]; + + ComposeAndSetDescription(composedCondition, symbolName, localizedStart, localizedParameterDescriptions); + + composedCondition.searchSegment = searchSegment; + return (composedCondition); } else { @@ -422,7 +536,9 @@ + (instancetype)forSearchSegment:(NSString *)segmentString } } - return ([OCQueryCondition negating:negateCondition condition:[OCQueryCondition where:OCItemPropertyNameName contains:segmentString]]); + OCQueryCondition *nameCondition = [OCQueryCondition negating:negateCondition condition:[OCQueryCondition where:OCItemPropertyNameName contains:segmentString]]; + nameCondition.searchSegment = searchSegment; + return (nameCondition); } + (instancetype)fromSearchTerm:(NSString *)searchTerm @@ -454,3 +570,129 @@ + (instancetype)fromSearchTerm:(NSString *)searchTerm } @end + +@implementation OCQueryCondition (SearchSegmentDescription) + +- (void)setValue:(id)value forMutableUserInfoKey:(OCQueryConditionUserInfoKey)key +{ + NSMutableDictionary *userInfo = nil; + + if ((userInfo = (NSMutableDictionary *)self.userInfo) != nil) + { + if (![userInfo isKindOfClass:NSMutableDictionary.class]) + { + userInfo = [[NSMutableDictionary alloc] initWithDictionary:userInfo]; + } + } + else + { + userInfo = [NSMutableDictionary new]; + } + + userInfo[key] = value; + + self.userInfo = userInfo; + +} + +- (NSString *)symbolName +{ + return (self.userInfo[OCQueryConditionUserInfoKeySymbolName]); +} + +- (void)setSymbolName:(NSString *)symbolName +{ + [self setValue:symbolName forMutableUserInfoKey:OCQueryConditionUserInfoKeySymbolName]; +} + +- (NSString *)localizedDescription +{ + return (self.userInfo[OCQueryConditionUserInfoKeyLocalizedDescription]); +} + +- (void)setLocalizedDescription:(NSString *)localizedDescription +{ + [self setValue:localizedDescription forMutableUserInfoKey:OCQueryConditionUserInfoKeyLocalizedDescription]; +} + +- (NSString *)searchSegment +{ + return (self.userInfo[OCQueryConditionUserInfoKeySearchSegment]); +} + +- (void)setSearchSegment:(NSString *)searchSegment +{ + [self setValue:searchSegment forMutableUserInfoKey:OCQueryConditionUserInfoKeySearchSegment]; +} + +- (void)_addToComposedSearchTerm:(NSMutableString *)composedSearchTerm +{ + NSString *searchSegment = self.searchSegment; + + if (searchSegment.length > 0) + { + if (composedSearchTerm.length > 0) + { + [composedSearchTerm appendFormat:@" %@", searchSegment]; + } + else + { + [composedSearchTerm appendString:searchSegment]; + } + } + + switch (self.operator) + { + case OCQueryConditionOperatorNegate: + case OCQueryConditionOperatorAnd: + case OCQueryConditionOperatorOr: { + OCQueryCondition *containedCondition; + NSArray *containedConditions; + + if ((containedCondition = OCTypedCast(self.value, OCQueryCondition)) != nil) + { + [containedCondition _addToComposedSearchTerm:composedSearchTerm]; + } + else if ((containedConditions = OCTypedCast(self.value, NSArray)) != nil) + { + for (OCQueryCondition *condition in containedConditions) + { + [condition _addToComposedSearchTerm:composedSearchTerm]; + } + } + } + break; + + default: + break; + } +} + +- (NSString *)composedSearchTerm +{ + NSMutableString *composedSearchTerm = [NSMutableString new]; + + [self _addToComposedSearchTerm:composedSearchTerm]; + + if (composedSearchTerm.length > 0) + { + return (composedSearchTerm); + } + + return(nil); +} + +- (instancetype)withSymbolName:(NSString *)symbolName localizedDescription:(NSString *)localizedDescription searchSegment:(NSString *)searchSegment +{ + self.symbolName = symbolName; + self.localizedDescription = localizedDescription; + self.searchSegment = searchSegment; + + return (self); +} + +@end + +OCQueryConditionUserInfoKey OCQueryConditionUserInfoKeySymbolName = @"symbolName"; +OCQueryConditionUserInfoKey OCQueryConditionUserInfoKeyLocalizedDescription = @"localizedDescription"; +OCQueryConditionUserInfoKey OCQueryConditionUserInfoKeySearchSegment = @"searchSegment"; diff --git a/ownCloudAppFramework/SDK Extensions/OCSearchSegment.h b/ownCloudAppFramework/SDK Extensions/OCSearchSegment.h new file mode 100644 index 000000000..877aed365 --- /dev/null +++ b/ownCloudAppFramework/SDK Extensions/OCSearchSegment.h @@ -0,0 +1,35 @@ +// +// OCSearchSegment.h +// ownCloudApp +// +// Created by Felix Schwarz on 22.08.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface OCSearchSegment : NSObject + +@property(assign) NSRange range; //!< The range of the segment within the search term it was extracted from. +@property(assign) BOOL hasCursor; //!< YES if the cursor is currently placed at the end or inside this segment. + +@property(strong) NSString *originalString; //!< Original segment string, before normalization. +@property(assign) NSInteger cursorOffset; //!< If .hasCursor is YES, the position of the cursor within the originalString. -1 otherwise. + +@property(strong) NSString *segmentedString; //!< The normalized string of this segment. + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudAppFramework/SDK Extensions/OCSearchSegment.m b/ownCloudAppFramework/SDK Extensions/OCSearchSegment.m new file mode 100644 index 000000000..26453a162 --- /dev/null +++ b/ownCloudAppFramework/SDK Extensions/OCSearchSegment.m @@ -0,0 +1,36 @@ +// +// OCSearchSegment.m +// ownCloudApp +// +// Created by Felix Schwarz on 22.08.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import "OCSearchSegment.h" +#import + +@implementation OCSearchSegment + +#pragma mark - Description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@ hasCursor: %d, cursorOffset: %ld, range: %@>", NSStringFromClass(self.class), self, + OCExpandVar(originalString), + OCExpandVar(segmentedString), + _hasCursor, + _cursorOffset, + NSStringFromRange(_range) + ]); +} + +@end diff --git a/ownCloudAppFramework/ownCloudApp.h b/ownCloudAppFramework/ownCloudApp.h index c4cec81f9..4417057dc 100644 --- a/ownCloudAppFramework/ownCloudApp.h +++ b/ownCloudAppFramework/ownCloudApp.h @@ -30,6 +30,7 @@ FOUNDATION_EXPORT const unsigned char ownCloudAppVersionString[]; #import #import #import +#import #import #import #import diff --git a/ownCloudAppFrameworkTests/SearchSegmentationTests.m b/ownCloudAppFrameworkTests/SearchSegmentationTests.m index 56400f7da..9421261dd 100644 --- a/ownCloudAppFrameworkTests/SearchSegmentationTests.m +++ b/ownCloudAppFrameworkTests/SearchSegmentationTests.m @@ -60,6 +60,157 @@ - (void)testStringSegmentation }]; } +- (void)testStringSegmentationWithCursorPosition +{ + NSArray *> *testCases = @[ + @{ + @"term" : @"012 456", + @"cursorPosition" : @(2), + @"expectedSegments" : @[ + @"012", + @"456" + ], + @"expectedSegmentOffset" : @(0) + }, + + @{ + @"term" : @"012 456", + @"cursorPosition" : @(4), + @"expectedSegments" : @[ + @"012", + @"456" + ], + @"expectedSegmentOffset" : @(-1) + }, + + @{ + @"term" : @"012 456", + @"cursorPosition" : @(5), + @"expectedSegments" : @[ + @"012", + @"456" + ], + @"expectedSegmentOffset" : @(1) + }, + + @{ + @"term" : @"012 456", + @"cursorPosition" : @(6), + @"expectedSegments" : @[ + @"012", + @"456" + ], + @"expectedSegmentOffset" : @(1) + }, + + @{ + @"term" : @"012 456", + @"cursorPosition" : @(7), + @"expectedSegments" : @[ + @"012", + @"456" + ], + @"expectedSegmentOffset" : @(1) + }, + + @{ + @"term" : @"012 456 ", + @"cursorPosition" : @(8), + @"expectedSegments" : @[ + @"012", + @"456" + ], + @"expectedSegmentOffset" : @(-1) + }, + + @{ + @"term" : @"123 \"678 ", + @"cursorPosition" : @(9), + @"expectedSegments" : @[ + @"123", + @"\"678 \"" + ], + @"expectedSegmentOffset" : @(1) + }, + + @{ + @"term" : @"123 \"678 X\"", + @"cursorPosition" : @(10), + @"expectedSegments" : @[ + @"123", + @"\"678 X\"" + ], + @"expectedSegmentOffset" : @(1) + }, + + @{ + @"term" : @"123 \"678 X\" ", + @"cursorPosition" : @(11), + @"expectedSegments" : @[ + @"123", + @"\"678 X\"" + ], + @"expectedSegmentOffset" : @(1) + }, + + @{ + @"term" : @"123 \"678 X\" ", + @"cursorPosition" : @(12), + @"expectedSegments" : @[ + @"123", + @"\"678 X\"" + ], + @"expectedSegmentOffset" : @(-1) + }, + + @{ + @"term" : @"123 \"678 X ", + @"cursorPosition" : @(11), + @"expectedSegments" : @[ + @"123", + @"\"678 X \"" + ], + @"expectedSegmentOffset" : @(1) + }, + + @{ + @"term" : @"123 \"678 X ", + @"cursorPosition" : @(12), + @"expectedSegments" : @[ + @"123", + @"\"678 X \"" + ], + @"expectedSegmentOffset" : @(-1) + } + ]; + + for (NSDictionary *testCase in testCases) + { + NSString *term = testCase[@"term"]; + NSArray *expectedSegments = testCase[@"expectedSegments"]; + NSNumber *cursorPosition = testCase[@"cursorPosition"]; + NSNumber *expectedSegmentOffset = testCase[@"expectedSegmentOffset"]; + __block NSInteger segmentWithCursorOffset = -1; + + NSArray *searchSegments = [term segmentedForSearchWithQuotationMarks:YES cursorPosition:cursorPosition]; + NSMutableArray *segments = [NSMutableArray new]; + + [searchSegments enumerateObjectsUsingBlock:^(OCSearchSegment * _Nonnull searchSegment, NSUInteger idx, BOOL * _Nonnull stop) { + [segments addObject:searchSegment.segmentedString]; + if (searchSegment.hasCursor) + { + segmentWithCursorOffset = idx; + } + }]; + + XCTAssert([segments isEqual:expectedSegments], @"segments %@ doesn't match expectation %@", segments, expectedSegments); + if (expectedSegmentOffset != nil) + { + XCTAssert((segmentWithCursorOffset == expectedSegmentOffset.integerValue), @"segment cursor offset %ld doesn't match expectation %ld for cursor position %@", segmentWithCursorOffset, expectedSegmentOffset.integerValue, cursorPosition); + } + } +} + - (void)testDateComputations { NSLog(@"Start of day(-2): %@", [NSDate startOfRelativeDay:-2]); diff --git a/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift b/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift index 5034be31c..7ca1c14c0 100644 --- a/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift +++ b/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift @@ -29,6 +29,8 @@ open class SearchScope: NSObject { public var clientContext: ClientContext + public var tokenizer: SearchTokenizer? + static public func modifyingQuery(with context: ClientContext, localizedName: String) -> SearchScope { return QueryModifyingSearchScope(with: context, cellStyle: nil, localizedName: localizedName) } @@ -47,11 +49,12 @@ open class SearchScope: NSObject { super.init() + tokenizer = SearchTokenizer(scope: self, clientContext: context) + resultsCellStyle = cellStyle } - open func updateForSearchTerm(_ term: String?) { - + open func updateFor(_ searchElements: [SearchElement]) { } } @@ -86,12 +89,19 @@ open class ItemSearchScope : SearchScope { open var searchTerm: String? - open override func updateForSearchTerm(_ term: String?) { + open override func updateFor(_ searchElements: [SearchElement]) { if isSelected { - searchTerm = term + var queryConditions : [OCQueryCondition] = [] + + for searchElement in searchElements { + if let queryCondition = searchElement.representedObject as? OCQueryCondition { + queryConditions.append(queryCondition) + } + } - if let searchText = term { - queryCondition = OCQueryCondition.fromSearchTerm(searchText) + if queryConditions.count > 0 { + queryCondition = OCQueryCondition.require(queryConditions) + Log.debug("Assembled search: \(queryCondition!.composedSearchTerm)") } else { queryCondition = nil } diff --git a/ownCloudAppShared/Client/Search/SearchViewController.swift b/ownCloudAppShared/Client/Search/SearchViewController.swift index e11d7fc9f..16ce9979f 100644 --- a/ownCloudAppShared/Client/Search/SearchViewController.swift +++ b/ownCloudAppShared/Client/Search/SearchViewController.swift @@ -213,8 +213,7 @@ open class SearchViewController: UIViewController, UITextFieldDelegate, Themeabl } func sendSearchFieldContentsToActiveScope() { - let searchText = searchField.text - self.activeScope?.updateForSearchTerm((searchText != "") ? searchText : nil) + self.activeScope?.tokenizer?.updateFor(searchField: searchField) } // MARK: - End search diff --git a/ownCloudAppShared/Client/Search/Tokenizer/OCQueryCondition+SearchToken.swift b/ownCloudAppShared/Client/Search/Tokenizer/OCQueryCondition+SearchToken.swift new file mode 100644 index 000000000..f202fab3a --- /dev/null +++ b/ownCloudAppShared/Client/Search/Tokenizer/OCQueryCondition+SearchToken.swift @@ -0,0 +1,111 @@ +// +// OCQueryCondition+SearchToken.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 22.08.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK + +extension OCQueryCondition { + var firstNonLogicalCondition: OCQueryCondition? { + switch self.operator { + case .negate, .or, .and: + if let condition = self.value as? OCQueryCondition { + return condition + } + + default: + return self + } + + return self + } + + var firstDescriptiveCondition: OCQueryCondition? { + if localizedDescription != nil { + return self + } else { + if let condition = self.value as? OCQueryCondition { + return condition.firstDescriptiveCondition + } else if let conditions = self.value as? [OCQueryCondition] { + for condition in conditions { + if let descriptiveCondition = condition.firstDescriptiveCondition { + return descriptiveCondition + } + } + } + } + + return nil + } + + func generateSearchToken(fallbackText: String, inputComplete: Bool) -> SearchToken? { + // Use existing description and symbol + if let firstDescriptiveCondition = firstDescriptiveCondition, let localizedDescription = firstDescriptiveCondition.localizedDescription { + var icon : UIImage? + + if let symbolName = firstDescriptiveCondition.symbolName { + icon = UIImage(systemName: symbolName) + } + + return SearchToken(text: localizedDescription, icon: icon, representedObject: self, inputComplete: inputComplete) + } + + // Try to determine a useful icon and description + guard let effectiveCondition = firstNonLogicalCondition, let effectiveProperty = effectiveCondition.property else { + return nil + } + + let effectiveOperator = effectiveCondition.operator + var icon : UIImage? + + switch effectiveProperty { + case .name: + if effectiveOperator == .propertyHasSuffix { + icon = UIImage(systemName: "smallcircle.filled.circle") + } + + case .driveID: + icon = UIImage(systemName: "square.grid.2x2") + + case .mimeType: + icon = UIImage(systemName: "photo") + + case .size: + switch effectiveOperator { + case .propertyGreaterThanValue: + icon = UIImage(systemName: "greaterthan") + + case .propertyLessThanValue: + icon = UIImage(systemName: "lessthan") + + case .propertyEqualToValue: + icon = UIImage(systemName: "equal") + + default: break + } + + case .ownerUserName: break + + case .lastModified: + icon = UIImage(systemName: "calendar") + + default: break + } + + return SearchToken(text: localizedDescription ?? fallbackText, icon: icon, representedObject: self, inputComplete: inputComplete) + } +} diff --git a/ownCloudAppShared/Client/Search/Tokenizer/SearchElement.swift b/ownCloudAppShared/Client/Search/Tokenizer/SearchElement.swift new file mode 100644 index 000000000..1543c87de --- /dev/null +++ b/ownCloudAppShared/Client/Search/Tokenizer/SearchElement.swift @@ -0,0 +1,49 @@ +// +// SearchElement.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 12.08.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit + +open class SearchElement: NSObject { + var text: String + var inputComplete: Bool + + var representedObject: AnyObject? + + required public init(text: String, representedObject: AnyObject? = nil, inputComplete: Bool) { + self.text = text + self.inputComplete = inputComplete + + super.init() + + self.representedObject = representedObject + } +} + +open class SearchToken: SearchElement { + var icon: UIImage? + + required public init(text: String, icon: UIImage?, representedObject: AnyObject?, inputComplete: Bool) { + super.init(text: text, representedObject: representedObject, inputComplete: inputComplete) + + self.icon = icon + } + + required public init(text: String, representedObject: AnyObject? = nil, inputComplete: Bool) { + fatalError("init(text:representedObject:inputComplete:) has not been implemented") + } +} diff --git a/ownCloudAppShared/Client/Search/Tokenizer/SearchTokenizer.swift b/ownCloudAppShared/Client/Search/Tokenizer/SearchTokenizer.swift new file mode 100644 index 000000000..1cdb1024b --- /dev/null +++ b/ownCloudAppShared/Client/Search/Tokenizer/SearchTokenizer.swift @@ -0,0 +1,109 @@ +// +// SearchTokenizer.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 12.08.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudApp + +open class SearchTokenizer: NSObject { + weak var scope: SearchScope? + public var clientContext: ClientContext? + + var elements : [SearchElement] = [] + + weak var searchField: UISearchTextField? + + open func updateFor(searchField: UISearchTextField) { + let searchText = searchField.text + let cursorOffset = searchField.cursorPositionInTextualRange + + self.searchField = searchField + + var searchTokens : [SearchToken] = [] + + for token in searchField.tokens { + if let searchToken = token.representedObject as? SearchToken { + searchTokens.append(searchToken) + } + } + + updateForSearchTerm((searchText != "") ? searchText : nil, cursorOffset: cursorOffset, tokens: searchTokens) + } + + open func updateForSearchTerm(_ term: String?, cursorOffset: Int?, tokens: [SearchToken]) { + var assembledTokens : [SearchToken] = [] + var assembledElements : [SearchElement] = [] + + // Find terms and tokens in provided searchTerm + if let searchSegments = term?.segmentedForSearch(withQuotationMarks: false, cursorPosition: (cursorOffset as? NSNumber)) { + Log.log("SearchSegments: \(String.init(describing: searchSegments))") + + for searchSegment in searchSegments.reversed() { // Iterate segments in reverse so that replacing a segment doesn't change its position + if !searchSegment.hasCursor, let token = shouldTokenize(segment: searchSegment) { + assembledTokens.insert(token, at: 0) + replace(segment: searchSegment, with: token) + } else { + let queryCondition = OCQueryCondition.fromSearchTerm(searchSegment.segmentedString) + assembledElements.insert(SearchElement(text: searchSegment.segmentedString, representedObject: queryCondition, inputComplete: !searchSegment.hasCursor), at: 0) + } + } + } + + // Insert existing tokens at the front of the found tokens + assembledTokens.insert(contentsOf: tokens, at: 0) + + // Insert tokens in front of elements + assembledElements.insert(contentsOf: assembledTokens, at: 0) + + // Tell scope to update for the provided elements + scope?.updateFor(assembledElements) + } + + open func shouldTokenize(segment: OCSearchSegment) -> SearchToken? { + if let queryCondition = OCQueryCondition.fromSearchTerm(segment.segmentedString) { + if let property = queryCondition.property { + if (property != .name) || (queryCondition.operator != .propertyContains) { + return queryCondition.generateSearchToken(fallbackText: segment.segmentedString, inputComplete: !segment.hasCursor) + } + } else { + return queryCondition.generateSearchToken(fallbackText: segment.segmentedString, inputComplete: !segment.hasCursor) + } + } + + return nil + } + + open func replace(segment: OCSearchSegment, with searchToken: SearchToken) { + var replaceRange = segment.range + replaceRange.length += 1 // remove trailing space as well as the spaces accumulate otherwise + + if let replaceRange = searchField?.textRange(from: replaceRange) { + let token = UISearchToken(icon: searchToken.icon, text: searchToken.text) + token.representedObject = searchToken + + searchField?.replace(replaceRange, withText: "") + searchField?.insertToken(token, at: searchField?.tokens.count ?? 0) + } + } + + public init(scope: SearchScope, clientContext: ClientContext?) { + super.init() + + self.scope = scope + self.clientContext = clientContext + } +} diff --git a/ownCloudAppShared/UIKit Extension/UITextField+Extension.swift b/ownCloudAppShared/UIKit Extension/UITextField+Extension.swift new file mode 100644 index 000000000..5a9b194c6 --- /dev/null +++ b/ownCloudAppShared/UIKit Extension/UITextField+Extension.swift @@ -0,0 +1,47 @@ +// +// UITextField+Extension.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 09.08.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit + +public extension UITextField { + var cursorPosition : Int? { + if let selectedTextRange = selectedTextRange, selectedTextRange.isEmpty { + return offset(from: beginningOfDocument, to: selectedTextRange.start) + } + return nil + } +} + +public extension UISearchTextField { + var cursorPositionInTextualRange : Int? { + if let selectedTextRange = selectedTextRange, selectedTextRange.isEmpty { + return offset(from: textualRange.start, to: selectedTextRange.start) + } + return nil + } + + func textRange(from range: NSRange) -> UITextRange? { + let textualRange = textualRange + if let startPosition = position(from: textualRange.start, offset: range.location), + let endPosition = position(from: startPosition, in: .right, offset: range.length) { + return textRange(from: startPosition, to: endPosition) + } + + return nil + } +} From 2f43dbd985c84b5f2f2cf8b42a0fd009798b4d84 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Thu, 25 Aug 2022 16:50:56 +0200 Subject: [PATCH 053/328] - clean up and reorganize search base code into separate source code files - add explanatory comments to several source files --- ownCloud.xcodeproj/project.pbxproj | 42 +++- .../Scopes/CustomQuerySearchScope.swift | 133 +++++++++++ .../Item Search/Scopes/ItemSearchScope.swift | 76 +++++++ .../Scopes/QueryModifyingSearchScope.swift | 66 ++++++ .../CustomQuerySearchTokenizer.swift | 41 ++++ .../OCQueryCondition+SearchToken.swift | 0 .../Client/Search/Scopes/SearchScope.swift | 208 +----------------- .../Search/Tokenizer/SearchElement.swift | 8 +- .../Search/Tokenizer/SearchTokenizer.swift | 32 +-- 9 files changed, 380 insertions(+), 226 deletions(-) create mode 100644 ownCloudAppShared/Client/Search/Item Search/Scopes/CustomQuerySearchScope.swift create mode 100644 ownCloudAppShared/Client/Search/Item Search/Scopes/ItemSearchScope.swift create mode 100644 ownCloudAppShared/Client/Search/Item Search/Scopes/QueryModifyingSearchScope.swift create mode 100644 ownCloudAppShared/Client/Search/Item Search/Tokenizer/CustomQuerySearchTokenizer.swift rename ownCloudAppShared/Client/Search/{ => Item Search}/Tokenizer/OCQueryCondition+SearchToken.swift (100%) diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 23846840d..ea6fa527d 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -290,6 +290,10 @@ DC24E0EA28B36A81002E4F5B /* OCSearchSegment.h in Headers */ = {isa = PBXBuildFile; fileRef = DC24E0E828B36A81002E4F5B /* OCSearchSegment.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC24E0EB28B36A81002E4F5B /* OCSearchSegment.m in Sources */ = {isa = PBXBuildFile; fileRef = DC24E0E928B36A81002E4F5B /* OCSearchSegment.m */; }; DC24E0F828B41694002E4F5B /* OCQueryCondition+SearchToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC24E0F728B41693002E4F5B /* OCQueryCondition+SearchToken.swift */; }; + DC24E10428B7BF4E002E4F5B /* CustomQuerySearchTokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC24E10328B7BF4E002E4F5B /* CustomQuerySearchTokenizer.swift */; }; + DC24E10728B7BFD6002E4F5B /* ItemSearchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC24E10628B7BFD6002E4F5B /* ItemSearchScope.swift */; }; + DC24E10B28B7C185002E4F5B /* QueryModifyingSearchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC24E10A28B7C185002E4F5B /* QueryModifyingSearchScope.swift */; }; + DC24E10D28B7C19F002E4F5B /* CustomQuerySearchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC24E10C28B7C19F002E4F5B /* CustomQuerySearchScope.swift */; }; DC2565EE225F5A1900828AA5 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC2565E8225F5A1900828AA5 /* UserNotifications.framework */; }; DC26ADDE2550C0B20059680D /* MetadataDocumentationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC26ADDD2550C0B20059680D /* MetadataDocumentationTests.swift */; }; DC27A18E20CA9F66008ACB6C /* OCItem+FileProviderItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DC27A18D20CA9F66008ACB6C /* OCItem+FileProviderItem.m */; }; @@ -1273,6 +1277,10 @@ DC24E0E828B36A81002E4F5B /* OCSearchSegment.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCSearchSegment.h; sourceTree = ""; }; DC24E0E928B36A81002E4F5B /* OCSearchSegment.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCSearchSegment.m; sourceTree = ""; }; DC24E0F728B41693002E4F5B /* OCQueryCondition+SearchToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCQueryCondition+SearchToken.swift"; sourceTree = ""; }; + DC24E10328B7BF4E002E4F5B /* CustomQuerySearchTokenizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomQuerySearchTokenizer.swift; sourceTree = ""; }; + DC24E10628B7BFD6002E4F5B /* ItemSearchScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemSearchScope.swift; sourceTree = ""; }; + DC24E10A28B7C185002E4F5B /* QueryModifyingSearchScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryModifyingSearchScope.swift; sourceTree = ""; }; + DC24E10C28B7C19F002E4F5B /* CustomQuerySearchScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomQuerySearchScope.swift; sourceTree = ""; }; DC2565E8225F5A1900828AA5 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; DC26ADDD2550C0B20059680D /* MetadataDocumentationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataDocumentationTests.swift; sourceTree = ""; }; DC27A18C20CA9F66008ACB6C /* OCItem+FileProviderItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCItem+FileProviderItem.h"; sourceTree = ""; }; @@ -2380,6 +2388,34 @@ path = Branding; sourceTree = ""; }; + DC24E10228B7BF13002E4F5B /* Tokenizer */ = { + isa = PBXGroup; + children = ( + DC24E10328B7BF4E002E4F5B /* CustomQuerySearchTokenizer.swift */, + DC24E0F728B41693002E4F5B /* OCQueryCondition+SearchToken.swift */, + ); + path = Tokenizer; + sourceTree = ""; + }; + DC24E10828B7C04B002E4F5B /* Item Search */ = { + isa = PBXGroup; + children = ( + DC24E10228B7BF13002E4F5B /* Tokenizer */, + DC24E10928B7C05E002E4F5B /* Scopes */, + ); + path = "Item Search"; + sourceTree = ""; + }; + DC24E10928B7C05E002E4F5B /* Scopes */ = { + isa = PBXGroup; + children = ( + DC24E10628B7BFD6002E4F5B /* ItemSearchScope.swift */, + DC24E10A28B7C185002E4F5B /* QueryModifyingSearchScope.swift */, + DC24E10C28B7C19F002E4F5B /* CustomQuerySearchScope.swift */, + ); + path = Scopes; + sourceTree = ""; + }; DC255E432319AD13007279B1 /* Scanner */ = { isa = PBXGroup; children = ( @@ -2532,7 +2568,6 @@ children = ( DC65592D28A644E10003D130 /* SearchTokenizer.swift */, DC65593128A648680003D130 /* SearchElement.swift */, - DC24E0F728B41693002E4F5B /* OCQueryCondition+SearchToken.swift */, ); path = Tokenizer; sourceTree = ""; @@ -2797,6 +2832,7 @@ DCB5D56A2861BEBE004AF425 /* SearchViewController.swift */, DCB5D5A828632C1B004AF425 /* Scopes */, DC65592C28A644B60003D130 /* Tokenizer */, + DC24E10828B7C04B002E4F5B /* Item Search */, ); path = Search; sourceTree = ""; @@ -4418,6 +4454,7 @@ DC3AB2422810404000789435 /* DriveHeaderCell.swift in Sources */, DCE4E44524C1A4260051722F /* FileListTableViewController.swift in Sources */, DC65590F28A2633C0003D130 /* UITextField+Extension.swift in Sources */, + DC24E10728B7BFD6002E4F5B /* ItemSearchScope.swift in Sources */, DC46F3D72845FCFA00038880 /* UIView+OCDataItem.swift in Sources */, 399EA75A25E66DB000B6FF11 /* ClientItemResolvingCell.swift in Sources */, DCE4E43524C1999A0051722F /* Action.swift in Sources */, @@ -4507,6 +4544,7 @@ 399EA6F725E6544100B6FF11 /* PublicLinkEditTableViewController.swift in Sources */, DC46F3C72844A75200038880 /* OCDataItem+InteractionProtocols.swift in Sources */, DC0A358824C0E44B00FB58FC /* ThemeTableViewCell.swift in Sources */, + DC24E10428B7BF4E002E4F5B /* CustomQuerySearchTokenizer.swift in Sources */, DC36886224DDA9AB00333600 /* ProgressIndicatorViewController.swift in Sources */, 399EA72625E6565900B6FF11 /* OCCore+Extension.swift in Sources */, DC0A359424C0E5C800FB58FC /* GitCommit.swift in Sources */, @@ -4527,11 +4565,13 @@ DC0A358324C0E44200FB58FC /* VectorImage.swift in Sources */, DC0A359824C0E68700FB58FC /* OCItem+AppExtension.swift in Sources */, DC0A358424C0E44200FB58FC /* VectorImageView.swift in Sources */, + DC24E10D28B7C19F002E4F5B /* CustomQuerySearchScope.swift in Sources */, DC65593228A648680003D130 /* SearchElement.swift in Sources */, DC0A355324C0E2C200FB58FC /* ClientItemCell.swift in Sources */, DC0A357D24C0E43C00FB58FC /* ThemeStyle.swift in Sources */, DC46F3CC2844A8EA00038880 /* ViewCell.swift in Sources */, DC0A357524C0E43200FB58FC /* ProgressView.swift in Sources */, + DC24E10B28B7C185002E4F5B /* QueryModifyingSearchScope.swift in Sources */, 3912208223436EB80026C290 /* SortMethod.swift in Sources */, DCE4E43F24C19D370051722F /* UIAlertController+OCIssue.swift in Sources */, DC0A359524C0E5F900FB58FC /* UIImage+Extension.swift in Sources */, diff --git a/ownCloudAppShared/Client/Search/Item Search/Scopes/CustomQuerySearchScope.swift b/ownCloudAppShared/Client/Search/Item Search/Scopes/CustomQuerySearchScope.swift new file mode 100644 index 000000000..70e7bcc42 --- /dev/null +++ b/ownCloudAppShared/Client/Search/Item Search/Scopes/CustomQuerySearchScope.swift @@ -0,0 +1,133 @@ +// +// CustomQuerySearchScope.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 25.08.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK +import ownCloudApp + +// Search scope that creates and manages its own OCQuery using OCQueryConditions +// Used for server-wide search + +open class CustomQuerySearchScope : ItemSearchScope { + private let maxResultCountDefault = 100 // Maximum number of results to return from database (default) + private var maxResultCount = 100 // Maximum number of results to return from database (flexible) + + public override var isSelected: Bool { + didSet { + if isSelected { + resultActionSource.setItems([ + OCAction(title: "Show more results".localized, icon: nil, action: { [weak self] action, options, completion in + self?.showMoreResults() + completion(nil) + }) + ], updated: nil) + composeResultsDataSource() + } + } + } + + public var resultActionSource: OCDataSourceArray = OCDataSourceArray() + + var resultsSubscription: OCDataSourceSubscription? + + func composeResultsDataSource() { + if let queryResultsSource = customQuery?.queryResultsDataSource { + let composedResults = OCDataSourceComposition(sources: [ + queryResultsSource, + resultActionSource + ]) + + let maxResultCount = maxResultCount + let resultActionSource = resultActionSource + + resultsSubscription = queryResultsSource.subscribe(updateHandler: { [weak composedResults, weak resultActionSource] (subscription) in + let snapshot = subscription.snapshotResettingChangeTracking(true) + + if let resultActionSource = resultActionSource { + OnMainThread { + composedResults?.setInclude((snapshot.numberOfItems >= maxResultCount), for: resultActionSource) + } + } + }, on: .main, trackDifferences: false, performIntialUpdate: true) + + results = composedResults + } else { + results = nil + } + } + + public var customQuery: OCQuery? { + willSet { + if let core = clientContext.core, let oldQuery = customQuery { + core.stop(oldQuery) + } + } + + didSet { + if let core = clientContext.core, let newQuery = customQuery { + core.start(newQuery) + + composeResultsDataSource() + } else { + results = nil + } + } + } + + private var lastSearchTerm : String? + private var scrollToTopWithNextRefresh : Bool = false + + public func updateCustomSearchQuery() { + if lastSearchTerm != searchTerm { + // Reset max result count when search text changes + maxResultCount = maxResultCountDefault + lastSearchTerm = searchTerm + + // Scroll to top when search text changes + scrollToTopWithNextRefresh = true + } + + if let condition = queryCondition { + if let sortDescriptor = clientContext.sortDescriptor { + condition.sortBy = sortDescriptor.method.sortPropertyName + condition.sortAscending = sortDescriptor.direction != .ascendant + } + + condition.maxResultCount = NSNumber(value: maxResultCount) + + customQuery = OCQuery(condition:condition, inputFilter: nil) + } else { + customQuery = nil + } + } + + func showMoreResults() { + maxResultCount += maxResultCountDefault + updateCustomSearchQuery() + } + + open override var queryCondition: OCQueryCondition? { + didSet { + updateCustomSearchQuery() + } + } + + open override func sortDescriptorChanged(to sortDescriptor: SortDescriptor?) { + updateCustomSearchQuery() + } +} diff --git a/ownCloudAppShared/Client/Search/Item Search/Scopes/ItemSearchScope.swift b/ownCloudAppShared/Client/Search/Item Search/Scopes/ItemSearchScope.swift new file mode 100644 index 000000000..aae9ea144 --- /dev/null +++ b/ownCloudAppShared/Client/Search/Item Search/Scopes/ItemSearchScope.swift @@ -0,0 +1,76 @@ +// +// ItemSearchScope.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 25.08.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK +import ownCloudApp + +// Common base class for query-modifying and custom query search scopes, implementing commonly used tools + +open class ItemSearchScope : SearchScope { + private var sortDescriptorObserver: NSKeyValueObservation? + + public override init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String, icon: UIImage? = nil) { + super.init(with: context, cellStyle: cellStyle, localizedName: name, icon: icon) + + tokenizer = CustomQuerySearchTokenizer(scope: self, clientContext: context) + + sortDescriptorObserver = context.observe(\.sortDescriptor, changeHandler: { [weak self] context, change in + self?.sortDescriptorChanged(to: context.sortDescriptor) + }) + } + + deinit { + sortDescriptorObserver?.invalidate() + } + + open func sortDescriptorChanged(to sortDescriptor: SortDescriptor?) { + } + + open var queryCondition: OCQueryCondition? + + open override var isSelected: Bool { + didSet { + if !isSelected { + queryCondition = nil + results = nil + } + } + } + + open var searchTerm: String? + + open override func updateFor(_ searchElements: [SearchElement]) { + if isSelected { + var queryConditions : [OCQueryCondition] = [] + + for searchElement in searchElements { + if let queryCondition = searchElement.representedObject as? OCQueryCondition { + queryConditions.append(queryCondition) + } + } + + if queryConditions.count > 0 { + queryCondition = OCQueryCondition.require(queryConditions) + // Log.debug("Assembled search: \(queryCondition!.composedSearchTerm)") + } else { + queryCondition = nil + } + } + } +} diff --git a/ownCloudAppShared/Client/Search/Item Search/Scopes/QueryModifyingSearchScope.swift b/ownCloudAppShared/Client/Search/Item Search/Scopes/QueryModifyingSearchScope.swift new file mode 100644 index 000000000..3d08613ca --- /dev/null +++ b/ownCloudAppShared/Client/Search/Item Search/Scopes/QueryModifyingSearchScope.swift @@ -0,0 +1,66 @@ +// +// QueryModifyingSearchScope.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 25.08.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK +import ownCloudApp + +// Search scope that modifies an existing OCQuery so that it only returns matching results +// Used for folder-only search + +open class QueryModifyingSearchScope : ItemSearchScope { + public override var isSelected: Bool { + didSet { + if let query = clientContext.query { + if isSelected { + // Modify existing query provided via clientContext + results = query.queryResultsDataSource + } + } + } + } + + open override var queryCondition: OCQueryCondition? { + didSet { + let queryCondition = queryCondition + + if let query = clientContext.query { + if queryCondition != nil { + let filterHandler: OCQueryFilterHandler = { (_, _, item) -> Bool in + if let item = item, let queryCondition = queryCondition { + return queryCondition.fulfilled(by: item) + } + return false + } + + if let filter = query.filter(withIdentifier: "text-search") { + query.updateFilter(filter, applyChanges: { filterToChange in + (filterToChange as? OCQueryFilter)?.filterHandler = filterHandler + }) + } else { + query.addFilter(OCQueryFilter(handler: filterHandler), withIdentifier: "text-search") + } + } else { + if let filter = query.filter(withIdentifier: "text-search") { + query.removeFilter(filter) + } + } + } + } + } +} diff --git a/ownCloudAppShared/Client/Search/Item Search/Tokenizer/CustomQuerySearchTokenizer.swift b/ownCloudAppShared/Client/Search/Item Search/Tokenizer/CustomQuerySearchTokenizer.swift new file mode 100644 index 000000000..7a0536a52 --- /dev/null +++ b/ownCloudAppShared/Client/Search/Item Search/Tokenizer/CustomQuerySearchTokenizer.swift @@ -0,0 +1,41 @@ +// +// CustomQuerySearchTokenizer.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 25.08.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudApp + +open class CustomQuerySearchTokenizer : SearchTokenizer { + open override func shouldTokenize(segment: OCSearchSegment) -> SearchToken? { + // + if let queryCondition = OCQueryCondition.fromSearchTerm(segment.segmentedString) { + if let property = queryCondition.property { + if (property != .name) || (queryCondition.operator != .propertyContains) { + return queryCondition.generateSearchToken(fallbackText: segment.segmentedString, inputComplete: !segment.hasCursor) + } + } else { + return queryCondition.generateSearchToken(fallbackText: segment.segmentedString, inputComplete: !segment.hasCursor) + } + } + + return nil + } + + open override func composeTextElement(segment: OCSearchSegment) -> SearchElement { + return SearchElement(text: segment.segmentedString, representedObject: OCQueryCondition.fromSearchTerm(segment.segmentedString), inputComplete: !segment.hasCursor) + } +} diff --git a/ownCloudAppShared/Client/Search/Tokenizer/OCQueryCondition+SearchToken.swift b/ownCloudAppShared/Client/Search/Item Search/Tokenizer/OCQueryCondition+SearchToken.swift similarity index 100% rename from ownCloudAppShared/Client/Search/Tokenizer/OCQueryCondition+SearchToken.swift rename to ownCloudAppShared/Client/Search/Item Search/Tokenizer/OCQueryCondition+SearchToken.swift diff --git a/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift b/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift index 7ca1c14c0..c37e86ab8 100644 --- a/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift +++ b/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift @@ -21,6 +21,7 @@ import ownCloudSDK open class SearchScope: NSObject { public var localizedName : String + public var icon : UIImage? @objc public dynamic var results: OCDataSource? @objc public dynamic var resultsCellStyle: CollectionViewCellStyle? @@ -43,219 +44,16 @@ open class SearchScope: NSObject { return CustomQuerySearchScope(with: context, cellStyle: revealCellStyle, localizedName: localizedName) } - public init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String) { + public init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String, icon: UIImage? = nil) { clientContext = context localizedName = name super.init() - tokenizer = SearchTokenizer(scope: self, clientContext: context) - resultsCellStyle = cellStyle + self.icon = icon } open func updateFor(_ searchElements: [SearchElement]) { } } - -open class ItemSearchScope : SearchScope { - private var sortDescriptorObserver: NSKeyValueObservation? - - public override init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String) { - super.init(with: context, cellStyle: cellStyle, localizedName: name) - - sortDescriptorObserver = context.observe(\.sortDescriptor, changeHandler: { [weak self] context, change in - self?.sortDescriptorChanged(to: context.sortDescriptor) - }) - } - - deinit { - sortDescriptorObserver?.invalidate() - } - - open func sortDescriptorChanged(to sortDescriptor: SortDescriptor?) { - } - - open var queryCondition: OCQueryCondition? - - open override var isSelected: Bool { - didSet { - if !isSelected { - queryCondition = nil - results = nil - } - } - } - - open var searchTerm: String? - - open override func updateFor(_ searchElements: [SearchElement]) { - if isSelected { - var queryConditions : [OCQueryCondition] = [] - - for searchElement in searchElements { - if let queryCondition = searchElement.representedObject as? OCQueryCondition { - queryConditions.append(queryCondition) - } - } - - if queryConditions.count > 0 { - queryCondition = OCQueryCondition.require(queryConditions) - Log.debug("Assembled search: \(queryCondition!.composedSearchTerm)") - } else { - queryCondition = nil - } - } - } -} - -open class QueryModifyingSearchScope : ItemSearchScope { - public override var isSelected: Bool { - didSet { - if let query = clientContext.query { - if isSelected { - // Modify existing query provided via clientContext - results = query.queryResultsDataSource - } - } - } - } - - open override var queryCondition: OCQueryCondition? { - didSet { - let queryCondition = queryCondition - - if let query = clientContext.query { - if queryCondition != nil { - let filterHandler: OCQueryFilterHandler = { (_, _, item) -> Bool in - if let item = item, let queryCondition = queryCondition { - return queryCondition.fulfilled(by: item) - } - return false - } - - if let filter = query.filter(withIdentifier: "text-search") { - query.updateFilter(filter, applyChanges: { filterToChange in - (filterToChange as? OCQueryFilter)?.filterHandler = filterHandler - }) - } else { - query.addFilter(OCQueryFilter(handler: filterHandler), withIdentifier: "text-search") - } - } else { - if let filter = query.filter(withIdentifier: "text-search") { - query.removeFilter(filter) - } - } - } - } - } -} - -open class CustomQuerySearchScope : ItemSearchScope { - private let maxResultCountDefault = 100 // Maximum number of results to return from database (default) - private var maxResultCount = 100 // Maximum number of results to return from database (flexible) - - public override var isSelected: Bool { - didSet { - if isSelected { - resultActionSource.setItems([ - OCAction(title: "Show more results".localized, icon: nil, action: { [weak self] action, options, completion in - self?.showMoreResults() - completion(nil) - }) - ], updated: nil) - composeResultsDataSource() - } - } - } - - public var resultActionSource: OCDataSourceArray = OCDataSourceArray() - - var resultsSubscription: OCDataSourceSubscription? - - func composeResultsDataSource() { - if let queryResultsSource = customQuery?.queryResultsDataSource { - let composedResults = OCDataSourceComposition(sources: [ - queryResultsSource, - resultActionSource - ]) - - let maxResultCount = maxResultCount - let resultActionSource = resultActionSource - - resultsSubscription = queryResultsSource.subscribe(updateHandler: { [weak composedResults, weak resultActionSource] (subscription) in - let snapshot = subscription.snapshotResettingChangeTracking(true) - - if let resultActionSource = resultActionSource { - OnMainThread { - composedResults?.setInclude((snapshot.numberOfItems >= maxResultCount), for: resultActionSource) - } - } - }, on: .main, trackDifferences: false, performIntialUpdate: true) - - results = composedResults - } else { - results = nil - } - } - - public var customQuery: OCQuery? { - willSet { - if let core = clientContext.core, let oldQuery = customQuery { - core.stop(oldQuery) - } - } - - didSet { - if let core = clientContext.core, let newQuery = customQuery { - core.start(newQuery) - - composeResultsDataSource() - } else { - results = nil - } - } - } - - private var lastSearchTerm : String? - private var scrollToTopWithNextRefresh : Bool = false - - public func updateCustomSearchQuery() { - if lastSearchTerm != searchTerm { - // Reset max result count when search text changes - maxResultCount = maxResultCountDefault - lastSearchTerm = searchTerm - - // Scroll to top when search text changes - scrollToTopWithNextRefresh = true - } - - if let condition = queryCondition { - if let sortDescriptor = clientContext.sortDescriptor { - condition.sortBy = sortDescriptor.method.sortPropertyName - condition.sortAscending = sortDescriptor.direction != .ascendant - } - - condition.maxResultCount = NSNumber(value: maxResultCount) - - customQuery = OCQuery(condition:condition, inputFilter: nil) - } else { - customQuery = nil - } - } - - func showMoreResults() { - maxResultCount += maxResultCountDefault - updateCustomSearchQuery() - } - - open override var queryCondition: OCQueryCondition? { - didSet { - updateCustomSearchQuery() - } - } - - open override func sortDescriptorChanged(to sortDescriptor: SortDescriptor?) { - updateCustomSearchQuery() - } -} diff --git a/ownCloudAppShared/Client/Search/Tokenizer/SearchElement.swift b/ownCloudAppShared/Client/Search/Tokenizer/SearchElement.swift index 1543c87de..bf9ac15a6 100644 --- a/ownCloudAppShared/Client/Search/Tokenizer/SearchElement.swift +++ b/ownCloudAppShared/Client/Search/Tokenizer/SearchElement.swift @@ -19,10 +19,10 @@ import UIKit open class SearchElement: NSObject { - var text: String - var inputComplete: Bool + open var text: String + open var inputComplete: Bool - var representedObject: AnyObject? + open var representedObject: AnyObject? required public init(text: String, representedObject: AnyObject? = nil, inputComplete: Bool) { self.text = text @@ -35,7 +35,7 @@ open class SearchElement: NSObject { } open class SearchToken: SearchElement { - var icon: UIImage? + open var icon: UIImage? required public init(text: String, icon: UIImage?, representedObject: AnyObject?, inputComplete: Bool) { super.init(text: text, representedObject: representedObject, inputComplete: inputComplete) diff --git a/ownCloudAppShared/Client/Search/Tokenizer/SearchTokenizer.swift b/ownCloudAppShared/Client/Search/Tokenizer/SearchTokenizer.swift index 1cdb1024b..34582db67 100644 --- a/ownCloudAppShared/Client/Search/Tokenizer/SearchTokenizer.swift +++ b/ownCloudAppShared/Client/Search/Tokenizer/SearchTokenizer.swift @@ -19,6 +19,7 @@ import UIKit import ownCloudApp +// Base search tokenizer class open class SearchTokenizer: NSObject { weak var scope: SearchScope? public var clientContext: ClientContext? @@ -54,11 +55,12 @@ open class SearchTokenizer: NSObject { for searchSegment in searchSegments.reversed() { // Iterate segments in reverse so that replacing a segment doesn't change its position if !searchSegment.hasCursor, let token = shouldTokenize(segment: searchSegment) { + // Create token from search segment and insert it position 0 (remember: we're iterating segments in reverse!) assembledTokens.insert(token, at: 0) replace(segment: searchSegment, with: token) } else { - let queryCondition = OCQueryCondition.fromSearchTerm(searchSegment.segmentedString) - assembledElements.insert(SearchElement(text: searchSegment.segmentedString, representedObject: queryCondition, inputComplete: !searchSegment.hasCursor), at: 0) + // Create search term text element and insert it position 0 (remember: we're iterating segments in reverse!) + assembledElements.insert(composeTextElement(segment: searchSegment), at: 0) } } } @@ -73,20 +75,7 @@ open class SearchTokenizer: NSObject { scope?.updateFor(assembledElements) } - open func shouldTokenize(segment: OCSearchSegment) -> SearchToken? { - if let queryCondition = OCQueryCondition.fromSearchTerm(segment.segmentedString) { - if let property = queryCondition.property { - if (property != .name) || (queryCondition.operator != .propertyContains) { - return queryCondition.generateSearchToken(fallbackText: segment.segmentedString, inputComplete: !segment.hasCursor) - } - } else { - return queryCondition.generateSearchToken(fallbackText: segment.segmentedString, inputComplete: !segment.hasCursor) - } - } - - return nil - } - + // MARK: UISearchToken generation open func replace(segment: OCSearchSegment, with searchToken: SearchToken) { var replaceRange = segment.range replaceRange.length += 1 // remove trailing space as well as the spaces accumulate otherwise @@ -106,4 +95,15 @@ open class SearchTokenizer: NSObject { self.scope = scope self.clientContext = clientContext } + + // MARK: - Conversion to token & text element + open func shouldTokenize(segment: OCSearchSegment) -> SearchToken? { + // No tokenization + return nil + } + + open func composeTextElement(segment: OCSearchSegment) -> SearchElement { + // Conversion of text elements in SearchElements + return SearchElement(text: segment.segmentedString, representedObject: nil, inputComplete: !segment.hasCursor) + } } From 2a49ecb72353ee6f2b59442f80fef373ac919810 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Thu, 25 Aug 2022 17:48:51 +0200 Subject: [PATCH 054/328] - CustomQuerySearchScope: - add queryConditionModifier to allow modification of query conditions en route to OCQuery construction - add AccountSearchScope and DriveSearchScope subclasses, the latter implementing filtering on a single drive - QueryModifyingSearchScope - add SingleFolderSearchScope subclass to keep descriptive QueryModifyingSearchScope while also using explanatory SingleFolderSearchScope --- ownCloud.xcodeproj/project.pbxproj | 16 +++---- .../ClientItemViewController.swift | 19 +++++--- ...chScope.swift => AccountSearchScope.swift} | 46 ++++++++++++++++++- ...pe.swift => SingleFolderSearchScope.swift} | 6 ++- .../Client/Search/Scopes/SearchScope.swift | 12 ++--- 5 files changed, 76 insertions(+), 23 deletions(-) rename ownCloudAppShared/Client/Search/Item Search/Scopes/{CustomQuerySearchScope.swift => AccountSearchScope.swift} (67%) rename ownCloudAppShared/Client/Search/Item Search/Scopes/{QueryModifyingSearchScope.swift => SingleFolderSearchScope.swift} (94%) diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index ea6fa527d..08bf8c64a 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -292,8 +292,8 @@ DC24E0F828B41694002E4F5B /* OCQueryCondition+SearchToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC24E0F728B41693002E4F5B /* OCQueryCondition+SearchToken.swift */; }; DC24E10428B7BF4E002E4F5B /* CustomQuerySearchTokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC24E10328B7BF4E002E4F5B /* CustomQuerySearchTokenizer.swift */; }; DC24E10728B7BFD6002E4F5B /* ItemSearchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC24E10628B7BFD6002E4F5B /* ItemSearchScope.swift */; }; - DC24E10B28B7C185002E4F5B /* QueryModifyingSearchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC24E10A28B7C185002E4F5B /* QueryModifyingSearchScope.swift */; }; - DC24E10D28B7C19F002E4F5B /* CustomQuerySearchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC24E10C28B7C19F002E4F5B /* CustomQuerySearchScope.swift */; }; + DC24E10B28B7C185002E4F5B /* SingleFolderSearchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC24E10A28B7C185002E4F5B /* SingleFolderSearchScope.swift */; }; + DC24E10D28B7C19F002E4F5B /* AccountSearchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC24E10C28B7C19F002E4F5B /* AccountSearchScope.swift */; }; DC2565EE225F5A1900828AA5 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC2565E8225F5A1900828AA5 /* UserNotifications.framework */; }; DC26ADDE2550C0B20059680D /* MetadataDocumentationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC26ADDD2550C0B20059680D /* MetadataDocumentationTests.swift */; }; DC27A18E20CA9F66008ACB6C /* OCItem+FileProviderItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DC27A18D20CA9F66008ACB6C /* OCItem+FileProviderItem.m */; }; @@ -1279,8 +1279,8 @@ DC24E0F728B41693002E4F5B /* OCQueryCondition+SearchToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCQueryCondition+SearchToken.swift"; sourceTree = ""; }; DC24E10328B7BF4E002E4F5B /* CustomQuerySearchTokenizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomQuerySearchTokenizer.swift; sourceTree = ""; }; DC24E10628B7BFD6002E4F5B /* ItemSearchScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemSearchScope.swift; sourceTree = ""; }; - DC24E10A28B7C185002E4F5B /* QueryModifyingSearchScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryModifyingSearchScope.swift; sourceTree = ""; }; - DC24E10C28B7C19F002E4F5B /* CustomQuerySearchScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomQuerySearchScope.swift; sourceTree = ""; }; + DC24E10A28B7C185002E4F5B /* SingleFolderSearchScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleFolderSearchScope.swift; sourceTree = ""; }; + DC24E10C28B7C19F002E4F5B /* AccountSearchScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSearchScope.swift; sourceTree = ""; }; DC2565E8225F5A1900828AA5 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; DC26ADDD2550C0B20059680D /* MetadataDocumentationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataDocumentationTests.swift; sourceTree = ""; }; DC27A18C20CA9F66008ACB6C /* OCItem+FileProviderItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCItem+FileProviderItem.h"; sourceTree = ""; }; @@ -2410,8 +2410,8 @@ isa = PBXGroup; children = ( DC24E10628B7BFD6002E4F5B /* ItemSearchScope.swift */, - DC24E10A28B7C185002E4F5B /* QueryModifyingSearchScope.swift */, - DC24E10C28B7C19F002E4F5B /* CustomQuerySearchScope.swift */, + DC24E10A28B7C185002E4F5B /* SingleFolderSearchScope.swift */, + DC24E10C28B7C19F002E4F5B /* AccountSearchScope.swift */, ); path = Scopes; sourceTree = ""; @@ -4565,13 +4565,13 @@ DC0A358324C0E44200FB58FC /* VectorImage.swift in Sources */, DC0A359824C0E68700FB58FC /* OCItem+AppExtension.swift in Sources */, DC0A358424C0E44200FB58FC /* VectorImageView.swift in Sources */, - DC24E10D28B7C19F002E4F5B /* CustomQuerySearchScope.swift in Sources */, + DC24E10D28B7C19F002E4F5B /* AccountSearchScope.swift in Sources */, DC65593228A648680003D130 /* SearchElement.swift in Sources */, DC0A355324C0E2C200FB58FC /* ClientItemCell.swift in Sources */, DC0A357D24C0E43C00FB58FC /* ThemeStyle.swift in Sources */, DC46F3CC2844A8EA00038880 /* ViewCell.swift in Sources */, DC0A357524C0E43200FB58FC /* ProgressView.swift in Sources */, - DC24E10B28B7C185002E4F5B /* QueryModifyingSearchScope.swift in Sources */, + DC24E10B28B7C185002E4F5B /* SingleFolderSearchScope.swift in Sources */, 3912208223436EB80026C290 /* SortMethod.swift in Sources */, DCE4E43F24C19D370051722F /* UIAlertController+OCIssue.swift in Sources */, DC0A359524C0E5F900FB58FC /* UIImage+Extension.swift in Sources */, diff --git a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift index f23c8bd88..850fcf24e 100644 --- a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift @@ -652,16 +652,23 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, @objc open func startSearch() { if searchViewController == nil { if let clientContext = clientContext, let cellStyle = itemSection?.cellStyle { - searchViewController = SearchViewController(with: clientContext, scopes: [ + var scopes : [SearchScope] = [ // In this folder - .modifyingQuery(with: clientContext, localizedName: "Folder".localized), + .modifyingQuery(with: clientContext, localizedName: "Folder".localized) // + Folder and subfolders - // + This space + ] - // Account - .globalSearch(with: clientContext, cellStyle: cellStyle, localizedName: "Account".localized) - ], delegate: self) + // Drive + if clientContext.core?.useDrives == true { + let driveName = clientContext.drive?.name ?? "Drive".localized + scopes.append(.driveSearch(with: clientContext, cellStyle: cellStyle, localizedName: driveName)) + } + + // Account + scopes.append(.accountSearch(with: clientContext, cellStyle: cellStyle, localizedName: "Account".localized)) + + searchViewController = SearchViewController(with: clientContext, scopes: scopes, delegate: self) if let searchViewController = searchViewController { self.addStacked(child: searchViewController, position: .top) diff --git a/ownCloudAppShared/Client/Search/Item Search/Scopes/CustomQuerySearchScope.swift b/ownCloudAppShared/Client/Search/Item Search/Scopes/AccountSearchScope.swift similarity index 67% rename from ownCloudAppShared/Client/Search/Item Search/Scopes/CustomQuerySearchScope.swift rename to ownCloudAppShared/Client/Search/Item Search/Scopes/AccountSearchScope.swift index 70e7bcc42..4f791e0e1 100644 --- a/ownCloudAppShared/Client/Search/Item Search/Scopes/CustomQuerySearchScope.swift +++ b/ownCloudAppShared/Client/Search/Item Search/Scopes/AccountSearchScope.swift @@ -1,5 +1,5 @@ // -// CustomQuerySearchScope.swift +// AccountSearchScope.swift // ownCloudAppShared // // Created by Felix Schwarz on 25.08.22. @@ -89,6 +89,8 @@ open class CustomQuerySearchScope : ItemSearchScope { } } + public var queryConditionModifier : ((OCQueryCondition?) -> OCQueryCondition?)? // MARK: modifier that can modify the query condition before it is passed to create the OCQuery backing the scope. The modification is invisible to the outside. Can be used to add constraints like limit to a drive, etc. + private var lastSearchTerm : String? private var scrollToTopWithNextRefresh : Bool = false @@ -102,7 +104,14 @@ open class CustomQuerySearchScope : ItemSearchScope { scrollToTopWithNextRefresh = true } - if let condition = queryCondition { + var condition = queryCondition + + if let queryConditionModifier = queryConditionModifier, let baseCondition = condition { + // Apply query condition modifier + condition = queryConditionModifier(baseCondition) + } + + if let condition = condition { if let sortDescriptor = clientContext.sortDescriptor { condition.sortBy = sortDescriptor.method.sortPropertyName condition.sortAscending = sortDescriptor.direction != .ascendant @@ -131,3 +140,36 @@ open class CustomQuerySearchScope : ItemSearchScope { updateCustomSearchQuery() } } + +// Subclasses +open class AccountSearchScope : CustomQuerySearchScope { + public override init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String, icon: UIImage? = nil) { + var revealCellStyle : CollectionViewCellStyle? + + if let cellStyle = cellStyle { + revealCellStyle = CollectionViewCellStyle(from: cellStyle, changing: { cellStyle in + cellStyle.showRevealButton = true + }) + } + + super.init(with: context, cellStyle: revealCellStyle, localizedName: name, icon: icon) + } +} + +open class DriveSearchScope : AccountSearchScope { + public override init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String, icon: UIImage? = nil) { + super.init(with: context, cellStyle: cellStyle, localizedName: name, icon: icon) + + if context.core?.useDrives == true, let driveID = context.drive?.identifier { + let requireDriveCondition = OCQueryCondition.where(.driveID, isEqualTo: driveID) + + queryConditionModifier = { (baseCondition) in + if let baseCondition = baseCondition { + return .require([ requireDriveCondition, baseCondition ]) + } + + return nil + } + } + } +} diff --git a/ownCloudAppShared/Client/Search/Item Search/Scopes/QueryModifyingSearchScope.swift b/ownCloudAppShared/Client/Search/Item Search/Scopes/SingleFolderSearchScope.swift similarity index 94% rename from ownCloudAppShared/Client/Search/Item Search/Scopes/QueryModifyingSearchScope.swift rename to ownCloudAppShared/Client/Search/Item Search/Scopes/SingleFolderSearchScope.swift index 3d08613ca..6bca049f5 100644 --- a/ownCloudAppShared/Client/Search/Item Search/Scopes/QueryModifyingSearchScope.swift +++ b/ownCloudAppShared/Client/Search/Item Search/Scopes/SingleFolderSearchScope.swift @@ -1,5 +1,5 @@ // -// QueryModifyingSearchScope.swift +// SingleFolderSearchScope.swift // ownCloudAppShared // // Created by Felix Schwarz on 25.08.22. @@ -64,3 +64,7 @@ open class QueryModifyingSearchScope : ItemSearchScope { } } } + +// Subclass +open class SingleFolderSearchScope : QueryModifyingSearchScope { +} diff --git a/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift b/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift index c37e86ab8..b0858c75b 100644 --- a/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift +++ b/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift @@ -33,15 +33,15 @@ open class SearchScope: NSObject { public var tokenizer: SearchTokenizer? static public func modifyingQuery(with context: ClientContext, localizedName: String) -> SearchScope { - return QueryModifyingSearchScope(with: context, cellStyle: nil, localizedName: localizedName) + return SingleFolderSearchScope(with: context, cellStyle: nil, localizedName: localizedName, icon: UIImage(systemName: "folder")) } - static public func globalSearch(with context: ClientContext, cellStyle: CollectionViewCellStyle, localizedName: String) -> SearchScope { - let revealCellStyle = CollectionViewCellStyle(from: cellStyle, changing: { cellStyle in - cellStyle.showRevealButton = true - }) + static public func driveSearch(with context: ClientContext, cellStyle: CollectionViewCellStyle, localizedName: String) -> SearchScope { + return DriveSearchScope(with: context, cellStyle: cellStyle, localizedName: localizedName, icon: UIImage(systemName: "person")) + } - return CustomQuerySearchScope(with: context, cellStyle: revealCellStyle, localizedName: localizedName) + static public func accountSearch(with context: ClientContext, cellStyle: CollectionViewCellStyle, localizedName: String) -> SearchScope { + return AccountSearchScope(with: context, cellStyle: cellStyle, localizedName: localizedName, icon: UIImage(systemName: "square.grid.2x2j")) } public init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String, icon: UIImage? = nil) { From 7566c8f6f7bb80ca20391c9db86acb56596ce3dc Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Fri, 26 Aug 2022 21:21:42 +0200 Subject: [PATCH 055/328] - PopupButtonController: class making it easy to create and manage a UIButton as a popup - integrate scope as popup into the search field - refine scopes, add placeholder text - bump build number to 228 --- ownCloud.xcodeproj/project.pbxproj | 8 +- .../Resources/en.lproj/Localizable.strings | 2 + .../ClientItemViewController.swift | 2 +- .../Scopes/AccountSearchScope.swift | 8 +- .../Item Search/Scopes/ItemSearchScope.swift | 4 +- .../Client/Search/Scopes/SearchScope.swift | 14 +- .../Client/Search/SearchViewController.swift | 101 ++++++++--- .../PopupButtonController.swift | 158 ++++++++++++++++++ 8 files changed, 258 insertions(+), 39 deletions(-) create mode 100644 ownCloudAppShared/Client/User Interface/PopupButtonController.swift diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 08bf8c64a..f66094cf5 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -294,6 +294,7 @@ DC24E10728B7BFD6002E4F5B /* ItemSearchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC24E10628B7BFD6002E4F5B /* ItemSearchScope.swift */; }; DC24E10B28B7C185002E4F5B /* SingleFolderSearchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC24E10A28B7C185002E4F5B /* SingleFolderSearchScope.swift */; }; DC24E10D28B7C19F002E4F5B /* AccountSearchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC24E10C28B7C19F002E4F5B /* AccountSearchScope.swift */; }; + DC24E10F28B7D2B9002E4F5B /* PopupButtonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC24E10E28B7D2B9002E4F5B /* PopupButtonController.swift */; }; DC2565EE225F5A1900828AA5 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC2565E8225F5A1900828AA5 /* UserNotifications.framework */; }; DC26ADDE2550C0B20059680D /* MetadataDocumentationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC26ADDD2550C0B20059680D /* MetadataDocumentationTests.swift */; }; DC27A18E20CA9F66008ACB6C /* OCItem+FileProviderItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DC27A18D20CA9F66008ACB6C /* OCItem+FileProviderItem.m */; }; @@ -1281,6 +1282,7 @@ DC24E10628B7BFD6002E4F5B /* ItemSearchScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemSearchScope.swift; sourceTree = ""; }; DC24E10A28B7C185002E4F5B /* SingleFolderSearchScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleFolderSearchScope.swift; sourceTree = ""; }; DC24E10C28B7C19F002E4F5B /* AccountSearchScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSearchScope.swift; sourceTree = ""; }; + DC24E10E28B7D2B9002E4F5B /* PopupButtonController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupButtonController.swift; sourceTree = ""; }; DC2565E8225F5A1900828AA5 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; DC26ADDD2550C0B20059680D /* MetadataDocumentationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataDocumentationTests.swift; sourceTree = ""; }; DC27A18C20CA9F66008ACB6C /* OCItem+FileProviderItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCItem+FileProviderItem.h"; sourceTree = ""; }; @@ -3111,6 +3113,7 @@ 3912208123436EB80026C290 /* SortMethod.swift */, 23FA23E520BFD3D8009A6D73 /* SortBar.swift */, DCD864112811FC5700CA6631 /* GradientView.swift */, + DC24E10E28B7D2B9002E4F5B /* PopupButtonController.swift */, ); path = "User Interface"; sourceTree = ""; @@ -4530,6 +4533,7 @@ DCE4E43124C197450051722F /* OpenItemUserActivity.swift in Sources */, DCFC9ED3280023BB005D9144 /* CollectionViewCellProvider+StandardImplementations.swift in Sources */, DC0A358D24C0E44B00FB58FC /* ThemedAlertController.swift in Sources */, + DC24E10F28B7D2B9002E4F5B /* PopupButtonController.swift in Sources */, DCE4E43C24C19B660051722F /* FrameViewController.swift in Sources */, DC0A35A124C1091400FB58FC /* UserInterfaceContext.swift in Sources */, DC0A357424C0E42D00FB58FC /* PushTransition.swift in Sources */, @@ -5056,7 +5060,7 @@ APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; APP_SHORT_VERSION = 12.0; - APP_VERSION = 224; + APP_VERSION = 228; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -5126,7 +5130,7 @@ APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; APP_SHORT_VERSION = 12.0; - APP_VERSION = 224; + APP_VERSION = 228; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; diff --git a/ownCloud/Resources/en.lproj/Localizable.strings b/ownCloud/Resources/en.lproj/Localizable.strings index 4e8ef97ca..75eccb9f1 100644 --- a/ownCloud/Resources/en.lproj/Localizable.strings +++ b/ownCloud/Resources/en.lproj/Localizable.strings @@ -105,6 +105,8 @@ "size" = "size"; "date" = "date"; "Search folder" = "Search folder"; +"Search space" = "Search space"; +"Search {{space.name}}" = "Search {{space.name}}"; "Search account" = "Search account"; "Pending" = "Pending"; "Show parent paths" = "Show parent paths"; diff --git a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift index 850fcf24e..f25069ce4 100644 --- a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift @@ -661,7 +661,7 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, // Drive if clientContext.core?.useDrives == true { - let driveName = clientContext.drive?.name ?? "Drive".localized + let driveName = "Space".localized scopes.append(.driveSearch(with: clientContext, cellStyle: cellStyle, localizedName: driveName)) } diff --git a/ownCloudAppShared/Client/Search/Item Search/Scopes/AccountSearchScope.swift b/ownCloudAppShared/Client/Search/Item Search/Scopes/AccountSearchScope.swift index 4f791e0e1..709f31c7a 100644 --- a/ownCloudAppShared/Client/Search/Item Search/Scopes/AccountSearchScope.swift +++ b/ownCloudAppShared/Client/Search/Item Search/Scopes/AccountSearchScope.swift @@ -143,7 +143,7 @@ open class CustomQuerySearchScope : ItemSearchScope { // Subclasses open class AccountSearchScope : CustomQuerySearchScope { - public override init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String, icon: UIImage? = nil) { + public override init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String, localizedPlaceholder placeholder: String? = nil, icon: UIImage? = nil) { var revealCellStyle : CollectionViewCellStyle? if let cellStyle = cellStyle { @@ -152,13 +152,13 @@ open class AccountSearchScope : CustomQuerySearchScope { }) } - super.init(with: context, cellStyle: revealCellStyle, localizedName: name, icon: icon) + super.init(with: context, cellStyle: revealCellStyle, localizedName: name, localizedPlaceholder: placeholder, icon: icon) } } open class DriveSearchScope : AccountSearchScope { - public override init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String, icon: UIImage? = nil) { - super.init(with: context, cellStyle: cellStyle, localizedName: name, icon: icon) + public override init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String, localizedPlaceholder placeholder: String? = nil, icon: UIImage? = nil) { + super.init(with: context, cellStyle: cellStyle, localizedName: name, localizedPlaceholder: placeholder, icon: icon) if context.core?.useDrives == true, let driveID = context.drive?.identifier { let requireDriveCondition = OCQueryCondition.where(.driveID, isEqualTo: driveID) diff --git a/ownCloudAppShared/Client/Search/Item Search/Scopes/ItemSearchScope.swift b/ownCloudAppShared/Client/Search/Item Search/Scopes/ItemSearchScope.swift index aae9ea144..274dabbe9 100644 --- a/ownCloudAppShared/Client/Search/Item Search/Scopes/ItemSearchScope.swift +++ b/ownCloudAppShared/Client/Search/Item Search/Scopes/ItemSearchScope.swift @@ -25,8 +25,8 @@ import ownCloudApp open class ItemSearchScope : SearchScope { private var sortDescriptorObserver: NSKeyValueObservation? - public override init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String, icon: UIImage? = nil) { - super.init(with: context, cellStyle: cellStyle, localizedName: name, icon: icon) + public override init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String, localizedPlaceholder placeholder: String? = nil, icon: UIImage? = nil) { + super.init(with: context, cellStyle: cellStyle, localizedName: name, localizedPlaceholder: placeholder, icon: icon) tokenizer = CustomQuerySearchTokenizer(scope: self, clientContext: context) diff --git a/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift b/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift index b0858c75b..0a0fe2e96 100644 --- a/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift +++ b/ownCloudAppShared/Client/Search/Scopes/SearchScope.swift @@ -21,6 +21,7 @@ import ownCloudSDK open class SearchScope: NSObject { public var localizedName : String + public var localizedPlaceholder: String? public var icon : UIImage? @objc public dynamic var results: OCDataSource? @@ -33,24 +34,29 @@ open class SearchScope: NSObject { public var tokenizer: SearchTokenizer? static public func modifyingQuery(with context: ClientContext, localizedName: String) -> SearchScope { - return SingleFolderSearchScope(with: context, cellStyle: nil, localizedName: localizedName, icon: UIImage(systemName: "folder")) + return SingleFolderSearchScope(with: context, cellStyle: nil, localizedName: localizedName, localizedPlaceholder: "Search folder".localized, icon: UIImage(systemName: "folder")) } static public func driveSearch(with context: ClientContext, cellStyle: CollectionViewCellStyle, localizedName: String) -> SearchScope { - return DriveSearchScope(with: context, cellStyle: cellStyle, localizedName: localizedName, icon: UIImage(systemName: "person")) + var placeholder = "Search space".localized + if let driveName = context.drive?.name, driveName.count > 0 { + placeholder = "Search {{space.name}}".localized(["space.name" : driveName]) + } + return DriveSearchScope(with: context, cellStyle: cellStyle, localizedName: localizedName, localizedPlaceholder: placeholder, icon: UIImage(systemName: "square.grid.2x2")) } static public func accountSearch(with context: ClientContext, cellStyle: CollectionViewCellStyle, localizedName: String) -> SearchScope { - return AccountSearchScope(with: context, cellStyle: cellStyle, localizedName: localizedName, icon: UIImage(systemName: "square.grid.2x2j")) + return AccountSearchScope(with: context, cellStyle: cellStyle, localizedName: localizedName, localizedPlaceholder: "Search account".localized, icon: UIImage(systemName: "person")) } - public init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String, icon: UIImage? = nil) { + public init(with context: ClientContext, cellStyle: CollectionViewCellStyle?, localizedName name: String, localizedPlaceholder placeholder: String? = nil, icon: UIImage? = nil) { clientContext = context localizedName = name super.init() resultsCellStyle = cellStyle + self.localizedPlaceholder = placeholder self.icon = icon } diff --git a/ownCloudAppShared/Client/Search/SearchViewController.swift b/ownCloudAppShared/Client/Search/SearchViewController.swift index 16ce9979f..b8cfa9193 100644 --- a/ownCloudAppShared/Client/Search/SearchViewController.swift +++ b/ownCloudAppShared/Client/Search/SearchViewController.swift @@ -35,7 +35,7 @@ open class SearchViewController: UIViewController, UITextFieldDelegate, Themeabl open var scopes: [SearchScope]? { didSet { - updateSegmentsFromScopes() + updateChoicesFromScopes() } } @@ -53,9 +53,11 @@ open class SearchViewController: UIViewController, UITextFieldDelegate, Themeabl } didSet { - if let activeScope = activeScope, let scopeIndex = scopes?.firstIndex(of: activeScope) { + if let activeScope = activeScope, let activeScopeChoice = self.scopePopup?.choices?.first(where: { choice in + (choice.representedObject as? NSObject) == activeScope + }) { OnMainThread { - self.scopeView.selectedSegmentIndex = scopeIndex + self.scopePopup?.selectedChoice = activeScopeChoice } } @@ -74,19 +76,35 @@ open class SearchViewController: UIViewController, UITextFieldDelegate, Themeabl } }) + searchField.placeholder = activeScope?.localizedPlaceholder + sendSearchFieldContentsToActiveScope() } } } - private func updateSegmentsFromScopes() { - scopeView.removeAllSegments() + private func updateChoicesFromScopes() { + var choices : [PopupButtonChoice] = [] if let scopes = scopes { for scope in scopes { - scopeView.insertSegment(withTitle: scope.localizedName, at: scopeView.numberOfSegments, animated: false) + choices.append(PopupButtonChoice(with: scope.localizedName, image: scope.icon, representedObject: scope)) } } + + let previouslySelectedChoice = scopePopup?.selectedChoice + + scopePopup?.choices = choices + + if let previouslySelectedChoice = previouslySelectedChoice, let previouslyRepresentedObject = previouslySelectedChoice.representedObject as? NSObject, let equalSelectedChoice = choices.first(where: { choice in + if let representedObject = choice.representedObject as? NSObject { + return representedObject == previouslyRepresentedObject + } + + return false + }) { + scopePopup?.selectedChoice = equalSelectedChoice + } } init(with clientContext: ClientContext, scopes: [SearchScope]?, targetNavigationItem: UINavigationItem? = nil, delegate: SearchViewControllerDelegate?) { @@ -110,31 +128,57 @@ open class SearchViewController: UIViewController, UITextFieldDelegate, Themeabl // MARK: - Views var searchField: UISearchTextField = UISearchTextField() - var scopeView: UISegmentedControl = UISegmentedControl() + var scopePopup: PopupButtonController? + var scopeViewController: UIViewController? { + willSet { + scopeViewController?.willMove(toParent: nil) + scopeViewController?.view.removeFromSuperview() + scopeViewController?.removeFromParent() + + scopeViewControllerConstraints = nil + } + didSet { + if let scopeViewController = scopeViewController, let scopeViewControllerView = scopeViewController.view { + addChild(scopeViewController) + view.addSubview(scopeViewControllerView) + scopeViewControllerConstraints = [ + scopeViewControllerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 5), + scopeViewControllerView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10), + scopeViewControllerView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10), + scopeViewControllerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10) + ] + scopeViewController.didMove(toParent: self) + } + } + } + private var scopeViewControllerConstraints : [NSLayoutConstraint]? { + willSet { + if let scopeViewControllerConstraints = scopeViewControllerConstraints { + NSLayoutConstraint.deactivate(scopeViewControllerConstraints) + } + } + didSet { + if let scopeViewControllerConstraints = scopeViewControllerConstraints { + NSLayoutConstraint.activate(scopeViewControllerConstraints) + } + } + } open override func loadView() { let rootView = UIView() - scopeView.translatesAutoresizingMaskIntoConstraints = false - scopeView.addAction(UIAction(handler: { [weak self] action in - guard let self = self, let scopes = self.scopes else { - return - } - - let selectedIndex = self.scopeView.selectedSegmentIndex + scopePopup = PopupButtonController(with: [], selectedChoice: nil, choiceHandler: { [weak self] choice in + self?.activeScope = choice.representedObject as? SearchScope + }) + // scopePopup?.showTitleInButton = false - if selectedIndex >= 0, selectedIndex < scopes.count { - self.activeScope = scopes[selectedIndex] - } - }), for: .valueChanged) - rootView.addSubview(scopeView) + var scopePopupButtonConfiguration = UIButton.Configuration.borderless() + scopePopupButtonConfiguration.contentInsets.leading = 0 + scopePopupButtonConfiguration.contentInsets.trailing = 5 + scopePopup?.button.configuration = scopePopupButtonConfiguration NSLayoutConstraint.activate([ - scopeView.topAnchor.constraint(equalTo: rootView.safeAreaLayoutGuide.topAnchor, constant: 5), - scopeView.leadingAnchor.constraint(equalTo: rootView.safeAreaLayoutGuide.leadingAnchor, constant: 10), - scopeView.trailingAnchor.constraint(equalTo: rootView.safeAreaLayoutGuide.trailingAnchor, constant: -10), - scopeView.bottomAnchor.constraint(equalTo: rootView.safeAreaLayoutGuide.bottomAnchor, constant: -10), - + rootView.heightAnchor.constraint(equalToConstant: 10).with(priority: .defaultHigh), // Shrink to 10 points height if no scopeViewController is set searchField.widthAnchor.constraint(equalToConstant: 10000).with(priority: .defaultHigh) // maximize width of searchField in UINavigationBar ]) @@ -149,9 +193,13 @@ open class SearchViewController: UIViewController, UITextFieldDelegate, Themeabl searchField.addTarget(self, action: #selector(searchFieldContentsChanged), for: .editingChanged) searchField.delegate = self + if let scopesCount = scopes?.count, scopesCount > 1 { + searchField.leftView = scopePopup?.button + } + injectIntoNavigationItem() - updateSegmentsFromScopes() + updateChoicesFromScopes() delegate?.searchBegan(for: self) @@ -185,7 +233,8 @@ open class SearchViewController: UIViewController, UITextFieldDelegate, Themeabl // Overwrite content targetNavigationItem.titleView = searchField - let cancelToolbarButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(endSearch)) + // Alternative implementation as a standard "Cancel" button, more convention compliant, but needs more space: let cancelToolbarButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(endSearch)) + let cancelToolbarButton = UIBarButtonItem(image: UIImage(systemName: "xmark"), style: .done, target: self, action: #selector(endSearch)) targetNavigationItem.rightBarButtonItems = [ cancelToolbarButton ] targetNavigationItem.hidesBackButton = true diff --git a/ownCloudAppShared/Client/User Interface/PopupButtonController.swift b/ownCloudAppShared/Client/User Interface/PopupButtonController.swift new file mode 100644 index 000000000..032affd8d --- /dev/null +++ b/ownCloudAppShared/Client/User Interface/PopupButtonController.swift @@ -0,0 +1,158 @@ +// +// UIButton+PopupButton.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 25.08.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit + +open class PopupButtonChoice : NSObject { + var image: UIImage? + + var title: String + var buttonTitle: String? + var buttonAccessibilityLabel: String? + + var representedObject: AnyObject? + + init(with title: String, image: UIImage?, buttonTitle: String? = nil, buttonAccessibilityLabel: String? = nil, representedObject: AnyObject? = nil) { + self.title = title + super.init() + + self.image = image + self.buttonTitle = buttonTitle + self.buttonAccessibilityLabel = buttonAccessibilityLabel + self.representedObject = representedObject + } +} + +open class PopupButtonController : NSObject { + typealias TitleCustomizer = (_ choice: PopupButtonChoice, _ isSelected: Bool) -> String + typealias ChoiceHandler = (_ choice: PopupButtonChoice) -> Void + + var button : UIButton + + var choices : [PopupButtonChoice]? + var selectedChoice : PopupButtonChoice? { + didSet { + _updateTitleFromSelectedChoice() + } + } + var isDropDown : Bool = true + var showTitleInButton: Bool = true { + didSet { + _updateTitleFromSelectedChoice() + } + } + var showImageInButton: Bool = true { + didSet { + _updateTitleFromSelectedChoice() + } + } + + var titleCustomizer: TitleCustomizer? + var choiceHandler: ChoiceHandler? + + override init() { + button = UIButton(type: .system) + + super.init() + + button.translatesAutoresizingMaskIntoConstraints = false + + button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .subheadline) + button.titleLabel?.adjustsFontForContentSizeCategory = true + + button.setContentHuggingPriority(.required, for: .horizontal) + + button.showsMenuAsPrimaryAction = true + } + + convenience init(with choices: [PopupButtonChoice], selectedChoice: PopupButtonChoice?, titleCustomizer: TitleCustomizer? = nil, dropDown: Bool = false, title: String? = nil, choiceHandler: ChoiceHandler? = nil) { + self.init() + + self.choices = choices + self.selectedChoice = selectedChoice ?? choices.first + self.titleCustomizer = titleCustomizer + self.choiceHandler = choiceHandler + + self.isDropDown = dropDown + + if let title = title { + button.setTitle(title, for: .normal) + } else { + _updateTitleFromSelectedChoice() + } + + button.menu = UIMenu(title: "", children: [ + UIDeferredMenuElement.uncached({ [weak self] completion in + var menuItems : [UIMenuElement] = [] + guard let choices = self?.choices else { + completion(menuItems) + return + } + + for choice in choices { + let isSelectedChoice : Bool = (self?.isDropDown == false) ? (self?.selectedChoice == choice) : false + + var title = choice.title + + if let titleCustomizer = self?.titleCustomizer { + title = titleCustomizer(choice, isSelectedChoice) + } + + let menuItem = UIAction(title: title, image: choice.image, attributes: [], state: isSelectedChoice ? .on : .off) { [weak self] _ in + self?.selectedChoice = choice + self?.choiceHandler?(choice) + } + + menuItems.append(menuItem) + } + + completion(menuItems) + }) + ]) + } + + private func _updateTitleFromSelectedChoice() { + if let selectedChoice = selectedChoice, !isDropDown { + var title : String? = selectedChoice.buttonTitle ?? selectedChoice.title + + if let titleCustomizer = titleCustomizer { + title = titleCustomizer(selectedChoice, true) + } + + let chevronAttachment = NSTextAttachment() + chevronAttachment.image = UIImage(named: "chevron-small-light")?.withRenderingMode(.alwaysTemplate) + // Alternative using SF Symbols (but too strong IMO): chevronAttachment.image = UIImage(systemName: "chevron.down")?.withRenderingMode(.alwaysTemplate) + let chevronString = NSAttributedString(attachment: chevronAttachment) + + let attributedTitle = NSMutableAttributedString(string: (title != nil) && showTitleInButton ? " \(title!) " : " ") + + if button.effectiveUserInterfaceLayoutDirection == .leftToRight { + attributedTitle.append(chevronString) + } else { + attributedTitle.insert(chevronString, at: 0) + } + + button.setAttributedTitle(attributedTitle, for: .normal) + if showImageInButton { + button.setImage(selectedChoice.image, for: .normal) + } + button.accessibilityLabel = selectedChoice.buttonAccessibilityLabel + button.sizeToFit() + } + } +} From 6abb59dbe222b0d5c56de287185fad117f23bd4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Hu=CC=88hne?= <> Date: Wed, 7 Sep 2022 10:45:32 +0200 Subject: [PATCH 056/328] Changed app version to value 11.11.0 --- ownCloud.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 95b506781..75f0fe523 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -4686,8 +4686,8 @@ APP_BUILD_FLAGS = "$(inherited)"; APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; - APP_SHORT_VERSION = 11.10.1; - APP_VERSION = 226; + APP_SHORT_VERSION = 11.11.0; + APP_VERSION = 229; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -4755,8 +4755,8 @@ APP_BUILD_FLAGS = "$(inherited)"; APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; - APP_SHORT_VERSION = 11.10.1; - APP_VERSION = 226; + APP_SHORT_VERSION = 11.11.0; + APP_VERSION = 229; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; From 10d6367fafeac31f094d8dafc364da75b752b881 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Thu, 8 Sep 2022 11:43:03 +0200 Subject: [PATCH 057/328] - support for App Providers - new OpenInWebAction to open a document in a web app via Safari - new CreateDocumentAction to create a new document, after picking a format and entering a file name - preloading / caching of App Provider icons in ClientRootViewController - extend ActionContext with ClientContext property and adapt code accordingly --- ios-sdk | 2 +- ownCloud.xcodeproj/project.pbxproj | 8 + .../xcshareddata/swiftpm/Package.resolved | 32 --- ownCloud/AppDelegate.swift | 1 + .../CreateDocumentAction.swift | 187 ++++++++++++++++++ .../OpenInWebAppAction.swift | 129 ++++++++++++ ...ClientRootViewController+ItemActions.swift | 2 +- .../Client/ClientRootViewController.swift | 65 ++++++ .../Resources/en.lproj/Localizable.strings | 4 + ownCloudAppShared/Client/Actions/Action.swift | 33 ++-- .../ClientItemViewController.swift | 8 +- .../OCItem+Interactions.swift | 6 +- 12 files changed, 422 insertions(+), 55 deletions(-) delete mode 100644 ownCloud.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 ownCloud/Client/Actions/Actions+Extensions/CreateDocumentAction.swift create mode 100644 ownCloud/Client/Actions/Actions+Extensions/OpenInWebAppAction.swift diff --git a/ios-sdk b/ios-sdk index adea1a552..9d93c4dab 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit adea1a55209069502d392b80019bfec908c5161e +Subproject commit 9d93c4dabbbb0c6426f9f1c397ea29bd2b62e756 diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 1c8a63896..6e4636122 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -274,6 +274,8 @@ DC0A35A124C1091400FB58FC /* UserInterfaceContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0A35A024C1091400FB58FC /* UserInterfaceContext.swift */; }; DC0A5C432550C70800E6674B /* class-settings-sdk in Resources */ = {isa = PBXBuildFile; fileRef = DC0A5C422550C70800E6674B /* class-settings-sdk */; }; DC0B379420514E4700189B9A /* ServerListBookmarkCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B379320514E4700189B9A /* ServerListBookmarkCell.swift */; }; + DC0CE19228C7DBE3009ABDFB /* OpenInWebAppAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0CE19128C7DBE3009ABDFB /* OpenInWebAppAction.swift */; }; + DC0CE19D28C89CD9009ABDFB /* CreateDocumentAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0CE19C28C89CD9009ABDFB /* CreateDocumentAction.swift */; }; DC18898E218A773700CFB3F9 /* ownCloudMocking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC0196A620F754CA00C41B78 /* ownCloudMocking.framework */; }; DC1B270C209CF34B004715E1 /* BookmarkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1B270B209CF34B004715E1 /* BookmarkViewController.swift */; }; DC20DE5C21C01A3D0096000B /* ownCloudMocking.framework in EarlGrey Copy Files */ = {isa = PBXBuildFile; fileRef = DC0196A620F754CA00C41B78 /* ownCloudMocking.framework */; }; @@ -1249,6 +1251,8 @@ DC0B379320514E4700189B9A /* ServerListBookmarkCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListBookmarkCell.swift; sourceTree = ""; }; DC0B37952051541C00189B9A /* ownCloud.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ownCloud.entitlements; sourceTree = ""; }; DC0B37962051681600189B9A /* ThemeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeButton.swift; sourceTree = ""; }; + DC0CE19128C7DBE3009ABDFB /* OpenInWebAppAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInWebAppAction.swift; sourceTree = ""; }; + DC0CE19C28C89CD9009ABDFB /* CreateDocumentAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateDocumentAction.swift; sourceTree = ""; }; DC136581208223F000FC0F60 /* OCBookmark+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCBookmark+Extension.swift"; sourceTree = ""; }; DC1AC7CF2319ADAE002B7892 /* ScanViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanViewController.swift; sourceTree = ""; }; DC1B270B209CF34B004715E1 /* BookmarkViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkViewController.swift; sourceTree = ""; }; @@ -2231,6 +2235,8 @@ 0233F45D246E9D960095A799 /* UploadCameraMediaAction.swift */, 025F063224AA163C009D8FC5 /* DisplayExifMetadataAction.swift */, 39EF06AF25D6C3FC001E1E19 /* PresentationModeAction.swift */, + DC0CE19128C7DBE3009ABDFB /* OpenInWebAppAction.swift */, + DC0CE19C28C89CD9009ABDFB /* CreateDocumentAction.swift */, ); path = "Actions+Extensions"; sourceTree = ""; @@ -4224,6 +4230,7 @@ 396BE4CA2289500E00B254A9 /* RoundedLabel.swift in Sources */, 394E1FFF233E43F5009D2897 /* LinksAction.swift in Sources */, 392DDB1424CF024D009E5406 /* ImportFilesController.swift in Sources */, + DC0CE19228C7DBE3009ABDFB /* OpenInWebAppAction.swift in Sources */, 396C82FB2319AFDD00938262 /* CollaborateAction.swift in Sources */, 0233F45E246E9D960095A799 /* UploadCameraMediaAction.swift in Sources */, DC854936218331CF00782BA8 /* UserInterfaceSettingsSection.swift in Sources */, @@ -4291,6 +4298,7 @@ DCC3701624D4D365008B0DEB /* OCScanJobActivity+DiagnosticGenerator.swift in Sources */, 39A243C424BDD9E100F4441F /* StaticLoginBundle.swift in Sources */, DC27A19D20CAB602008ACB6C /* FileProviderInterfaceManager.swift in Sources */, + DC0CE19D28C89CD9009ABDFB /* CreateDocumentAction.swift in Sources */, 4C51727D22DE04BD001BC97F /* ScheduledTaskExtension.swift in Sources */, 025F063324AA163C009D8FC5 /* DisplayExifMetadataAction.swift in Sources */, DCC085512293ED52008CC05C /* DisplaySettingsSection.swift in Sources */, diff --git a/ownCloud.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ownCloud.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index b14d7da78..000000000 --- a/ownCloud.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,32 +0,0 @@ -{ - "pins" : [ - { - "identity" : "openssl", - "kind" : "remoteSourceControl", - "location" : "https://github.com/krzyzanowskim/OpenSSL.git", - "state" : { - "revision" : "8697a05bcddbbeb5ac2d29cd60442cf93ee0d3ae", - "version" : "1.1.1400" - } - }, - { - "identity" : "plcrashreporter", - "kind" : "remoteSourceControl", - "location" : "https://github.com/microsoft/plcrashreporter.git", - "state" : { - "revision" : "4637a7854de2cc5c354d46fb931d74bdbc2c043e", - "version" : "1.7.0" - } - }, - { - "identity" : "pocketsvg", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pocketsvg/PocketSVG.git", - "state" : { - "revision" : "51d4fd9ae48e79207034afdd53f365fc2b9d6068", - "version" : "2.7.0" - } - } - ], - "version" : 2 -} diff --git a/ownCloud/AppDelegate.swift b/ownCloud/AppDelegate.swift index 57e3b649b..01131ac6e 100644 --- a/ownCloud/AppDelegate.swift +++ b/ownCloud/AppDelegate.swift @@ -121,6 +121,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { OCExtensionManager.shared.addExtension(PDFGoToPageAction.actionExtension) OCExtensionManager.shared.addExtension(ImportPasteboardAction.actionExtension) OCExtensionManager.shared.addExtension(CutAction.actionExtension) + OCExtensionManager.shared.addExtension(CreateDocumentAction.actionExtension) if UIDevice.current.isIpad { // iPad only diff --git a/ownCloud/Client/Actions/Actions+Extensions/CreateDocumentAction.swift b/ownCloud/Client/Actions/Actions+Extensions/CreateDocumentAction.swift new file mode 100644 index 000000000..4bc1094fe --- /dev/null +++ b/ownCloud/Client/Actions/Actions+Extensions/CreateDocumentAction.swift @@ -0,0 +1,187 @@ +// +// CreateDocumentAction.swift +// ownCloud +// +// Created by Felix Schwarz on 07.09.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK +import ownCloudAppShared + +class CreateDocumentAction: Action { + override open class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.createDocument") } + override open class var category : ActionCategory? { return .normal } + override open class var name : String? { return "New document".localized } + override open class var locations : [OCExtensionLocationIdentifier]? { return [.folderAction, .keyboardShortcut, .emptyFolder] } + override open class var keyCommand : String? { return "N" } + override open class var keyModifierFlags: UIKeyModifierFlags? { return [.command, .shift] } + + // MARK: - Extension matching + override open class func applicablePosition(forContext: ActionContext) -> ActionPosition { + if forContext.items.count > 1 { + return .none + } + + if forContext.items.first?.type != OCItemType.collection { + return .none + } + + if forContext.items.first?.permissions.contains(.createFile) == false { + return .none + } + + if forContext.core?.appProvider?.types?.contains(where: { fileType in + return fileType.allowCreation + }) == true { + return .first + } + + return .none + } + + // MARK: - Action implementation + override open func run() { + guard context.items.count > 0 else { + completed(with: NSError(ocError: .itemNotFound)) + return + } + + let item = context.items.first + + guard item != nil, let itemLocation = item?.location else { + completed(with: NSError(ocError: .itemNotFound)) + return + } + + guard let viewController = context.viewController else { + completed(with: NSError(ocError: .internal)) + return + } + + guard let documentTypes = context.core?.appProvider?.types?.filter({ fileType in + return fileType.allowCreation + }).sorted(by: { (type1, type2) in + if let name1 = type1.name, let name2 = type2.name { + return name1.localizedCaseInsensitiveCompare(name2) == .orderedAscending + } + return false + }), documentTypes.count > 0 else { + completed() + return + } + + OnMainThread { + let documentTypesDataSource = OCDataSourceArray() + let documentTypesSection = CollectionViewSection(identifier: "documentTypes", dataSource: documentTypesDataSource, cellStyle: .init(with: .fillSpace), cellLayout: .fullWidth(itemHeightDimension: .estimated(54), groupHeightDimension: .estimated(54), edgeSpacing: NSCollectionLayoutEdgeSpacing(leading: .fixed(0), top: .fixed(10), trailing: .fixed(0), bottom: .fixed(10)), contentInsets: NSDirectionalEdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20)), clientContext: self.context.clientContext) + let documentPickerViewController = CollectionViewController(context: self.context.clientContext, sections: [ documentTypesSection ]) + + let navigationViewController = ThemeNavigationController(rootViewController: documentPickerViewController) + navigationViewController.modalPresentationStyle = .formSheet + viewController.present(navigationViewController, animated: true) + + documentPickerViewController.title = "New document".localized + documentPickerViewController.navigationItem.rightBarButtonItem = UIBarButtonItem(systemItem: .cancel, primaryAction: UIAction(handler: { [weak navigationViewController] action in + navigationViewController?.dismiss(animated: true) + self.completed() + }), menu: nil) + + let createDocument : (OCAppProviderFileType) -> Void = { (fileType) in + guard let core = self.core, let parentItem = try? core.cachedItem(at: itemLocation) else { + self.completed() + return + } + + core.suggestUnusedNameBased(on: "New document".localized.appending((fileType.extension != nil) ? ".\(fileType.extension!)" : ""), at: itemLocation, isDirectory: true, using: .numbered, filteredBy: nil, resultHandler: { (suggestedName, _) in + guard let suggestedName = suggestedName else { return } + + OnMainThread { + let documentNameViewController = NamingViewController( with: self.core, defaultName: suggestedName, stringValidator: { name in + if name.contains("/") || name.contains("\\") { + return (false, nil, "File name cannot contain / or \\".localized) + } else { + if let item = item { + if ((try? self.core?.cachedItem(inParent: item, withName: name, isDirectory: true)) != nil) || + ((try? self.core?.cachedItem(inParent: item, withName: name, isDirectory: false)) != nil) { + return (false, "Item with same name already exists".localized, "An item with the same name already exists in this location.".localized) + } + } + + return (true, nil, nil) + } + }, completion: { newFileName, _ in + guard let newFileName = newFileName, let core = self.core else { + self.completed() + return + } + + if let progress = core.connection.createAppFile(of: fileType, in: parentItem, withName: newFileName, completionHandler: { (error, fileID, item) in + if error == nil, let query = self.context.clientContext?.query { + self.core?.reload(query) + } + + self.completed(with: error) + }) { + self.publish(progress: progress) + } + }) + + documentNameViewController.navigationItem.title = "Pick a name".localized + + navigationViewController.pushViewController(documentNameViewController, animated: true) + } + }) + } + + let fallbackIcon = UIImage(systemName: "doc")?.withRenderingMode(.alwaysTemplate) + let iconSize = CGSize(width: 36, height: 36) + + let headerItem = OCDataItemPresentable(reference: "_header" as NSString, originalDataItemType: nil, version: nil) + headerItem.title = "Pick a document type to create:".localized + headerItem.childrenDataSourceProvider = nil + + var documentTypeActions : [OCDataItem & OCDataItemVersioning] = [ headerItem ] + + for documentType in documentTypes { + if let documentTypeName = documentType.name { + var docIcon : UIImage? + + if let docTypeIcon = documentType.icon { + docIcon = docTypeIcon + } else if let mimeType = documentType.mimeType, let tvgIconName = OCItem.iconName(for: mimeType) { + docIcon = Theme.shared.image(for: tvgIconName, size: iconSize) + } + + if docIcon == nil { + docIcon = fallbackIcon + } + + docIcon = docIcon?.paddedTo(width: iconSize.width, height: iconSize.height) + + let action = OCAction(title: documentTypeName, icon: docIcon, action: { action, options, completionHandler in + createDocument(documentType) + }) + + documentTypeActions.append(action) + } + } + + documentTypesDataSource.setVersionedItems(documentTypeActions) + } + } + + override open class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { + return UIImage(systemName: "doc.badge.plus")?.withRenderingMode(.alwaysTemplate) + } +} diff --git a/ownCloud/Client/Actions/Actions+Extensions/OpenInWebAppAction.swift b/ownCloud/Client/Actions/Actions+Extensions/OpenInWebAppAction.swift new file mode 100644 index 000000000..8b886a9b3 --- /dev/null +++ b/ownCloud/Client/Actions/Actions+Extensions/OpenInWebAppAction.swift @@ -0,0 +1,129 @@ +// +// OpenInWebAppAction.swift +// ownCloud +// +// Created by Felix Schwarz on 06.09.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK +import ownCloudAppShared + +class OpenInWebAppAction: Action { + override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.openinwebapp") } + override class var category : ActionCategory? { return .normal } + override class var locations : [OCExtensionLocationIdentifier]? { return [.moreItem, .moreDetailItem, .contextMenuItem] } + + class open func createActionExtension(for app: OCAppProviderApp, core: OCCore) -> ActionExtension { + let objectProvider : OCExtensionObjectProvider = { (_ rawExtension, _ context, _ error) -> Any? in + if let actionExtension = rawExtension as? ActionExtension, + let actionContext = context as? ActionContext { + let action = self.init(for: actionExtension, with: actionContext) + + action.app = app + + return action + } + + return nil + } + + let appName = app.name ?? UUID().uuidString + let extensionIdentifier = OCExtensionIdentifier("\(identifier!.rawValue).\(appName.lowercased())") + let coreRunIdentifier = core.runIdentifier + + let standardMatcher = actionCustomContextMatcher + let customMatcher : OCExtensionCustomContextMatcher = { (context, priority) -> OCExtensionPriority in + // Apply standard matching + let standardPriority = standardMatcher(context, priority) + + guard standardPriority != .noMatch, let actionContext = context as? ActionContext else { + return .noMatch + } + + // Limit to specific core + guard let core = actionContext.core, core.runIdentifier == coreRunIdentifier else { + return .noMatch + } + + // Apply app matching + if self.applicablePosition(forContext: actionContext, app: app) == .none { + return .noMatch + } + + return standardPriority + } + + return ActionExtension(name: "Open in {{appName}} (web)".localized(["appName" : appName]), category: category!, identifier: extensionIdentifier, locations: locations, features: features, objectProvider: objectProvider, customMatcher: customMatcher, keyCommand: nil, keyModifierFlags: nil) + } + + class open func applicablePosition(forContext: ActionContext, app: OCAppProviderApp) -> ActionPosition { + // OpenInWebApp only supports a single item + guard let item = forContext.items.first, forContext.items.count == 1 else { + return .none + } + + // Exclude directories + if item.type == .collection { + return .none + } + + // Ensure item is supported by the web app + if !app.supportsItem(item) { + return .none + } + + return .nearFirst + } + + var app : OCAppProviderApp? + + // MARK: - Action implementation + override func run() { + guard context.items.count == 1, let item = context.items.first, let core = context.core else { + self.completed(with: NSError(ocError: .insufficientParameters)) + return + } + + core.connection.open(inWeb: item, with: app) { (error, url) in + if let url = url { + OnMainThread { + UIApplication.shared.open(url) + } + } + + self.completed(with: error) + } + } + + override var position: ActionPosition { + if let app = app { + return type(of: self).applicablePosition(forContext: context, app: app) + } + + return .none + } + + override var icon: UIImage? { + if let remoteIcon = (app?.iconResourceRequest?.resource as? OCResourceImage)?.image?.image { + return remoteIcon + } + + return super.icon + } + + override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { + return UIImage(systemName: "globe")?.withRenderingMode(.alwaysTemplate) + } +} diff --git a/ownCloud/Client/ClientRootViewController+ItemActions.swift b/ownCloud/Client/ClientRootViewController+ItemActions.swift index 07b6910be..96fde2e40 100644 --- a/ownCloud/Client/ClientRootViewController+ItemActions.swift +++ b/ownCloud/Client/ClientRootViewController+ItemActions.swift @@ -40,7 +40,7 @@ extension ClientRootViewController : MoreItemAction { } let originatingViewController : UIViewController = context.originatingViewController ?? self let actionsLocation = OCExtensionLocation(ofType: .action, identifier: locationIdentifier) - let actionContext = ActionContext(viewController: originatingViewController, core: core, query: context.query, items: [item], location: actionsLocation, sender: sender) + let actionContext = ActionContext(viewController: originatingViewController, clientContext: context, core: core, query: context.query, items: [item], location: actionsLocation, sender: sender) if let moreViewController = Action.cardViewController(for: item, with: actionContext, progressHandler: makeActionProgressHandler(), completionHandler: nil) { originatingViewController.present(asCard: moreViewController, animated: true) diff --git a/ownCloud/Client/ClientRootViewController.swift b/ownCloud/Client/ClientRootViewController.swift index f484e1fbb..dac3d23cc 100644 --- a/ownCloud/Client/ClientRootViewController.swift +++ b/ownCloud/Client/ClientRootViewController.swift @@ -74,6 +74,8 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa var alertQueue : OCAsyncSequentialQueue = OCAsyncSequentialQueue() + var appProviderObservation: NSKeyValueObservation? + init(bookmark inBookmark: OCBookmark) { bookmark = inBookmark @@ -182,6 +184,8 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa core?.messageQueue.remove(presenter: cardMessagePresenter) } + appProviderActionExtensions = nil + if self.coreRequested { self.fpServiceStandby?.stop() OCCoreManager.shared.returnCore(for: bookmark, completionHandler: nil) @@ -211,6 +215,11 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa core?.messageQueue.add(presenter: cardMessagePresenter) } + // Observe .appProvider property + self.appProviderObservation = core?.observe(\OCCore.appProvider, options: .initial, changeHandler: { [weak self] (core, change) in + self?.appProviderChanged(to: core.appProvider) + }) + // Remove skip available offline when user opens the bookmark core?.vault.keyValueStore?.storeObject(nil, forKey: .coreSkipAvailableOfflineKey) }, completionHandler: { (core, error) in @@ -528,6 +537,62 @@ class ClientRootViewController: UITabBarController, BookmarkContainer, ToolAndTa return nil } + + var appProviderActionExtensions : [OCExtension]? { + willSet { + if let extensions = appProviderActionExtensions { + for ext in extensions { + OCExtensionManager.shared.removeExtension(ext) + } + } + } + + didSet { + if let extensions = appProviderActionExtensions { + for ext in extensions { + OCExtensionManager.shared.addExtension(ext) + } + } + } + } + + func appProviderChanged(to appProvider: OCAppProvider?) { + var actionExtensions : [OCExtension] = [] + + if let core = core { + if let apps = core.appProvider?.apps { + for app in apps { + // Pre-load app icon + if let appIconRequest = app.iconResourceRequest { + core.vault.resourceManager?.start(appIconRequest) + } + + // Create app-specific open-in-web-app action + let openInWebAction = OpenInWebAppAction.createActionExtension(for: app, core: core) + actionExtensions.append(openInWebAction) + } + } + + if let types = core.appProvider?.types { + let creationTypes = types.filter({ type in + return type.allowCreation + }) + + if creationTypes.count > 0 { + // Pre-load document icons + for type in creationTypes { + if let typeIconRequest = type.iconResourceRequest { + core.vault.resourceManager?.start(typeIconRequest) + } + } + + // Log.debug("Creation Types: \(String(describing: creationTypes))") + } + } + } + + appProviderActionExtensions = actionExtensions + } } extension ClientRootViewController : Themeable { diff --git a/ownCloud/Resources/en.lproj/Localizable.strings b/ownCloud/Resources/en.lproj/Localizable.strings index 4e8ef97ca..d75b6b17a 100644 --- a/ownCloud/Resources/en.lproj/Localizable.strings +++ b/ownCloud/Resources/en.lproj/Localizable.strings @@ -357,9 +357,13 @@ "Folder name" = "Folder name"; "Rename" ="Rename"; "Create folder" ="Create folder"; +"New document" = "New document"; +"Pick a name" = "Pick a name"; +"Pick a document type to create:" = "Pick a document type to create:"; "Duplicate" = "Duplicate"; "Move" = "Move"; "Open in" = "Open in"; +"Open in {{appName}} (web)" = "Open in {{appName}} (web)"; "Copy" = "Copy"; "Copy here" = "Copy here"; "Cannot connect to " = "Cannot connect to "; diff --git a/ownCloudAppShared/Client/Actions/Action.swift b/ownCloudAppShared/Client/Actions/Action.swift index 217f7bb01..29df40562 100644 --- a/ownCloudAppShared/Client/Actions/Action.swift +++ b/ownCloudAppShared/Client/Actions/Action.swift @@ -111,6 +111,8 @@ public class ActionContext: OCExtensionContext { private var cachedParentFolders = [OCLocalID : OCItem]() private var itemStorage: [OCItem] + public var clientContext : ClientContext? + public var items: [OCItem] { get { return itemStorage @@ -152,7 +154,7 @@ public class ActionContext: OCExtensionContext { } // MARK: - Init & Deinit. - public init(viewController: UIViewController, core: OCCore, query: OCQuery? = nil, items: [OCItem], location: OCExtensionLocation, sender: AnyObject? = nil, requirements: [String : Any]? = nil, preferences: [String : Any]? = nil) { + public init(viewController: UIViewController, clientContext: ClientContext? = nil, core: OCCore, query: OCQuery? = nil, items: [OCItem], location: OCExtensionLocation, sender: AnyObject? = nil, requirements: [String : Any]? = nil, preferences: [String : Any]? = nil) { itemStorage = items @@ -163,6 +165,8 @@ public class ActionContext: OCExtensionContext { self.core = core self.location = location + self.clientContext = clientContext + self.query = query self.requirements = requirements self.preferences = preferences @@ -273,18 +277,8 @@ open class Action : NSObject { class open var features : [String : Any]? { return nil } // MARK: - Extension creation - class open var actionExtension : ActionExtension { - let objectProvider : OCExtensionObjectProvider = { (_ rawExtension, _ context, _ error) -> Any? in - if let actionExtension = rawExtension as? ActionExtension, - let actionContext = context as? ActionContext { - return self.init(for: actionExtension, with: actionContext) - } - - return nil - } - - let customMatcher : OCExtensionCustomContextMatcher = { (context, priority) -> OCExtensionPriority in - + class public var actionCustomContextMatcher : OCExtensionCustomContextMatcher { + return { (context, priority) -> OCExtensionPriority in // Make sure we have valid context and extension was not filtered out due to location mismatch guard let actionContext = context as? ActionContext, priority != .noMatch else { return priority @@ -314,8 +308,19 @@ open class Action : NSObject { // Additional filtering (f.ex. via OCClassSettings, Settings) goes here } + } + + class open var actionExtension : ActionExtension { + let objectProvider : OCExtensionObjectProvider = { (_ rawExtension, _ context, _ error) -> Any? in + if let actionExtension = rawExtension as? ActionExtension, + let actionContext = context as? ActionContext { + return self.init(for: actionExtension, with: actionContext) + } + + return nil + } - return ActionExtension(name: name!, category: category!, identifier: identifier!, locations: locations, features: features, objectProvider: objectProvider, customMatcher: customMatcher, keyCommand: keyCommand, keyModifierFlags: keyModifierFlags) + return ActionExtension(name: name!, category: category!, identifier: identifier!, locations: locations, features: features, objectProvider: objectProvider, customMatcher: actionCustomContextMatcher, keyCommand: keyCommand, keyModifierFlags: keyModifierFlags) } // MARK: - Extension matching diff --git a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift index f23c8bd88..9f6158cdf 100644 --- a/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift @@ -313,7 +313,7 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, let locationIdentifier: OCExtensionLocationIdentifier = .emptyFolder let originatingViewController : UIViewController = context.originatingViewController ?? self let actionsLocation = OCExtensionLocation(ofType: .action, identifier: locationIdentifier) - let actionContext = ActionContext(viewController: originatingViewController, core: core, query: context.query, items: [item], location: actionsLocation, sender: self) + let actionContext = ActionContext(viewController: originatingViewController, clientContext: clientContext, core: core, query: context.query, items: [item], location: actionsLocation, sender: self) let emptyFolderActions = Action.sortedApplicableActions(for: actionContext) let actions = emptyFolderActions.map({ action in action.provideOCAction() }) @@ -466,7 +466,7 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, if let core = clientContext?.core { let actionsLocation = OCExtensionLocation(ofType: .action, identifier: .multiSelection) - multiSelectionActionContext = ActionContext(viewController: self, core: core, query: query, items: [OCItem](), location: actionsLocation) + multiSelectionActionContext = ActionContext(viewController: self, clientContext: clientContext, core: core, query: query, items: [OCItem](), location: actionsLocation) } // Setup multi selection action datasource @@ -619,7 +619,7 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, let items = provideDropItems(from: dropSession, target: view) if items.count > 0, let core = clientContext?.core { - dropTargetsActionContext = ActionContext(viewController: self, core: core, items: items, location: OCExtensionLocation(ofType: .action, identifier: .dropAction)) + dropTargetsActionContext = ActionContext(viewController: self, clientContext: clientContext, core: core, items: items, location: OCExtensionLocation(ofType: .action, identifier: .dropAction)) if let dropTargetsActionContext = dropTargetsActionContext { let actions = Action.sortedApplicableActions(for: dropTargetsActionContext) @@ -638,7 +638,7 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, // MARK: - Reveal public func reveal(item: OCDataItem, context: ClientContext, sender: AnyObject?) -> Bool { if let revealInteraction = item as? DataItemSelectionInteraction { - if revealInteraction.revealItem?(from: self, with: clientContext, animated: true, pushViewController: true, completion: nil) != nil { + if revealInteraction.revealItem?(from: self, with: context, animated: true, pushViewController: true, completion: nil) != nil { return true } } diff --git a/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift b/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift index efb9646d8..31cfe6226 100644 --- a/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift +++ b/ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift @@ -99,7 +99,7 @@ extension OCItem : DataItemSwipeInteraction { } let actionsLocation = OCExtensionLocation(ofType: .action, identifier: .tableRow) - let actionContext = ActionContext(viewController: originatingViewController, core: core, items: [self], location: actionsLocation, sender: nil) + let actionContext = ActionContext(viewController: originatingViewController, clientContext: context, core: core, items: [self], location: actionsLocation, sender: nil) let actions = Action.sortedApplicableActions(for: actionContext) let contextualActions = actions.compactMap({ action in @@ -118,7 +118,7 @@ extension OCItem : DataItemContextMenuInteraction { } let item = self let actionsLocation = OCExtensionLocation(ofType: .action, identifier: location) // .contextMenuItem) - let actionContext = ActionContext(viewController: viewController, core: core, items: [item], location: actionsLocation, sender: nil) + let actionContext = ActionContext(viewController: viewController, clientContext: context, core: core, items: [item], location: actionsLocation, sender: nil) let actions = Action.sortedApplicableActions(for: actionContext) var actionMenuActions : [UIAction] = [] for action in actions { @@ -135,7 +135,7 @@ extension OCItem : DataItemContextMenuInteraction { // Share Items let sharingActionsLocation = OCExtensionLocation(ofType: .action, identifier: .contextMenuSharingItem) - let sharingActionContext = ActionContext(viewController: viewController, core: core, items: [item], location: sharingActionsLocation, sender: nil) + let sharingActionContext = ActionContext(viewController: viewController, clientContext: context, core: core, items: [item], location: sharingActionsLocation, sender: nil) let sharingActions = Action.sortedApplicableActions(for: sharingActionContext) for action in sharingActions { action.progressHandler = context?.actionProgressHandlerProvider?.makeActionProgressHandler() From d118d3909d3a776e2bf44ffee02f08ab9ca2a234 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Mon, 12 Sep 2022 11:14:01 +0200 Subject: [PATCH 058/328] [feature/graph-api] Spaces/Drives / Graph API support (WIP) (#1101) - adds support for spaces/drives / Graph API - new UICollectionView based user interface based on the new data sources --- .github/ISSUE_TEMPLATE/release_template.md | 1 + .../workflows/configuration-documentation.yml | 2 +- .xcode-version | 2 +- CHANGELOG.md | 226 ++ KNOWN_ISSUES.md | 72 + changelog/11.10.0_2022-05-18/1066 | 5 + changelog/11.10.0_2022-05-18/1114 | 5 + changelog/11.10.0_2022-05-18/1116 | 5 + changelog/11.10.0_2022-05-18/1119 | 5 + changelog/11.10.0_2022-05-18/1123 | 5 + changelog/11.10.1_2022-08-02/1129 | 5 + changelog/11.10.1_2022-08-02/1130 | 5 + changelog/11.10.1_2022-08-02/1132 | 5 + changelog/11.8.2_2022-01-17/4857 | 5 + changelog/11.8.2_2022-01-17/4924 | 5 + changelog/11.8.2_2022-01-17/4934 | 5 + changelog/11.9.0_2022-03-16/1004 | 5 + changelog/11.9.0_2022-03-16/1043 | 5 + changelog/11.9.0_2022-03-16/1059 | 5 + changelog/11.9.0_2022-03-16/1093 | 5 + changelog/11.9.0_2022-03-16/1105 | 5 + changelog/11.9.0_2022-03-16/950 | 5 + changelog/11.9.0_2022-03-16/972 | 5 + changelog/11.9.1_2022-03-29/1099 | 5 + changelog/11.9.1_2022-03-29/1112 | 5 + doc/BUILD_CUSTOMIZATION.md | 76 - doc/CONFIGURATION.json | 27 + doc/README.md | 14 + .../configuration.adoc | 21 + .../screenshots => doc/images}/Framefile.json | 0 .../screenshots => doc/images}/README.md | 0 .../screenshots => doc/images}/README.txt | 0 .../images}/ar/keyword.strings | Bin .../images}/ar/title.strings | Bin .../screenshots => doc/images}/background.jpg | Bin .../images}/ca/keyword.strings | Bin .../images}/ca/title.strings | Bin .../images}/de-DE/README.md | 0 .../images}/de-DE/keyword.strings | Bin .../images}/de-DE/title.strings | Bin .../images}/de/keyword.strings | Bin .../images}/de/title.strings | Bin .../images}/de_CH/keyword.strings | Bin .../images}/de_CH/title.strings | Bin .../images}/el/keyword.strings | Bin .../images}/el/title.strings | Bin .../images}/en-GB/keyword.strings | Bin .../images}/en-GB/title.strings | Bin ...neration)-10_ios_accounts_welcome_demo.png | Bin ... generation)-11_ios_accounts_list_demo.png | Bin ...2nd generation)-20_ios_files_list_demo.png | Bin ... generation)-21_ios_files_actions_demo.png | Bin ...eration)-22_ios_files_preview_pdf_demo.png | Bin ...s_files_list_multiple_window_landscape.png | Bin ...d generation)-40_ios_quick_access_demo.png | Bin ... (2nd generation)-60_ios_settings_demo.png | Bin ...neration)-10_ios_accounts_welcome_demo.png | Bin ...n)-10_ios_accounts_welcome_demo_framed.png | Bin ... generation)-11_ios_accounts_list_demo.png | Bin ...tion)-11_ios_accounts_list_demo_framed.png | Bin ...4th generation)-20_ios_files_list_demo.png | Bin ...eration)-20_ios_files_list_demo_framed.png | Bin ... generation)-21_ios_files_actions_demo.png | Bin ...tion)-21_ios_files_actions_demo_framed.png | Bin ...eration)-22_ios_files_preview_pdf_demo.png | Bin ...)-22_ios_files_preview_pdf_demo_framed.png | Bin ... (4th generation)-60_ios_settings_demo.png | Bin ...eneration)-60_ios_settings_demo_framed.png | Bin ...neration)-10_ios_accounts_welcome_demo.png | Bin ... generation)-11_ios_accounts_list_demo.png | Bin ...5th generation)-20_ios_files_list_demo.png | Bin ... generation)-21_ios_files_actions_demo.png | Bin ...eration)-22_ios_files_preview_pdf_demo.png | Bin ...s_files_list_multiple_window_landscape.png | Bin ...h generation)-40_ios_quick_access_demo.png | Bin ... (5th generation)-60_ios_settings_demo.png | Bin ...9.7-inch)-10_ios_accounts_welcome_demo.png | Bin ...h)-10_ios_accounts_welcome_demo_framed.png | Bin ...o (9.7-inch)-11_ios_accounts_list_demo.png | Bin ...inch)-11_ios_accounts_list_demo_framed.png | Bin ... Pro (9.7-inch)-20_ios_files_list_demo.png | Bin ....7-inch)-20_ios_files_list_demo_framed.png | Bin ...o (9.7-inch)-21_ios_files_actions_demo.png | Bin ...inch)-21_ios_files_actions_demo_framed.png | Bin ....7-inch)-22_ios_files_preview_pdf_demo.png | Bin ...)-22_ios_files_preview_pdf_demo_framed.png | Bin ...s_files_list_multiple_window_landscape.png | Bin ..._list_multiple_window_landscape_framed.png | Bin ...ro (9.7-inch)-40_ios_quick_access_demo.png | Bin ...-inch)-40_ios_quick_access_demo_framed.png | Bin ...ad Pro (9.7-inch)-60_ios_settings_demo.png | Bin ...(9.7-inch)-60_ios_settings_demo_framed.png | Bin ...1 Pro Max-10_ios_accounts_welcome_demo.png | Bin ...ax-10_ios_accounts_welcome_demo_framed.png | Bin ...e 11 Pro Max-11_ios_accounts_list_demo.png | Bin ...o Max-11_ios_accounts_list_demo_framed.png | Bin ...hone 11 Pro Max-20_ios_files_list_demo.png | Bin ... Pro Max-20_ios_files_list_demo_framed.png | Bin ...e 11 Pro Max-21_ios_files_actions_demo.png | Bin ...o Max-21_ios_files_actions_demo_framed.png | Bin ... Pro Max-22_ios_files_preview_pdf_demo.png | Bin ...x-22_ios_files_preview_pdf_demo_framed.png | Bin ...ne 11 Pro Max-40_ios_quick_access_demo.png | Bin ...ro Max-40_ios_quick_access_demo_framed.png | Bin ...iPhone 11 Pro Max-60_ios_settings_demo.png | Bin ...11 Pro Max-60_ios_settings_demo_framed.png | Bin ...iPhone 11-10_ios_accounts_welcome_demo.png | Bin ...11-10_ios_accounts_welcome_demo_framed.png | Bin .../iPhone 11-11_ios_accounts_list_demo.png | Bin ...ne 11-11_ios_accounts_list_demo_framed.png | Bin .../iPhone 11-20_ios_files_list_demo.png | Bin ...Phone 11-20_ios_files_list_demo_framed.png | Bin .../iPhone 11-21_ios_files_actions_demo.png | Bin ...ne 11-21_ios_files_actions_demo_framed.png | Bin ...Phone 11-22_ios_files_preview_pdf_demo.png | Bin ...1-22_ios_files_preview_pdf_demo_framed.png | Bin .../iPhone 11-40_ios_quick_access_demo.png | Bin ...one 11-40_ios_quick_access_demo_framed.png | Bin .../en-US/iPhone 11-60_ios_settings_demo.png | Bin .../iPhone 11-60_ios_settings_demo_framed.png | Bin ...ne 8 Plus-10_ios_accounts_welcome_demo.png | Bin ...us-10_ios_accounts_welcome_demo_framed.png | Bin ...Phone 8 Plus-11_ios_accounts_list_demo.png | Bin ... Plus-11_ios_accounts_list_demo_framed.png | Bin .../iPhone 8 Plus-20_ios_files_list_demo.png | Bin ...e 8 Plus-20_ios_files_list_demo_framed.png | Bin ...Phone 8 Plus-21_ios_files_actions_demo.png | Bin ... Plus-21_ios_files_actions_demo_framed.png | Bin ...e 8 Plus-22_ios_files_preview_pdf_demo.png | Bin ...s-22_ios_files_preview_pdf_demo_framed.png | Bin ...iPhone 8 Plus-40_ios_quick_access_demo.png | Bin ...8 Plus-40_ios_quick_access_demo_framed.png | Bin .../iPhone 8 Plus-60_ios_settings_demo.png | Bin ...one 8 Plus-60_ios_settings_demo_framed.png | Bin .../iPhone 8-10_ios_accounts_welcome_demo.png | Bin ... 8-10_ios_accounts_welcome_demo_framed.png | Bin .../iPhone 8-11_ios_accounts_list_demo.png | Bin ...one 8-11_ios_accounts_list_demo_framed.png | Bin .../en-US/iPhone 8-20_ios_files_list_demo.png | Bin ...iPhone 8-20_ios_files_list_demo_framed.png | Bin .../iPhone 8-21_ios_files_actions_demo.png | Bin ...one 8-21_ios_files_actions_demo_framed.png | Bin ...iPhone 8-22_ios_files_preview_pdf_demo.png | Bin ...8-22_ios_files_preview_pdf_demo_framed.png | Bin .../iPhone 8-40_ios_quick_access_demo.png | Bin ...hone 8-40_ios_quick_access_demo_framed.png | Bin .../en-US/iPhone 8-60_ios_settings_demo.png | Bin .../iPhone 8-60_ios_settings_demo_framed.png | Bin ...neration)-10_ios_accounts_welcome_demo.png | Bin ...n)-10_ios_accounts_welcome_demo_framed.png | Bin ... generation)-11_ios_accounts_list_demo.png | Bin ...tion)-11_ios_accounts_list_demo_framed.png | Bin ...2nd generation)-20_ios_files_list_demo.png | Bin ...eration)-20_ios_files_list_demo_framed.png | Bin ... generation)-21_ios_files_actions_demo.png | Bin ...tion)-21_ios_files_actions_demo_framed.png | Bin ...eration)-22_ios_files_preview_pdf_demo.png | Bin ...)-22_ios_files_preview_pdf_demo_framed.png | Bin ...d generation)-40_ios_quick_access_demo.png | Bin ...ation)-40_ios_quick_access_demo_framed.png | Bin ... (2nd generation)-60_ios_settings_demo.png | Bin ...eneration)-60_ios_settings_demo_framed.png | Bin .../images}/en-US/keyword.strings | 0 .../images}/en-US/title.strings | 0 .../images}/en/keyword.strings | 0 .../images}/en/title.strings | 0 .../screenshots => doc/images}/es/README.md | 0 .../images}/es/keyword.strings | Bin .../images}/es/title.strings | Bin .../images}/et_EE/title.strings | Bin .../images}/eu/keyword.strings | Bin .../images}/eu/title.strings | Bin .../images}/fonts/OpenSans-Regular.ttf | Bin .../images}/fonts/OpenSans-Semibold.ttf | Bin .../images}/fonts/README.md | 0 .../images}/fr/title.strings | Bin .../images}/gl/keyword.strings | Bin .../images}/gl/title.strings | Bin .../images}/he/keyword.strings | Bin .../images}/he/title.strings | Bin .../images}/id/keyword.strings | Bin .../images}/km/keyword.strings | Bin .../images}/lt_LT/keyword.strings | Bin .../images}/lv/title.strings | Bin .../images}/pl/keyword.strings | Bin .../images}/pl/title.strings | Bin .../images}/pt-BR/keyword.strings | Bin .../images}/pt-BR/title.strings | Bin .../images}/ru/keyword.strings | Bin .../images}/ru/title.strings | Bin .../images}/screenshots.html | 0 .../images}/sq/keyword.strings | Bin .../images}/sq/title.strings | Bin .../images}/th-TH/keyword.strings | Bin .../images}/th-TH/title.strings | Bin .../images}/tr/keyword.strings | Bin .../images}/tr/title.strings | Bin .../images}/ur_PK/keyword.strings | Bin .../images}/zh-Hans/keyword.strings | Bin .../images}/zh-Hans/title.strings | Bin .../images}/zh_TW/keyword.strings | Bin .../images}/zh_TW/title.strings | Bin docs/.gitignore | 4 - docs/README.md | 18 - docs/antora.yml | 6 - docs/bin/cli | 245 -- docs/books/ownCloud_iOS_App_Manual.adoc | 10 - docs/fonts/dejavu-sans-bold.ttf | Bin 705684 -> 0 bytes docs/fonts/dejavu-sans-boldoblique.ttf | Bin 643292 -> 0 bytes docs/fonts/dejavu-sans-condensed.ttf | Bin 680264 -> 0 bytes docs/fonts/dejavu-sans-condensedbold.ttf | Bin 665028 -> 0 bytes .../dejavu-sans-condensedboldoblique.ttf | Bin 611836 -> 0 bytes docs/fonts/dejavu-sans-condensedoblique.ttf | Bin 599292 -> 0 bytes docs/fonts/dejavu-sans-extralight.ttf | Bin 355380 -> 0 bytes docs/fonts/dejavu-sans-mono-bold.ttf | Bin 331992 -> 0 bytes docs/fonts/dejavu-sans-mono.ttf | Bin 340712 -> 0 bytes docs/fonts/dejavu-sans-monoboldoblique.ttf | Bin 253580 -> 0 bytes docs/fonts/dejavu-sans-monooblique.ttf | Bin 251932 -> 0 bytes docs/fonts/dejavu-sans-oblique.ttf | Bin 635416 -> 0 bytes docs/fonts/dejavu-sans.ttf | Bin 757076 -> 0 bytes docs/fonts/dejavuserif-bold.ttf | Bin 356088 -> 0 bytes docs/fonts/dejavuserif-bolditalic.ttf | Bin 347460 -> 0 bytes docs/fonts/dejavuserif-italic.ttf | Bin 345996 -> 0 bytes docs/fonts/dejavuserif.ttf | Bin 380132 -> 0 bytes docs/fonts/dejavuserifcondensed-bold.ttf | Bin 331244 -> 0 bytes .../fonts/dejavuserifcondensed-bolditalic.ttf | Bin 346508 -> 0 bytes docs/fonts/dejavuserifcondensed-italic.ttf | Bin 345324 -> 0 bytes docs/fonts/dejavuserifcondensed.ttf | Bin 346664 -> 0 bytes docs/fonts/notoserif-bold.ttf | Bin 370884 -> 0 bytes docs/fonts/notoserif-bolditalic.ttf | Bin 333656 -> 0 bytes docs/fonts/notoserif-italic.ttf | Bin 323188 -> 0 bytes docs/fonts/notoserif-regular.ttf | Bin 350668 -> 0 bytes docs/fonts/opensans-bold.ttf | Bin 224452 -> 0 bytes docs/fonts/opensans-bolditalic.ttf | Bin 213168 -> 0 bytes docs/fonts/opensans-extrabold.ttf | Bin 222424 -> 0 bytes docs/fonts/opensans-extrabolditalic.ttf | Bin 213336 -> 0 bytes docs/fonts/opensans-italic.ttf | Bin 212760 -> 0 bytes docs/fonts/opensans-light.ttf | Bin 222236 -> 0 bytes docs/fonts/opensans-lightitalic.ttf | Bin 213024 -> 0 bytes docs/fonts/opensans-regular.ttf | Bin 217276 -> 0 bytes docs/fonts/opensans-semibold.ttf | Bin 221164 -> 0 bytes docs/fonts/opensans-semibolditalic.ttf | Bin 212732 -> 0 bytes docs/generator/xref-validator.js | 93 - docs/modules/ROOT/assets/attachments/.gitkeep | 0 .../ROOT/assets/images/01_account_add.png | Bin 55303 -> 0 bytes .../ROOT/assets/images/02_basic_auth.png | Bin 85027 -> 0 bytes .../ROOT/assets/images/03_cert_pass.png | Bin 60613 -> 0 bytes .../ROOT/assets/images/04_Account_1x.png | Bin 30556 -> 0 bytes .../ROOT/assets/images/04_cert_error.png | Bin 32800 -> 0 bytes .../ROOT/assets/images/05_Account_swipe.png | Bin 38794 -> 0 bytes .../ROOT/assets/images/06_Account_manage.png | Bin 29617 -> 0 bytes .../ROOT/assets/images/07_Account_edit.png | Bin 51332 -> 0 bytes .../ROOT/assets/images/11_Account_OAuth.png | Bin 55157 -> 0 bytes .../ROOT/assets/images/12_OAuth_dialogue.png | Bin 39561 -> 0 bytes .../ROOT/assets/images/13_OAuth_Web_view.png | Bin 302833 -> 0 bytes .../images/14_OAuth_Web_view_authorize.png | Bin 415795 -> 0 bytes .../ROOT/assets/images/21_File_list.png | Bin 96756 -> 0 bytes .../assets/images/21_File_list_annotated.png | Bin 100164 -> 0 bytes .../assets/images/21_File_list_parent.jpg | Bin 105053 -> 0 bytes .../assets/images/21_File_list_parent.png | Bin 103215 -> 0 bytes .../ROOT/assets/images/22_File_plus.png | Bin 80360 -> 0 bytes .../ROOT/assets/images/23_Upload_File.png | Bin 97527 -> 0 bytes .../assets/images/24_Upload_Photo_multi.png | Bin 649574 -> 0 bytes .../assets/images/25_Files_multiselect.png | Bin 113988 -> 0 bytes .../assets/images/26_Files_multidragdrop.png | Bin 72278 -> 0 bytes .../images/26_Files_multidragdrop_iPad.png | Bin 657667 -> 0 bytes .../ROOT/assets/images/27_File_Actions.png | Bin 55188 -> 0 bytes .../assets/images/28_File_Actions_open.png | Bin 139128 -> 0 bytes docs/modules/ROOT/assets/images/31_Collab.png | Bin 49414 -> 0 bytes docs/modules/ROOT/assets/images/36_Links.png | Bin 71934 -> 0 bytes docs/modules/ROOT/assets/images/41_PDF.png | Bin 146831 -> 0 bytes .../modules/ROOT/assets/images/42_PDF_toc.png | Bin 92640 -> 0 bytes .../ROOT/assets/images/43_PDF_thumbs.png | Bin 251187 -> 0 bytes .../ROOT/assets/images/44_PDF_search.png | Bin 83508 -> 0 bytes .../ROOT/assets/images/51_Quick_access.png | Bin 47155 -> 0 bytes .../ROOT/assets/images/81_Settings-2.png | Bin 76469 -> 0 bytes .../ROOT/assets/images/81_Settings.png | Bin 76950 -> 0 bytes .../assets/images/82_Settings_passcode.png | Bin 48977 -> 0 bytes .../ROOT/assets/images/83_Settings_certs.png | Bin 31238 -> 0 bytes .../ROOT/assets/images/84_Settings_themes.png | Bin 25650 -> 0 bytes .../modules/ROOT/assets/images/91_Logging.png | Bin 71302 -> 0 bytes ...-account-certificate-passed-validation.png | Bin 57555 -> 0 bytes .../assets/images/add-account-step-four.png | Bin 171712 -> 0 bytes .../assets/images/add-account-step-one.png | Bin 67694 -> 0 bytes .../assets/images/add-account-step-three.png | Bin 148523 -> 0 bytes .../assets/images/add-account-step-two.png | Bin 67424 -> 0 bytes .../assets/images/app-settings-dark-theme.PNG | Bin 63233 -> 0 bytes .../ROOT/assets/images/app-settings.PNG | Bin 44849 -> 0 bytes ...access-with-passcode-or-biometric-data.png | Bin 39208 -> 0 bytes .../images/browse-quickaccess-status-bar.png | Bin 11280 -> 0 bytes .../images/confirm-account-deletion.png | Bin 24141 -> 0 bytes .../ROOT/assets/images/create-new-folder.png | Bin 63430 -> 0 bytes .../create-public-link-copy-private-link.png | Bin 33107 -> 0 bytes .../ROOT/assets/images/delete-files.png | Bin 55681 -> 0 bytes .../assets/images/delete-offline-copies.png | Bin 19580 -> 0 bytes .../ROOT/assets/images/delete-public-link.png | Bin 11898 -> 0 bytes .../ROOT/assets/images/directory-actions.png | Bin 120269 -> 0 bytes .../edit-oauth2-authenticated-account.png | Bin 67589 -> 0 bytes .../assets/images/edit-or-delete-account.png | Bin 33187 -> 0 bytes .../ROOT/assets/images/enable-location.png | Bin 56884 -> 0 bytes .../assets/images/file-actions-dialog.png | Bin 48374 -> 0 bytes .../file-actions-multiple-files-selected.png | Bin 49109 -> 0 bytes .../ROOT/assets/images/file-actions.jpg | Bin 64816 -> 0 bytes .../file-with-same-name-already-exists.png | Bin 101630 -> 0 bytes .../ROOT/assets/images/folder-actions.png | Bin 113666 -> 0 bytes .../images/grant-ios-app-access-to-photos.png | Bin 47325 -> 0 bytes .../ROOT/assets/images/icon-download.png | Bin 2765 -> 0 bytes .../images/icon-not-available-locally.png | Bin 2078 -> 0 bytes .../assets/images/index-bar-with-callout.png | Bin 58119 -> 0 bytes docs/modules/ROOT/assets/images/index-bar.png | Bin 146383 -> 0 bytes .../images/ios-app-settings-logging.png | Bin 277591 -> 0 bytes .../manage-public-link-for-a-directory.png | Bin 36028 -> 0 bytes .../images/manage-public-link-settings.png | Bin 37844 -> 0 bytes .../assets/images/masking-private-data.png | Bin 41849 -> 0 bytes .../notification-no-internet-connection.png | Bin 15716 -> 0 bytes .../all-available-offline-items.png | Bin 125482 -> 0 bytes ...ble-offline-badge-closeup-with-callout.png | Bin 191801 -> 0 bytes .../available-offline-badge-closeup.png | Bin 31355 -> 0 bytes .../available-offline-badge.png | Bin 163927 -> 0 bytes .../available-offline-items.png | Bin 55828 -> 0 bytes .../available-offline-logo.png | Bin 983 -> 0 bytes .../file-available-offline.png | Bin 31257 -> 0 bytes .../images/offline-storage/folder-action.png | Bin 155874 -> 0 bytes .../make-available-offline.png | Bin 32107 -> 0 bytes .../make-unavailable-offline.png | Bin 30530 -> 0 bytes .../not-available-offline-logo.png | Bin 811 -> 0 bytes .../offline-storage-settings.png | Bin 70090 -> 0 bytes .../one-folder-available-offline.png | Bin 33819 -> 0 bytes ...-access-view-nothing-available-offline.png | Bin 76308 -> 0 bytes .../offline-storage/quick-access-view-toc.png | Bin 76591 -> 0 bytes .../images/offline-storage/regular-badges.png | Bin 127263 -> 0 bytes .../images/offline-storage/settings.png | Bin 76438 -> 0 bytes .../images/owncloud-log-configuration.png | Bin 6638 -> 0 bytes .../images/owncloud-server-mobile-apps.png | Bin 16809 -> 0 bytes docs/modules/ROOT/assets/images/pin-lock.png | Bin 35468 -> 0 bytes .../public-link-set-expiration-date.png | Bin 12154 -> 0 bytes .../images/public-link-set-password.png | Bin 9720 -> 0 bytes .../images/quick-access-public-links.png | Bin 29326 -> 0 bytes .../images/quick-access-view-annotated.png | Bin 42850 -> 0 bytes .../ROOT/assets/images/quick-access-view.png | Bin 64756 -> 0 bytes .../review-ssl-certificate-connection.png | Bin 53031 -> 0 bytes .../images/selecting-multiple-files.PNG | Bin 77838 -> 0 bytes .../assets/images/settings-light-theme.png | Bin 40867 -> 0 bytes .../settings-lock-application-duration.png | Bin 28569 -> 0 bytes .../assets/images/settings-media-upload.png | Bin 23314 -> 0 bytes .../settings-security-passcode-enabled.png | Bin 33952 -> 0 bytes .../ROOT/assets/images/settings-theme.PNG | Bin 25320 -> 0 bytes .../assets/images/share-file-with-user.png | Bin 93747 -> 0 bytes .../modules/ROOT/assets/images/share-file.png | Bin 35419 -> 0 bytes .../share-files-from-other-apps-step-1.png | Bin 272449 -> 0 bytes .../share-files-from-other-apps-step-2.png | Bin 57373 -> 0 bytes .../images/sort-files-landscape-mode.png | Bin 38798 -> 0 bytes .../images/sort-files-portrait-mode.png | Bin 81529 -> 0 bytes .../ROOT/assets/images/sorting-files.png | Bin 63305 -> 0 bytes .../images/swipe-and-delete-public-link.png | Bin 35657 -> 0 bytes .../assets/images/themes-ownCloud-Classic.png | Bin 43904 -> 0 bytes .../assets/images/themes-ownCloud-Dark.png | Bin 60145 -> 0 bytes .../assets/images/themes-ownCloud-Light.png | Bin 38757 -> 0 bytes .../ROOT/assets/images/themes/classic.png | Bin 70728 -> 0 bytes .../ROOT/assets/images/themes/dark.png | Bin 92536 -> 0 bytes .../ROOT/assets/images/themes/light.png | Bin 63296 -> 0 bytes ...r-accounts-list-annotated-with-callout.png | Bin 53346 -> 0 bytes .../images/user-accounts-list-annotated.png | Bin 30864 -> 0 bytes .../ROOT/assets/images/user-accounts-list.png | Bin 31734 -> 0 bytes .../images/view-certificate-details.png | Bin 56799 -> 0 bytes .../ROOT/assets/images/view-file-image.png | Bin 212011 -> 0 bytes .../ROOT/assets/images/view-file-odt.png | Bin 18562 -> 0 bytes .../ROOT/assets/images/view-file-pdf.png | Bin 35549 -> 0 bytes .../assets/images/view-file-text-file.png | Bin 16628 -> 0 bytes .../ROOT/assets/images/view-file-video.png | Bin 198384 -> 0 bytes .../assets/images/view-links-for-file.png | Bin 28624 -> 0 bytes .../ROOT/assets/images/viewing-a-file.png | Bin 35632 -> 0 bytes .../assets/images/viewing-an-empty-folder.png | Bin 42600 -> 0 bytes .../images/viewing-files-classic-theme.PNG | Bin 48546 -> 0 bytes .../images/viewing-files-light-theme.PNG | Bin 43344 -> 0 bytes .../ROOT/assets/images/welcome-screen.png | Bin 42036 -> 0 bytes docs/modules/ROOT/examples/.gitkeep | 0 docs/modules/ROOT/nav.adoc | 13 - docs/modules/ROOT/pages/index.adoc | 11 - docs/modules/ROOT/pages/ios_accounts.adoc | 96 - .../ROOT/pages/ios_available_offline.adoc | 63 - .../modules/ROOT/pages/ios_collaboration.adoc | 80 - docs/modules/ROOT/pages/ios_faq.adoc | 52 - docs/modules/ROOT/pages/ios_files.adoc | 199 -- .../ROOT/pages/ios_files_integration.adoc | 39 - docs/modules/ROOT/pages/ios_installation.adoc | 11 - docs/modules/ROOT/pages/ios_mdm.adoc | 134 - docs/modules/ROOT/pages/ios_quick_access.adoc | 39 - docs/modules/ROOT/pages/ios_security.adoc | 158 -- docs/modules/ROOT/pages/ios_settings.adoc | 102 - .../ROOT/pages/ios_task_scheduling.adoc | 36 - .../ROOT/pages/ios_troubleshooting.adoc | 114 - docs/package.json | 53 - docs/resources/themes/custom-theme.yml | 257 -- docs/resources/themes/owncloud-theme.yml | 284 -- docs/site.yml | 24 - docs/yarn-error.log | 2275 ----------------- docs/yarn.lock | 2215 ---------------- enterprise/resign/README.md | 2 +- .../libzip/libzip.xcodeproj/project.pbxproj | 2 +- fastlane/Fastfile | 36 +- fastlane/README.md | 123 +- fastlane/metadata-emm/en-US/release_notes.txt | 12 +- .../en-US/release_notes.txt | 12 +- fastlane/metadata/en-US/release_notes.txt | 12 +- ios-sdk | 2 +- .../DocumentActionViewController.swift | 1 + .../FileProviderContentEnumerator.h | 42 + .../FileProviderContentEnumerator.m | 687 +++++ .../FileProviderEnumerator.m | 12 +- .../FileProviderEnumeratorObserver.h | 4 + .../FileProviderEnumeratorObserver.m | 16 + .../FileProviderExtension.h | 1 + .../FileProviderExtension.m | 199 +- .../OCItem+FileProviderItem.m | 54 +- .../OCVFSNode+FileProviderItem.h | 27 + .../OCVFSNode+FileProviderItem.m | 83 + .../Base.lproj/Intents.intentdefinition | 10 +- .../CreateFolderIntentHandler.swift | 4 +- .../DeletePathItemIntentHandler.swift | 2 +- .../GetDirectoryListingIntentHandler.swift | 2 +- .../GetFileInfoIntentHandler.swift | 2 +- ownCloud Intents/GetFileIntentHandler.swift | 2 +- .../PathExistsIntentHandler.swift | 2 +- ownCloud Intents/SaveFileIntentHandler.swift | 4 +- .../ShareViewController.swift | 21 +- ownCloud.xcodeproj/project.pbxproj | 397 ++- .../xcshareddata/xcschemes/MakeTVG.xcscheme | 2 +- .../xcschemes/ownCloud File Provider.xcscheme | 20 +- .../ownCloud File ProviderUI.xcscheme | 2 +- .../xcschemes/ownCloud Intents.xcscheme | 2 +- .../ownCloud Share Extension.xcscheme | 2 +- .../xcshareddata/xcschemes/ownCloud.xcscheme | 34 +- .../xcschemes/ownCloudApp.xcscheme | 2 +- .../ownCloudScreenshotsTests.xcscheme | 2 +- .../xcschemes/ownCloudTests.xcscheme | 2 +- .../xcshareddata/swiftpm/Package.resolved | 51 +- ownCloud/AppDelegate.swift | 2 + .../Bookmarks/BookmarkViewController.swift | 197 +- .../CollaborateAction.swift | 2 +- .../Actions+Extensions/CopyAction.swift | 14 +- .../Actions+Extensions/CutAction.swift | 8 +- .../Actions+Extensions/DeleteAction.swift | 8 +- .../DisplayExifMetadataAction.swift | 5 +- .../DocumentEditingAction.swift | 9 +- .../Actions+Extensions/DuplicateAction.swift | 12 +- .../Actions+Extensions/FavoriteAction.swift | 6 +- .../ImportPasteboardAction.swift | 33 +- .../Actions+Extensions/LinksAction.swift | 2 +- .../MakeAvailableOfflineAction.swift | 6 +- .../MakeUnavailableOfflineAction.swift | 10 +- .../Actions+Extensions/MoveAction.swift | 10 +- .../Actions+Extensions/OpenInAction.swift | 8 +- .../PDFGotoPageAction.swift | 6 +- .../PresentationModeAction.swift | 6 +- .../Actions+Extensions/RenameAction.swift | 6 +- .../Actions+Extensions/UnfavoriteAction.swift | 6 +- .../Actions+Extensions/UnshareAction.swift | 8 +- .../UploadCameraMediaAction.swift | 12 +- .../Actions+Extensions/UploadFileAction.swift | 13 +- .../UploadMediaAction.swift | 14 +- .../Actions/EditDocumentViewController.swift | 38 +- .../Client/Actions/Scanner/ScanAction.swift | 13 +- ownCloud/Client/ClientActivityCell.swift | 2 +- ...ClientRootViewController+ItemActions.swift | 90 + .../Client/ClientRootViewController.swift | 58 +- ownCloud/Client/ClientSessionManager.swift | 2 +- ...yViewController+InlineMessageSupport.swift | 2 +- ...ntroller+OpenItemTableViewController.swift | 4 +- .../Item Policies/ItemPolicyCell.swift | 12 +- .../ItemPolicyTableViewController.swift | 4 +- .../Library/LibraryTableViewController.swift | 6 +- .../Client/PhotoSelectionViewController.swift | 3 +- .../PendingSharesTableViewController.swift | 12 +- ownCloud/Client/Viewer/DisplayExtension.swift | 7 +- .../Viewer/DisplayHostViewController.swift | 15 +- .../Client/Viewer/DisplayViewController.swift | 60 +- .../Media/MediaDisplayViewController.swift | 11 +- .../Viewer/PDF/PDFSearchResultsView.swift | 2 +- .../WebViewDisplayViewController.swift | 2 - .../FileProviderInterfaceManager.swift | 2 +- ownCloud/Import/ImportFilesController.swift | 2 +- ownCloud/Key Commands/KeyCommands.swift | 4 +- .../Media Uploads/MediaUploadOperation.swift | 23 +- ownCloud/Media Uploads/MediaUploadQueue.swift | 8 +- .../Media Uploads/MediaUploadStorage.swift | 28 +- .../PhotoKit Extensions/PHAsset+Upload.swift | 9 +- ownCloud/Messages/AlertView.swift | 9 +- ownCloud/Messages/MessageGroupCell.swift | 21 +- ownCloud/Migration/Migration.swift | 8 +- ownCloud/Release Notes/ReleaseNotes.plist | 138 + .../Resources/Assets.xcassets/Contents.json | 6 +- .../biometrical-faceid.imageset/Contents.json | 21 + .../biometrical-faceid.pdf | Bin 0 -> 4657 bytes .../Contents.json | 24 + .../biometrical.pdf | Bin 0 -> 5494 bytes .../Resources/ar.lproj/Localizable.strings | Bin 79172 -> 82040 bytes .../Resources/de.lproj/Localizable.strings | Bin 88600 -> 91618 bytes ownCloud/Resources/el.lproj/InfoPlist.strings | Bin 2182 -> 3158 bytes .../Resources/en.lproj/Localizable.strings | 8 +- .../Resources/es.lproj/Localizable.strings | Bin 87284 -> 90022 bytes .../Resources/fr.lproj/Localizable.strings | Bin 90554 -> 93460 bytes .../Resources/gl.lproj/Localizable.strings | Bin 88202 -> 90996 bytes .../Resources/he.lproj/Localizable.strings | Bin 76660 -> 79602 bytes .../Resources/pt-BR.lproj/Localizable.strings | Bin 87038 -> 90066 bytes .../Resources/ru.lproj/Localizable.strings | Bin 87952 -> 90686 bytes .../Resources/sq.lproj/Localizable.strings | Bin 86662 -> 89620 bytes .../Resources/th-TH.lproj/Localizable.strings | Bin 79206 -> 82020 bytes .../Resources/zh_TW.lproj/Localizable.strings | Bin 64618 -> 65414 bytes .../OCCertificate+Extension.swift | 44 +- .../ServerListTableViewController.swift | 10 +- .../Settings/AutoUploadSettingsSection.swift | 6 +- ownCloud/Settings/DataSettingsSection.swift | 2 +- .../Settings/LogFilesViewController.swift | 3 +- .../StaticLoginSetupViewController.swift | 83 +- ...ingleAccountServerListViewController.swift | 6 +- .../Interface/StaticLoginViewController.swift | 3 +- .../InstantMediaUploadTaskExtension.swift | 12 +- ownCloud/Tools/PasswordManagerAccess.swift | 5 +- ownCloud/Tools/URL+Extensions.swift | 2 +- ownCloud/UI Elements/RoundedInfoView.swift | 9 +- .../OCResourceText+ViewProvider.swift | 104 + ownCloudAppFramework/Building/BuildOptions.m | 24 +- .../Licensing/OCLicenseTypes.h | 4 +- .../Licensing/Offer/OCLicenseOffer.h | 2 +- .../Providers/EMM/OCLicenseEMMProvider.m | 22 +- .../Notifications/NotificationManager.m | 2 +- ownCloudAppFramework/VFS/OCVault+VFSManager.h | 27 + ownCloudAppFramework/VFS/OCVault+VFSManager.m | 139 + ownCloudAppFramework/VFS/VFSManager.h | 33 + ownCloudAppFramework/VFS/VFSManager.m | 182 ++ .../View Providers/OCImage+ViewProvider.m | 13 +- .../View Providers/OCViewHost.h | 8 + .../View Providers/OCViewHost.m | 19 + ownCloudAppFramework/ownCloudApp.h | 2 + .../AppLock/AppLockManager.swift | 29 +- .../AppLock/PasscodeSetupCoordinator.swift | 4 +- .../AppLock/PasscodeViewController.swift | 37 +- .../AppLock/PasscodeViewController.xib | 34 +- ownCloudAppShared/Client/Actions/Action.swift | 39 +- .../Actions/ClientItemResolvingCell.swift | 8 +- .../Client/Actions/CreateFolderAction.swift | 12 +- .../Collection Views/Cells/ActionCell.swift | 199 ++ .../Cells/DriveHeaderCell.swift | 126 + .../Cells/DriveListCell.swift | 195 ++ .../Cells/ExpandableResourceCell.swift | 173 ++ .../Collection Views/Cells/ItemListCell.swift | 647 +++++ .../Cells/ThemeableCollectionViewCell.swift | 70 + .../ThemeableCollectionViewListCell.swift | 70 + .../Collection Views/Cells/ViewCell.swift | 48 + .../CollectionViewCellConfiguration.swift | 122 + ...CellProvider+StandardImplementations.swift | 113 + .../CollectionViewCellProvider.swift | 68 + .../CollectionViewController.swift | 720 ++++++ .../CollectionViewSection.swift | 294 +++ .../ClientItemViewController.swift | 729 ++++++ .../Client/Context/ClientContext.swift | 262 ++ .../OCAction+Interactions.swift | 46 + .../OCDataItem+InteractionProtocols.swift | 58 + .../OCDrive+Interactions.swift | 42 + .../OCItem+Interactions.swift | 378 +++ .../ClientDirectoryPickerViewController.swift | 77 +- .../ClientQueryViewController.swift | 69 +- .../ClientSpacesTableViewController.swift | 85 + .../FileListTableViewController.swift | 19 +- .../QueryFileListTableViewController.swift | 18 +- .../ResourceSourceItemIcons.swift | 2 +- .../Client/Search/Scopes/SearchScope.swift | 251 ++ .../Client/Search/SearchViewController.swift | 234 ++ .../GroupSharingEditTableViewController.swift | 2 +- .../GroupSharingTableViewController.swift | 6 +- .../PublicLinkEditTableViewController.swift | 2 +- .../PublicLinkTableViewController.swift | 19 +- .../Client/Sharing/ShareClientItemCell.swift | 4 +- .../BreadCrumbTableViewController.swift | 7 +- .../User Interface/ClientItemCell.swift | 47 +- .../Client/User Interface/GradientView.swift | 87 + .../Client/User Interface/MessageView.swift | 2 +- .../Client/User Interface/SortBar.swift | 177 +- .../Client/User Interface/SortMethod.swift | 17 +- .../SortMethodTableViewController.swift | 67 - ownCloudAppShared/Down.LICENSE | 218 ++ .../String+Extension.swift | 4 +- .../SDK Extensions/OCCore+Extension.swift | 6 +- .../SDK Extensions/OCItem+Extension.swift | 14 +- .../SDK Extensions/OCItemTracker.swift | 14 +- ownCloudAppShared/Tools/AppStatistics.swift | 6 +- ownCloudAppShared/Tools/VendorServices.swift | 4 +- .../UIKit Extension/LAContext+Extension.swift | 36 +- ...MutableAttributedString+AppendStyled.swift | 42 + .../UIAlertController+OCIssue.swift | 10 + ...llectionViewDiffableDataSource+Tools.swift | 27 + .../UIKit Extension/UIFont+Weight.swift | 29 + .../UIKit Extension/UILabel+Extension.swift | 28 + .../UIKit Extension/UIView+OCDataItem.swift | 34 + .../Cursor Support/PointerEffect.swift | 2 +- .../More/MoreStaticTableViewController.swift | 4 +- .../ProgressIndicatorViewController.swift | 91 +- .../Progress/ProgressSummarizer.swift | 10 +- .../StaticTableViewController.swift | 32 +- .../StaticTableView/StaticTableViewRow.swift | 2 +- .../StaticTableViewSection.swift | 4 +- .../Theme/NSObject+ThemeApplication.swift | 54 +- .../Theme/Resources/ThemeImage.swift | 2 +- .../User Interface/Theme/Theme.swift | 32 +- .../Theme/ThemeCollection.swift | 7 +- .../Theme/UI/ThemeNavigationController.swift | 2 +- .../Theme/UI/ThemeTableViewCell.swift | 10 +- .../User Interface/Theme/UI/ThemeWindow.swift | 16 + .../User Interface/UserInterfaceContext.swift | 2 +- .../File List/Subclasses/MockOCCore.swift | 2 +- .../File List/Subclasses/MockOCQuery.swift | 2 +- .../Metadata/MetadataDocumentationTests.swift | 20 +- tools/GenerateDocs/generate_docs.sh | 2 +- ...bles.adoc.tmpl => configuration.adoc.tmpl} | 0 .../gomplate/Branding.plist.tmpl | 0 616 files changed, 9387 insertions(+), 7983 deletions(-) create mode 100644 KNOWN_ISSUES.md create mode 100644 changelog/11.10.0_2022-05-18/1066 create mode 100644 changelog/11.10.0_2022-05-18/1114 create mode 100644 changelog/11.10.0_2022-05-18/1116 create mode 100644 changelog/11.10.0_2022-05-18/1119 create mode 100644 changelog/11.10.0_2022-05-18/1123 create mode 100644 changelog/11.10.1_2022-08-02/1129 create mode 100644 changelog/11.10.1_2022-08-02/1130 create mode 100644 changelog/11.10.1_2022-08-02/1132 create mode 100644 changelog/11.8.2_2022-01-17/4857 create mode 100644 changelog/11.8.2_2022-01-17/4924 create mode 100644 changelog/11.8.2_2022-01-17/4934 create mode 100644 changelog/11.9.0_2022-03-16/1004 create mode 100644 changelog/11.9.0_2022-03-16/1043 create mode 100644 changelog/11.9.0_2022-03-16/1059 create mode 100644 changelog/11.9.0_2022-03-16/1093 create mode 100644 changelog/11.9.0_2022-03-16/1105 create mode 100644 changelog/11.9.0_2022-03-16/950 create mode 100644 changelog/11.9.0_2022-03-16/972 create mode 100644 changelog/11.9.1_2022-03-29/1099 create mode 100644 changelog/11.9.1_2022-03-29/1112 delete mode 100644 doc/BUILD_CUSTOMIZATION.md create mode 100644 doc/README.md rename docs/modules/ROOT/pages/ios_mdm_tables.adoc => doc/configuration.adoc (98%) rename {fastlane/screenshots => doc/images}/Framefile.json (100%) rename {fastlane/screenshots => doc/images}/README.md (100%) rename {fastlane/screenshots => doc/images}/README.txt (100%) rename {fastlane/screenshots => doc/images}/ar/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/ar/title.strings (100%) rename {fastlane/screenshots => doc/images}/background.jpg (100%) rename {fastlane/screenshots => doc/images}/ca/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/ca/title.strings (100%) rename {fastlane/screenshots => doc/images}/de-DE/README.md (100%) rename {fastlane/screenshots => doc/images}/de-DE/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/de-DE/title.strings (100%) rename {fastlane/screenshots => doc/images}/de/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/de/title.strings (100%) rename {fastlane/screenshots => doc/images}/de_CH/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/de_CH/title.strings (100%) rename {fastlane/screenshots => doc/images}/el/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/el/title.strings (100%) rename {fastlane/screenshots => doc/images}/en-GB/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/en-GB/title.strings (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (2nd generation)-10_ios_accounts_welcome_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (2nd generation)-11_ios_accounts_list_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (2nd generation)-20_ios_files_list_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (2nd generation)-21_ios_files_actions_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (2nd generation)-22_ios_files_preview_pdf_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (2nd generation)-23_ios_files_list_multiple_window_landscape.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (2nd generation)-40_ios_quick_access_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (2nd generation)-60_ios_settings_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (4th generation)-10_ios_accounts_welcome_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (4th generation)-10_ios_accounts_welcome_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (4th generation)-11_ios_accounts_list_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (4th generation)-11_ios_accounts_list_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (4th generation)-20_ios_files_list_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (4th generation)-20_ios_files_list_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (4th generation)-21_ios_files_actions_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (4th generation)-21_ios_files_actions_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (4th generation)-22_ios_files_preview_pdf_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (4th generation)-22_ios_files_preview_pdf_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (4th generation)-60_ios_settings_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (4th generation)-60_ios_settings_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (5th generation)-10_ios_accounts_welcome_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (5th generation)-11_ios_accounts_list_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (5th generation)-20_ios_files_list_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (5th generation)-21_ios_files_actions_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (5th generation)-22_ios_files_preview_pdf_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (5th generation)-23_ios_files_list_multiple_window_landscape.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (5th generation)-40_ios_quick_access_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (12.9-inch) (5th generation)-60_ios_settings_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (9.7-inch)-10_ios_accounts_welcome_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (9.7-inch)-10_ios_accounts_welcome_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (9.7-inch)-11_ios_accounts_list_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (9.7-inch)-11_ios_accounts_list_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (9.7-inch)-20_ios_files_list_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (9.7-inch)-20_ios_files_list_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (9.7-inch)-21_ios_files_actions_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (9.7-inch)-21_ios_files_actions_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (9.7-inch)-22_ios_files_preview_pdf_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (9.7-inch)-22_ios_files_preview_pdf_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (9.7-inch)-23_ios_files_list_multiple_window_landscape.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (9.7-inch)-23_ios_files_list_multiple_window_landscape_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (9.7-inch)-40_ios_quick_access_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (9.7-inch)-40_ios_quick_access_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (9.7-inch)-60_ios_settings_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPad Pro (9.7-inch)-60_ios_settings_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11 Pro Max-10_ios_accounts_welcome_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11 Pro Max-10_ios_accounts_welcome_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11 Pro Max-11_ios_accounts_list_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11 Pro Max-11_ios_accounts_list_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11 Pro Max-20_ios_files_list_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11 Pro Max-20_ios_files_list_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11 Pro Max-21_ios_files_actions_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11 Pro Max-21_ios_files_actions_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11 Pro Max-22_ios_files_preview_pdf_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11 Pro Max-22_ios_files_preview_pdf_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11 Pro Max-40_ios_quick_access_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11 Pro Max-40_ios_quick_access_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11 Pro Max-60_ios_settings_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11 Pro Max-60_ios_settings_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11-10_ios_accounts_welcome_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11-10_ios_accounts_welcome_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11-11_ios_accounts_list_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11-11_ios_accounts_list_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11-20_ios_files_list_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11-20_ios_files_list_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11-21_ios_files_actions_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11-21_ios_files_actions_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11-22_ios_files_preview_pdf_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11-22_ios_files_preview_pdf_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11-40_ios_quick_access_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11-40_ios_quick_access_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11-60_ios_settings_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 11-60_ios_settings_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8 Plus-10_ios_accounts_welcome_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8 Plus-10_ios_accounts_welcome_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8 Plus-11_ios_accounts_list_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8 Plus-11_ios_accounts_list_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8 Plus-20_ios_files_list_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8 Plus-20_ios_files_list_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8 Plus-21_ios_files_actions_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8 Plus-21_ios_files_actions_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8 Plus-22_ios_files_preview_pdf_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8 Plus-22_ios_files_preview_pdf_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8 Plus-40_ios_quick_access_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8 Plus-40_ios_quick_access_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8 Plus-60_ios_settings_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8 Plus-60_ios_settings_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8-10_ios_accounts_welcome_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8-10_ios_accounts_welcome_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8-11_ios_accounts_list_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8-11_ios_accounts_list_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8-20_ios_files_list_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8-20_ios_files_list_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8-21_ios_files_actions_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8-21_ios_files_actions_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8-22_ios_files_preview_pdf_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8-22_ios_files_preview_pdf_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8-40_ios_quick_access_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8-40_ios_quick_access_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8-60_ios_settings_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone 8-60_ios_settings_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone SE (2nd generation)-10_ios_accounts_welcome_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone SE (2nd generation)-10_ios_accounts_welcome_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone SE (2nd generation)-11_ios_accounts_list_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone SE (2nd generation)-11_ios_accounts_list_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone SE (2nd generation)-20_ios_files_list_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone SE (2nd generation)-20_ios_files_list_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone SE (2nd generation)-21_ios_files_actions_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone SE (2nd generation)-21_ios_files_actions_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone SE (2nd generation)-22_ios_files_preview_pdf_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone SE (2nd generation)-22_ios_files_preview_pdf_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone SE (2nd generation)-40_ios_quick_access_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone SE (2nd generation)-40_ios_quick_access_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone SE (2nd generation)-60_ios_settings_demo.png (100%) rename {fastlane/screenshots => doc/images}/en-US/iPhone SE (2nd generation)-60_ios_settings_demo_framed.png (100%) rename {fastlane/screenshots => doc/images}/en-US/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/en-US/title.strings (100%) rename {fastlane/screenshots => doc/images}/en/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/en/title.strings (100%) rename {fastlane/screenshots => doc/images}/es/README.md (100%) rename {fastlane/screenshots => doc/images}/es/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/es/title.strings (100%) rename {fastlane/screenshots => doc/images}/et_EE/title.strings (100%) rename {fastlane/screenshots => doc/images}/eu/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/eu/title.strings (100%) rename {fastlane/screenshots => doc/images}/fonts/OpenSans-Regular.ttf (100%) rename {fastlane/screenshots => doc/images}/fonts/OpenSans-Semibold.ttf (100%) rename {fastlane/screenshots => doc/images}/fonts/README.md (100%) rename {fastlane/screenshots => doc/images}/fr/title.strings (100%) rename {fastlane/screenshots => doc/images}/gl/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/gl/title.strings (100%) rename {fastlane/screenshots => doc/images}/he/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/he/title.strings (100%) rename {fastlane/screenshots => doc/images}/id/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/km/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/lt_LT/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/lv/title.strings (100%) rename {fastlane/screenshots => doc/images}/pl/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/pl/title.strings (100%) rename {fastlane/screenshots => doc/images}/pt-BR/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/pt-BR/title.strings (100%) rename {fastlane/screenshots => doc/images}/ru/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/ru/title.strings (100%) rename {fastlane/screenshots => doc/images}/screenshots.html (100%) rename {fastlane/screenshots => doc/images}/sq/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/sq/title.strings (100%) rename {fastlane/screenshots => doc/images}/th-TH/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/th-TH/title.strings (100%) rename {fastlane/screenshots => doc/images}/tr/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/tr/title.strings (100%) rename {fastlane/screenshots => doc/images}/ur_PK/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/zh-Hans/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/zh-Hans/title.strings (100%) rename {fastlane/screenshots => doc/images}/zh_TW/keyword.strings (100%) rename {fastlane/screenshots => doc/images}/zh_TW/title.strings (100%) delete mode 100644 docs/.gitignore delete mode 100644 docs/README.md delete mode 100644 docs/antora.yml delete mode 100755 docs/bin/cli delete mode 100644 docs/books/ownCloud_iOS_App_Manual.adoc delete mode 100644 docs/fonts/dejavu-sans-bold.ttf delete mode 100644 docs/fonts/dejavu-sans-boldoblique.ttf delete mode 100644 docs/fonts/dejavu-sans-condensed.ttf delete mode 100644 docs/fonts/dejavu-sans-condensedbold.ttf delete mode 100644 docs/fonts/dejavu-sans-condensedboldoblique.ttf delete mode 100644 docs/fonts/dejavu-sans-condensedoblique.ttf delete mode 100644 docs/fonts/dejavu-sans-extralight.ttf delete mode 100644 docs/fonts/dejavu-sans-mono-bold.ttf delete mode 100644 docs/fonts/dejavu-sans-mono.ttf delete mode 100644 docs/fonts/dejavu-sans-monoboldoblique.ttf delete mode 100644 docs/fonts/dejavu-sans-monooblique.ttf delete mode 100644 docs/fonts/dejavu-sans-oblique.ttf delete mode 100644 docs/fonts/dejavu-sans.ttf delete mode 100644 docs/fonts/dejavuserif-bold.ttf delete mode 100644 docs/fonts/dejavuserif-bolditalic.ttf delete mode 100644 docs/fonts/dejavuserif-italic.ttf delete mode 100644 docs/fonts/dejavuserif.ttf delete mode 100644 docs/fonts/dejavuserifcondensed-bold.ttf delete mode 100644 docs/fonts/dejavuserifcondensed-bolditalic.ttf delete mode 100644 docs/fonts/dejavuserifcondensed-italic.ttf delete mode 100644 docs/fonts/dejavuserifcondensed.ttf delete mode 100755 docs/fonts/notoserif-bold.ttf delete mode 100755 docs/fonts/notoserif-bolditalic.ttf delete mode 100755 docs/fonts/notoserif-italic.ttf delete mode 100755 docs/fonts/notoserif-regular.ttf delete mode 100755 docs/fonts/opensans-bold.ttf delete mode 100755 docs/fonts/opensans-bolditalic.ttf delete mode 100755 docs/fonts/opensans-extrabold.ttf delete mode 100755 docs/fonts/opensans-extrabolditalic.ttf delete mode 100755 docs/fonts/opensans-italic.ttf delete mode 100755 docs/fonts/opensans-light.ttf delete mode 100755 docs/fonts/opensans-lightitalic.ttf delete mode 100755 docs/fonts/opensans-regular.ttf delete mode 100755 docs/fonts/opensans-semibold.ttf delete mode 100755 docs/fonts/opensans-semibolditalic.ttf delete mode 100644 docs/generator/xref-validator.js delete mode 100644 docs/modules/ROOT/assets/attachments/.gitkeep delete mode 100644 docs/modules/ROOT/assets/images/01_account_add.png delete mode 100644 docs/modules/ROOT/assets/images/02_basic_auth.png delete mode 100644 docs/modules/ROOT/assets/images/03_cert_pass.png delete mode 100644 docs/modules/ROOT/assets/images/04_Account_1x.png delete mode 100644 docs/modules/ROOT/assets/images/04_cert_error.png delete mode 100644 docs/modules/ROOT/assets/images/05_Account_swipe.png delete mode 100644 docs/modules/ROOT/assets/images/06_Account_manage.png delete mode 100644 docs/modules/ROOT/assets/images/07_Account_edit.png delete mode 100644 docs/modules/ROOT/assets/images/11_Account_OAuth.png delete mode 100644 docs/modules/ROOT/assets/images/12_OAuth_dialogue.png delete mode 100644 docs/modules/ROOT/assets/images/13_OAuth_Web_view.png delete mode 100644 docs/modules/ROOT/assets/images/14_OAuth_Web_view_authorize.png delete mode 100644 docs/modules/ROOT/assets/images/21_File_list.png delete mode 100644 docs/modules/ROOT/assets/images/21_File_list_annotated.png delete mode 100644 docs/modules/ROOT/assets/images/21_File_list_parent.jpg delete mode 100644 docs/modules/ROOT/assets/images/21_File_list_parent.png delete mode 100644 docs/modules/ROOT/assets/images/22_File_plus.png delete mode 100644 docs/modules/ROOT/assets/images/23_Upload_File.png delete mode 100644 docs/modules/ROOT/assets/images/24_Upload_Photo_multi.png delete mode 100644 docs/modules/ROOT/assets/images/25_Files_multiselect.png delete mode 100644 docs/modules/ROOT/assets/images/26_Files_multidragdrop.png delete mode 100644 docs/modules/ROOT/assets/images/26_Files_multidragdrop_iPad.png delete mode 100644 docs/modules/ROOT/assets/images/27_File_Actions.png delete mode 100644 docs/modules/ROOT/assets/images/28_File_Actions_open.png delete mode 100644 docs/modules/ROOT/assets/images/31_Collab.png delete mode 100644 docs/modules/ROOT/assets/images/36_Links.png delete mode 100644 docs/modules/ROOT/assets/images/41_PDF.png delete mode 100644 docs/modules/ROOT/assets/images/42_PDF_toc.png delete mode 100644 docs/modules/ROOT/assets/images/43_PDF_thumbs.png delete mode 100644 docs/modules/ROOT/assets/images/44_PDF_search.png delete mode 100644 docs/modules/ROOT/assets/images/51_Quick_access.png delete mode 100644 docs/modules/ROOT/assets/images/81_Settings-2.png delete mode 100644 docs/modules/ROOT/assets/images/81_Settings.png delete mode 100644 docs/modules/ROOT/assets/images/82_Settings_passcode.png delete mode 100644 docs/modules/ROOT/assets/images/83_Settings_certs.png delete mode 100644 docs/modules/ROOT/assets/images/84_Settings_themes.png delete mode 100644 docs/modules/ROOT/assets/images/91_Logging.png delete mode 100644 docs/modules/ROOT/assets/images/add-account-certificate-passed-validation.png delete mode 100644 docs/modules/ROOT/assets/images/add-account-step-four.png delete mode 100644 docs/modules/ROOT/assets/images/add-account-step-one.png delete mode 100644 docs/modules/ROOT/assets/images/add-account-step-three.png delete mode 100644 docs/modules/ROOT/assets/images/add-account-step-two.png delete mode 100644 docs/modules/ROOT/assets/images/app-settings-dark-theme.PNG delete mode 100644 docs/modules/ROOT/assets/images/app-settings.PNG delete mode 100644 docs/modules/ROOT/assets/images/authorise-access-with-passcode-or-biometric-data.png delete mode 100644 docs/modules/ROOT/assets/images/browse-quickaccess-status-bar.png delete mode 100644 docs/modules/ROOT/assets/images/confirm-account-deletion.png delete mode 100644 docs/modules/ROOT/assets/images/create-new-folder.png delete mode 100644 docs/modules/ROOT/assets/images/create-public-link-copy-private-link.png delete mode 100644 docs/modules/ROOT/assets/images/delete-files.png delete mode 100644 docs/modules/ROOT/assets/images/delete-offline-copies.png delete mode 100644 docs/modules/ROOT/assets/images/delete-public-link.png delete mode 100644 docs/modules/ROOT/assets/images/directory-actions.png delete mode 100644 docs/modules/ROOT/assets/images/edit-oauth2-authenticated-account.png delete mode 100644 docs/modules/ROOT/assets/images/edit-or-delete-account.png delete mode 100644 docs/modules/ROOT/assets/images/enable-location.png delete mode 100644 docs/modules/ROOT/assets/images/file-actions-dialog.png delete mode 100644 docs/modules/ROOT/assets/images/file-actions-multiple-files-selected.png delete mode 100644 docs/modules/ROOT/assets/images/file-actions.jpg delete mode 100644 docs/modules/ROOT/assets/images/file-with-same-name-already-exists.png delete mode 100644 docs/modules/ROOT/assets/images/folder-actions.png delete mode 100644 docs/modules/ROOT/assets/images/grant-ios-app-access-to-photos.png delete mode 100644 docs/modules/ROOT/assets/images/icon-download.png delete mode 100644 docs/modules/ROOT/assets/images/icon-not-available-locally.png delete mode 100644 docs/modules/ROOT/assets/images/index-bar-with-callout.png delete mode 100644 docs/modules/ROOT/assets/images/index-bar.png delete mode 100644 docs/modules/ROOT/assets/images/ios-app-settings-logging.png delete mode 100644 docs/modules/ROOT/assets/images/manage-public-link-for-a-directory.png delete mode 100644 docs/modules/ROOT/assets/images/manage-public-link-settings.png delete mode 100644 docs/modules/ROOT/assets/images/masking-private-data.png delete mode 100644 docs/modules/ROOT/assets/images/notification-no-internet-connection.png delete mode 100644 docs/modules/ROOT/assets/images/offline-storage/all-available-offline-items.png delete mode 100644 docs/modules/ROOT/assets/images/offline-storage/available-offline-badge-closeup-with-callout.png delete mode 100644 docs/modules/ROOT/assets/images/offline-storage/available-offline-badge-closeup.png delete mode 100644 docs/modules/ROOT/assets/images/offline-storage/available-offline-badge.png delete mode 100644 docs/modules/ROOT/assets/images/offline-storage/available-offline-items.png delete mode 100644 docs/modules/ROOT/assets/images/offline-storage/available-offline-logo.png delete mode 100644 docs/modules/ROOT/assets/images/offline-storage/file-available-offline.png delete mode 100644 docs/modules/ROOT/assets/images/offline-storage/folder-action.png delete mode 100644 docs/modules/ROOT/assets/images/offline-storage/make-available-offline.png delete mode 100644 docs/modules/ROOT/assets/images/offline-storage/make-unavailable-offline.png delete mode 100644 docs/modules/ROOT/assets/images/offline-storage/not-available-offline-logo.png delete mode 100644 docs/modules/ROOT/assets/images/offline-storage/offline-storage-settings.png delete mode 100644 docs/modules/ROOT/assets/images/offline-storage/one-folder-available-offline.png delete mode 100644 docs/modules/ROOT/assets/images/offline-storage/quick-access-view-nothing-available-offline.png delete mode 100644 docs/modules/ROOT/assets/images/offline-storage/quick-access-view-toc.png delete mode 100644 docs/modules/ROOT/assets/images/offline-storage/regular-badges.png delete mode 100644 docs/modules/ROOT/assets/images/offline-storage/settings.png delete mode 100644 docs/modules/ROOT/assets/images/owncloud-log-configuration.png delete mode 100644 docs/modules/ROOT/assets/images/owncloud-server-mobile-apps.png delete mode 100644 docs/modules/ROOT/assets/images/pin-lock.png delete mode 100644 docs/modules/ROOT/assets/images/public-link-set-expiration-date.png delete mode 100644 docs/modules/ROOT/assets/images/public-link-set-password.png delete mode 100644 docs/modules/ROOT/assets/images/quick-access-public-links.png delete mode 100644 docs/modules/ROOT/assets/images/quick-access-view-annotated.png delete mode 100644 docs/modules/ROOT/assets/images/quick-access-view.png delete mode 100644 docs/modules/ROOT/assets/images/review-ssl-certificate-connection.png delete mode 100644 docs/modules/ROOT/assets/images/selecting-multiple-files.PNG delete mode 100644 docs/modules/ROOT/assets/images/settings-light-theme.png delete mode 100644 docs/modules/ROOT/assets/images/settings-lock-application-duration.png delete mode 100644 docs/modules/ROOT/assets/images/settings-media-upload.png delete mode 100644 docs/modules/ROOT/assets/images/settings-security-passcode-enabled.png delete mode 100644 docs/modules/ROOT/assets/images/settings-theme.PNG delete mode 100644 docs/modules/ROOT/assets/images/share-file-with-user.png delete mode 100644 docs/modules/ROOT/assets/images/share-file.png delete mode 100644 docs/modules/ROOT/assets/images/share-files-from-other-apps-step-1.png delete mode 100644 docs/modules/ROOT/assets/images/share-files-from-other-apps-step-2.png delete mode 100644 docs/modules/ROOT/assets/images/sort-files-landscape-mode.png delete mode 100644 docs/modules/ROOT/assets/images/sort-files-portrait-mode.png delete mode 100644 docs/modules/ROOT/assets/images/sorting-files.png delete mode 100644 docs/modules/ROOT/assets/images/swipe-and-delete-public-link.png delete mode 100644 docs/modules/ROOT/assets/images/themes-ownCloud-Classic.png delete mode 100644 docs/modules/ROOT/assets/images/themes-ownCloud-Dark.png delete mode 100644 docs/modules/ROOT/assets/images/themes-ownCloud-Light.png delete mode 100644 docs/modules/ROOT/assets/images/themes/classic.png delete mode 100644 docs/modules/ROOT/assets/images/themes/dark.png delete mode 100644 docs/modules/ROOT/assets/images/themes/light.png delete mode 100644 docs/modules/ROOT/assets/images/user-accounts-list-annotated-with-callout.png delete mode 100644 docs/modules/ROOT/assets/images/user-accounts-list-annotated.png delete mode 100644 docs/modules/ROOT/assets/images/user-accounts-list.png delete mode 100644 docs/modules/ROOT/assets/images/view-certificate-details.png delete mode 100644 docs/modules/ROOT/assets/images/view-file-image.png delete mode 100644 docs/modules/ROOT/assets/images/view-file-odt.png delete mode 100644 docs/modules/ROOT/assets/images/view-file-pdf.png delete mode 100644 docs/modules/ROOT/assets/images/view-file-text-file.png delete mode 100644 docs/modules/ROOT/assets/images/view-file-video.png delete mode 100644 docs/modules/ROOT/assets/images/view-links-for-file.png delete mode 100644 docs/modules/ROOT/assets/images/viewing-a-file.png delete mode 100644 docs/modules/ROOT/assets/images/viewing-an-empty-folder.png delete mode 100644 docs/modules/ROOT/assets/images/viewing-files-classic-theme.PNG delete mode 100644 docs/modules/ROOT/assets/images/viewing-files-light-theme.PNG delete mode 100644 docs/modules/ROOT/assets/images/welcome-screen.png delete mode 100644 docs/modules/ROOT/examples/.gitkeep delete mode 100644 docs/modules/ROOT/nav.adoc delete mode 100644 docs/modules/ROOT/pages/index.adoc delete mode 100644 docs/modules/ROOT/pages/ios_accounts.adoc delete mode 100644 docs/modules/ROOT/pages/ios_available_offline.adoc delete mode 100644 docs/modules/ROOT/pages/ios_collaboration.adoc delete mode 100644 docs/modules/ROOT/pages/ios_faq.adoc delete mode 100644 docs/modules/ROOT/pages/ios_files.adoc delete mode 100644 docs/modules/ROOT/pages/ios_files_integration.adoc delete mode 100644 docs/modules/ROOT/pages/ios_installation.adoc delete mode 100644 docs/modules/ROOT/pages/ios_mdm.adoc delete mode 100644 docs/modules/ROOT/pages/ios_quick_access.adoc delete mode 100644 docs/modules/ROOT/pages/ios_security.adoc delete mode 100644 docs/modules/ROOT/pages/ios_settings.adoc delete mode 100644 docs/modules/ROOT/pages/ios_task_scheduling.adoc delete mode 100644 docs/modules/ROOT/pages/ios_troubleshooting.adoc delete mode 100644 docs/package.json delete mode 100644 docs/resources/themes/custom-theme.yml delete mode 100644 docs/resources/themes/owncloud-theme.yml delete mode 100644 docs/site.yml delete mode 100644 docs/yarn-error.log delete mode 100644 docs/yarn.lock create mode 100644 ownCloud File Provider/FileProviderContentEnumerator.h create mode 100644 ownCloud File Provider/FileProviderContentEnumerator.m create mode 100644 ownCloud File Provider/OCVFSNode+FileProviderItem.h create mode 100644 ownCloud File Provider/OCVFSNode+FileProviderItem.m create mode 100644 ownCloud/Client/ClientRootViewController+ItemActions.swift create mode 100644 ownCloud/Resources/Assets.xcassets/biometrical-faceid.imageset/Contents.json create mode 100644 ownCloud/Resources/Assets.xcassets/biometrical-faceid.imageset/biometrical-faceid.pdf create mode 100644 ownCloud/Resources/Assets.xcassets/biometrical-touchid.imageset/Contents.json create mode 100644 ownCloud/Resources/Assets.xcassets/biometrical-touchid.imageset/biometrical.pdf create mode 100644 ownCloud/View Providers/OCResourceText+ViewProvider.swift create mode 100644 ownCloudAppFramework/VFS/OCVault+VFSManager.h create mode 100644 ownCloudAppFramework/VFS/OCVault+VFSManager.m create mode 100644 ownCloudAppFramework/VFS/VFSManager.h create mode 100644 ownCloudAppFramework/VFS/VFSManager.m create mode 100644 ownCloudAppShared/Client/Collection Views/Cells/ActionCell.swift create mode 100644 ownCloudAppShared/Client/Collection Views/Cells/DriveHeaderCell.swift create mode 100644 ownCloudAppShared/Client/Collection Views/Cells/DriveListCell.swift create mode 100644 ownCloudAppShared/Client/Collection Views/Cells/ExpandableResourceCell.swift create mode 100644 ownCloudAppShared/Client/Collection Views/Cells/ItemListCell.swift create mode 100644 ownCloudAppShared/Client/Collection Views/Cells/ThemeableCollectionViewCell.swift create mode 100644 ownCloudAppShared/Client/Collection Views/Cells/ThemeableCollectionViewListCell.swift create mode 100644 ownCloudAppShared/Client/Collection Views/Cells/ViewCell.swift create mode 100644 ownCloudAppShared/Client/Collection Views/CollectionViewCellConfiguration.swift create mode 100644 ownCloudAppShared/Client/Collection Views/CollectionViewCellProvider+StandardImplementations.swift create mode 100644 ownCloudAppShared/Client/Collection Views/CollectionViewCellProvider.swift create mode 100644 ownCloudAppShared/Client/Collection Views/CollectionViewController.swift create mode 100644 ownCloudAppShared/Client/Collection Views/CollectionViewSection.swift create mode 100644 ownCloudAppShared/Client/Collection Views/View Controllers/ClientItemViewController.swift create mode 100644 ownCloudAppShared/Client/Context/ClientContext.swift create mode 100644 ownCloudAppShared/Client/Data Item Interactions/OCAction+Interactions.swift create mode 100644 ownCloudAppShared/Client/Data Item Interactions/OCDataItem+InteractionProtocols.swift create mode 100644 ownCloudAppShared/Client/Data Item Interactions/OCDrive+Interactions.swift create mode 100644 ownCloudAppShared/Client/Data Item Interactions/OCItem+Interactions.swift create mode 100644 ownCloudAppShared/Client/File Lists/ClientSpacesTableViewController.swift create mode 100644 ownCloudAppShared/Client/Search/Scopes/SearchScope.swift create mode 100644 ownCloudAppShared/Client/Search/SearchViewController.swift create mode 100644 ownCloudAppShared/Client/User Interface/GradientView.swift delete mode 100644 ownCloudAppShared/Client/User Interface/SortMethodTableViewController.swift create mode 100644 ownCloudAppShared/Down.LICENSE create mode 100644 ownCloudAppShared/UIKit Extension/NSMutableAttributedString+AppendStyled.swift create mode 100644 ownCloudAppShared/UIKit Extension/UICollectionViewDiffableDataSource+Tools.swift create mode 100644 ownCloudAppShared/UIKit Extension/UIFont+Weight.swift create mode 100644 ownCloudAppShared/UIKit Extension/UILabel+Extension.swift create mode 100644 ownCloudAppShared/UIKit Extension/UIView+OCDataItem.swift rename tools/GenerateDocs/templates/{ios_mdm_tables.adoc.tmpl => configuration.adoc.tmpl} (100%) rename {enterprise => tools}/gomplate/Branding.plist.tmpl (100%) diff --git a/.github/ISSUE_TEMPLATE/release_template.md b/.github/ISSUE_TEMPLATE/release_template.md index e61f6cab0..53eef0413 100644 --- a/.github/ISSUE_TEMPLATE/release_template.md +++ b/.github/ISSUE_TEMPLATE/release_template.md @@ -26,6 +26,7 @@ Xcode version to work with: - [ ] [DEV] Inform Documentation-Team for the upcoming major/minor release with new version tag (notify #documentation-internal) - [ ] [QA] Design Test plan - [ ] [QA] Regression Test plan +- [ ] [DOC] Update https://owncloud.com/mobile-apps/#ios version numbers (notify #marketing) - [ ] [GIT] Merge branch `release/[major].[minor].[patch]` in master - [ ] [GIT] Create tag and sign it `[major].[minor].[patch]` - [ ] [GIT] Add the new release on [GitHub ios-app](https://github.com/owncloud/ios-app/releases) diff --git a/.github/workflows/configuration-documentation.yml b/.github/workflows/configuration-documentation.yml index 2699a95a2..68f470888 100644 --- a/.github/workflows/configuration-documentation.yml +++ b/.github/workflows/configuration-documentation.yml @@ -32,4 +32,4 @@ jobs: uses: stefanzweifel/git-auto-commit-action@v4 with: commit_message: Configuration documentation updated - file_pattern: doc/CONFIGURATION.json docs/modules/ROOT/pages/ios_mdm_tables.adoc + file_pattern: doc/CONFIGURATION.json doc/configuration.adoc diff --git a/.xcode-version b/.xcode-version index 2b4b4d7cb..c3d10c59d 100644 --- a/.xcode-version +++ b/.xcode-version @@ -1 +1 @@ -12.5.1 +13.3.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e6d63540..e06254fa1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,229 @@ +Changelog for ownCloud iOS Client [11.10.1] (2022-08-02) +======================================= +The following sections list the changes in ownCloud iOS Client 11.10.1 relevant to +ownCloud admins and users. + +[11.10.1]: https://github.com/owncloud/ios-app/compare/milestone/11.10.0...milestone/11.10.1 + +Summary +------- + +* Bugfix - (Branding) Biometrical Unlock in Share Sheet: [#1129](https://github.com/owncloud/ios-app/pull/1129) +* Bugfix - Show folder contents from cache when offline: [#1130](https://github.com/owncloud/ios-app/issues/1130) +* Bugfix - (Branding) Color Issues: [#1132](https://github.com/owncloud/ios-app/pull/1132) + +Details +------- + +* Bugfix - (Branding) Biometrical Unlock in Share Sheet: [#1129](https://github.com/owncloud/ios-app/pull/1129) + + Biometrical unlock in the share sheet does not work in some third party apps like Boxer. With new + branding parameters it is now possible to disable biometrical unlock in the share sheet or to + exclude specific apps. + + https://github.com/owncloud/ios-app/pull/1129 + +* Bugfix - Show folder contents from cache when offline: [#1130](https://github.com/owncloud/ios-app/issues/1130) + + With this fix the app shows the contents of the available folders when offline. + + https://github.com/owncloud/ios-app/issues/1130 + +* Bugfix - (Branding) Color Issues: [#1132](https://github.com/owncloud/ios-app/pull/1132) + + Fix some automatic color values, if the branding color is bright by checking the brightness of + the color. + + https://github.com/owncloud/ios-app/pull/1132 + +Changelog for ownCloud iOS Client [11.10.0] (2022-05-18) +======================================= +The following sections list the changes in ownCloud iOS Client 11.10.0 relevant to +ownCloud admins and users. + +[11.10.0]: https://github.com/owncloud/ios-app/compare/milestone/11.9.1...milestone/11.10.0 + +Summary +------- + +* Bugfix - IOS 15 SDK: [#1066](https://github.com/owncloud/ios-app/issues/1066) +* Bugfix - EMM Shortcuts Licensing: [#1114](https://github.com/owncloud/ios-app/issues/1114) +* Bugfix - Increased Timeout for Copy Action: [#1119](https://github.com/owncloud/ios-app/issues/1119) +* Bugfix - Shortcuts Action Delete Path Item: [#1123](https://github.com/owncloud/ios-app/issues/1123) +* Change - Migration to OpenSSL 1.1.0: [#1116](https://github.com/owncloud/ios-app/pull/1116) + +Details +------- + +* Bugfix - IOS 15 SDK: [#1066](https://github.com/owncloud/ios-app/issues/1066) + + After upgrading to iOS 15 SDK some UI fixes were needed. + + https://github.com/owncloud/ios-app/issues/1066 + +* Bugfix - EMM Shortcuts Licensing: [#1114](https://github.com/owncloud/ios-app/issues/1114) + + If app was build as EMM version, the app shown an licensing error, when running shortcut + intents. + + https://github.com/owncloud/ios-app/issues/1114 + +* Bugfix - Increased Timeout for Copy Action: [#1119](https://github.com/owncloud/ios-app/issues/1119) + + Increased HTTP request timeout for COPY actions from 1 minute to 10 minutes and improved error + handling for request timeouts. + + https://github.com/owncloud/ios-app/issues/1119 + +* Bugfix - Shortcuts Action Delete Path Item: [#1123](https://github.com/owncloud/ios-app/issues/1123) + + The shortcuts action Delete Path Item did not provided configured accounts. + + https://github.com/owncloud/ios-app/issues/1123 + +* Change - Migration to OpenSSL 1.1.0: [#1116](https://github.com/owncloud/ios-app/pull/1116) + + Migrated code to OpenSSL 1.1.1 API. + + https://github.com/owncloud/ios-app/pull/1116 + +Changelog for ownCloud iOS Client [11.9.1] (2022-03-29) +======================================= +The following sections list the changes in ownCloud iOS Client 11.9.1 relevant to +ownCloud admins and users. + +[11.9.1]: https://github.com/owncloud/ios-app/compare/milestone/11.9.0...milestone/11.9.1 + +Summary +------- + +* Bugfix - Setup Passcode with Biometrical Unlock: [#1112](https://github.com/owncloud/ios-app/pull/1112) +* Change - Set App Group Identifier: [#1099](https://github.com/owncloud/ios-app/pull/1099) + +Details +------- + +* Bugfix - Setup Passcode with Biometrical Unlock: [#1112](https://github.com/owncloud/ios-app/pull/1112) + + Biometrical unlock button no longer appear in setup view and after passcode was successfully + setup, show biometrical unlock for permissions dialog. + + https://github.com/owncloud/ios-app/pull/1112 + +* Change - Set App Group Identifier: [#1099](https://github.com/owncloud/ios-app/pull/1099) + + Set a custom app group identifier via Branding.plist this parameter. This value will be set by + fastlane to all needed Info.plist keys. This is needed, if a customer is using an own resigning + script which does not handle setting the app group identifier. + + https://github.com/owncloud/ios-app/pull/1099 + +Changelog for ownCloud iOS Client [11.9.0] (2022-03-16) +======================================= +The following sections list the changes in ownCloud iOS Client 11.9.0 relevant to +ownCloud admins and users. + +[11.9.0]: https://github.com/owncloud/ios-app/compare/milestone/11.8.2...milestone/11.9.0 + +Summary +------- + +* Bugfix - Fix WebDAV endpoint URL for media playback after restoration: [#1093](https://github.com/owncloud/ios-app/pull/1093) +* Bugfix - OAuth token renewal race condition: [#1105](https://github.com/owncloud/ios-app/pull/1105) +* Change - Biometrical Authentication Button: [#1004](https://github.com/owncloud/ios-app/issues/1004) +* Change - Poll for changes efficiency enhancements: [#1043](https://github.com/owncloud/ios-app/pull/1043) +* Change - Webfinger / server location: [#1059](https://github.com/owncloud/ios-app/pull/1059) +* Change - Infinite PROPFIND support: [#950](https://github.com/owncloud/ios-app/issues/950) +* Change - Rename Account (without re-authentication): [#972](https://github.com/owncloud/ios-app/issues/972) + +Details +------- + +* Bugfix - Fix WebDAV endpoint URL for media playback after restoration: [#1093](https://github.com/owncloud/ios-app/pull/1093) + + Fixes a bug where media playback failed with a 404 Not Found error after restoration because the + WebDAV endpoint URL was constructed from authentication data rather than OC user endpoint + data. + + https://github.com/owncloud/ios-app/pull/1093 + +* Bugfix - OAuth token renewal race condition: [#1105](https://github.com/owncloud/ios-app/pull/1105) + + Retry requests that failed with a 401 during a token refresh + + https://github.com/owncloud/ios-app/pull/1105 + +* Change - Biometrical Authentication Button: [#1004](https://github.com/owncloud/ios-app/issues/1004) + + Added biometrical authentication button to provide a fallback for the fileprovider or app, if + the automatically biometrical unlock does not work, or the user cancel the biometrical + authentication flow. + + https://github.com/owncloud/ios-app/issues/1004 + +* Change - Poll for changes efficiency enhancements: [#1043](https://github.com/owncloud/ios-app/pull/1043) + + Avoids simultaneous polling for changes by FileProvider and app. + + https://github.com/owncloud/ios-app/pull/1043 + +* Change - Webfinger / server location: [#1059](https://github.com/owncloud/ios-app/pull/1059) + + Allows using webfinger or a lookup table to locate and use an alternative server based on the + user name + + https://github.com/owncloud/ios-app/pull/1059 + +* Change - Infinite PROPFIND support: [#950](https://github.com/owncloud/ios-app/issues/950) + + Added support for prepopulation of newly created account bookmarks via infinite PROPFINDs, + which speeds up the initial scan + + https://github.com/owncloud/ios-app/issues/950 + +* Change - Rename Account (without re-authentication): [#972](https://github.com/owncloud/ios-app/issues/972) + + Check if only the account name was changed in edit mode: save and dismiss without + re-authentication + + https://github.com/owncloud/ios-app/issues/972 + +Changelog for ownCloud iOS Client [11.8.2] (2022-01-17) +======================================= +The following sections list the changes in ownCloud iOS Client 11.8.2 relevant to +ownCloud admins and users. + +[11.8.2]: https://github.com/owncloud/ios-app/compare/milestone/11.8.1...milestone/11.8.2 + +Summary +------- + +* Bugfix - Continuous Audio Playback: [#4924](https://github.com/owncloud/enterprise/issues/4924) +* Bugfix - PDF Editing: [#4934](https://github.com/owncloud/enterprise/issues/4934) +* Change - (Branding) Corporate Color as Folder Color: [#1069](https://github.com/owncloud/ios-app/issues/1069) + +Details +------- + +* Bugfix - Continuous Audio Playback: [#4924](https://github.com/owncloud/enterprise/issues/4924) + + Fixed continuous audio playback, which stopped after two audio files. + + https://github.com/owncloud/enterprise/issues/4924 + +* Bugfix - PDF Editing: [#4934](https://github.com/owncloud/enterprise/issues/4934) + + Fixed bug that prevents changes to PDFs being saved in place. + + https://github.com/owncloud/enterprise/issues/4934 + +* Change - (Branding) Corporate Color as Folder Color: [#1069](https://github.com/owncloud/ios-app/issues/1069) + + Use the corporate color as folder color as default color (can be overridden by the specific + key/value pair). + + https://github.com/owncloud/ios-app/issues/1069 + Changelog for ownCloud iOS Client [11.8.1] (2021-12-22) ======================================= The following sections list the changes in ownCloud iOS Client 11.8.1 relevant to diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md new file mode 100644 index 000000000..576976d90 --- /dev/null +++ b/KNOWN_ISSUES.md @@ -0,0 +1,72 @@ +# Known issues in version 12.0 alpha 2 + +## WARNING + +This release of version 12 is an alpha preview release and not yet ready for production or regular use. +It should only be used with dedicated test servers, test data - and test devices. + +## App +- in the new browsing experience, some features are not yet available: + - a grid view + - breadcrumb title + - item / folder / usage info at the bottom of lists +- spaces do not yet show a member count or provide access to a list of members +- subscription of spaces can't be turned on/off yet +- the root of spaces-based accounts is not yet shown as hierarchic sidebar +- support for sharing is widely untested and/or unavailable in the alpha +- inactivated state of spaces is not yet represented in the UI +- Copy & Paste allows copying a folder into a subfolder of its own / itself, leading to an infinite cycle +- handling of detached drives with user data in them (see OCVault.detachedDrives) +- sync actions that are actually complete are not always cleared from the Status tab until a logout/login +- dropping an item into its source/origin folder (same view controller) triggers a MOVE that fails + +## File Provider +- dragging an entire space on top of another starts a full copy of the space, which eventually fails halfway through + +## SDK +- local storage consumed by spaces that are then deleted or inactivated is not reclaimed +- pre-population of accounts using infinite PROPFIND is not supported + +# Evolution roadmap +- collection views + - support sidebars / hierarchies, including expanded state, with dynamic updates from data sources + +- location picker replaces folder picker + - supports picking + - accounts + - spaces + - folders + - returns an OCLocation + - allow passing "quick locations" to present on top in a group + - track and re-offer last-picked / recent locations (via account's KVS) + - quick access to personal and other spaces + - integrate favorites as group + +- account list + - allow grouping accounts (i.e. Home / Work) + - replace simple list with modern CollectionViewController-based UI + +- make sync smarter, f.ex.: + - a file that is updated locally multiple times only should be uploaded once, not once for every update + - a file or folder that is scheduled for upload / creation - and then deleted, should not be uploaded then deleted + - a file scheduled for upload in a folder that is then deleted should not be uploaded then deleted + +- make sync more resilient + - more rigid dependency tracking -> stuck sync actions waiting for a request to return should no longer be possible as a result + - allow users to manually reschedule sync actions (=> maybe only after implementing cross-process progress reporting) + +- progress reporting sync across processes + - app -> FP + - FP -> app + - possibly use dedicated OC KVS + OCProgress for that + +- support for versions + +- photo uploads + - needs better error reporting / handling + - photos vanished from photos between upload request and when it is its turn + - report to user, drop silently, retry (how often/long?)? + - other errors + - report to user, drop silently, retry (how often/long?)? + +- more expressive "Empty folder" message display, based on new .message item type diff --git a/changelog/11.10.0_2022-05-18/1066 b/changelog/11.10.0_2022-05-18/1066 new file mode 100644 index 000000000..e64d438d3 --- /dev/null +++ b/changelog/11.10.0_2022-05-18/1066 @@ -0,0 +1,5 @@ +Bugfix: iOS 15 SDK + +After upgrading to iOS 15 SDK some UI fixes were needed. + +https://github.com/owncloud/ios-app/issues/1066 diff --git a/changelog/11.10.0_2022-05-18/1114 b/changelog/11.10.0_2022-05-18/1114 new file mode 100644 index 000000000..f86c8d37e --- /dev/null +++ b/changelog/11.10.0_2022-05-18/1114 @@ -0,0 +1,5 @@ +Bugfix: EMM Shortcuts Licensing + +If app was build as EMM version, the app shown an licensing error, when running shortcut intents. + +https://github.com/owncloud/ios-app/issues/1114 diff --git a/changelog/11.10.0_2022-05-18/1116 b/changelog/11.10.0_2022-05-18/1116 new file mode 100644 index 000000000..510d7303f --- /dev/null +++ b/changelog/11.10.0_2022-05-18/1116 @@ -0,0 +1,5 @@ +Change: Migration to OpenSSL 1.1.0 + +Migrated code to OpenSSL 1.1.1 API. + +https://github.com/owncloud/ios-app/pull/1116 diff --git a/changelog/11.10.0_2022-05-18/1119 b/changelog/11.10.0_2022-05-18/1119 new file mode 100644 index 000000000..0259df939 --- /dev/null +++ b/changelog/11.10.0_2022-05-18/1119 @@ -0,0 +1,5 @@ +Bugfix: Increased Timeout for Copy Action + +Increased HTTP request timeout for COPY actions from 1 minute to 10 minutes and improved error handling for request timeouts. + +https://github.com/owncloud/ios-app/issues/1119 diff --git a/changelog/11.10.0_2022-05-18/1123 b/changelog/11.10.0_2022-05-18/1123 new file mode 100644 index 000000000..36bd1621e --- /dev/null +++ b/changelog/11.10.0_2022-05-18/1123 @@ -0,0 +1,5 @@ +Bugfix: Shortcuts Action Delete Path Item + +The shortcuts action Delete Path Item did not provided configured accounts. + +https://github.com/owncloud/ios-app/issues/1123 diff --git a/changelog/11.10.1_2022-08-02/1129 b/changelog/11.10.1_2022-08-02/1129 new file mode 100644 index 000000000..64f0996d0 --- /dev/null +++ b/changelog/11.10.1_2022-08-02/1129 @@ -0,0 +1,5 @@ +Bugfix: (Branding) Biometrical Unlock in Share Sheet + +Biometrical unlock in the share sheet does not work in some third party apps like Boxer. With new branding parameters it is now possible to disable biometrical unlock in the share sheet or to exclude specific apps. + +https://github.com/owncloud/ios-app/pull/1129 diff --git a/changelog/11.10.1_2022-08-02/1130 b/changelog/11.10.1_2022-08-02/1130 new file mode 100644 index 000000000..630ff061d --- /dev/null +++ b/changelog/11.10.1_2022-08-02/1130 @@ -0,0 +1,5 @@ +Bugfix: Show folder contents from cache when offline + +With this fix the app shows the contents of the available folders when offline. + +https://github.com/owncloud/ios-app/issues/1130 diff --git a/changelog/11.10.1_2022-08-02/1132 b/changelog/11.10.1_2022-08-02/1132 new file mode 100644 index 000000000..3275f1a31 --- /dev/null +++ b/changelog/11.10.1_2022-08-02/1132 @@ -0,0 +1,5 @@ +Bugfix: (Branding) Color Issues + +Fix some automatic color values, if the branding color is bright by checking the brightness of the color. + +https://github.com/owncloud/ios-app/pull/1132 diff --git a/changelog/11.8.2_2022-01-17/4857 b/changelog/11.8.2_2022-01-17/4857 new file mode 100644 index 000000000..975725b3f --- /dev/null +++ b/changelog/11.8.2_2022-01-17/4857 @@ -0,0 +1,5 @@ +Change: (Branding) Corporate Color as Folder Color + +Use the corporate color as folder color as default color (can be overridden by the specific key/value pair). + +https://github.com/owncloud/ios-app/issues/1069 diff --git a/changelog/11.8.2_2022-01-17/4924 b/changelog/11.8.2_2022-01-17/4924 new file mode 100644 index 000000000..d9030a4b4 --- /dev/null +++ b/changelog/11.8.2_2022-01-17/4924 @@ -0,0 +1,5 @@ +Bugfix: Continuous Audio Playback + +Fixed continuous audio playback, which stopped after two audio files. + +https://github.com/owncloud/enterprise/issues/4924 diff --git a/changelog/11.8.2_2022-01-17/4934 b/changelog/11.8.2_2022-01-17/4934 new file mode 100644 index 000000000..9a7bb8bfb --- /dev/null +++ b/changelog/11.8.2_2022-01-17/4934 @@ -0,0 +1,5 @@ +Bugfix: PDF Editing + +Fixed bug that prevents changes to PDFs being saved in place. + +https://github.com/owncloud/enterprise/issues/4934 diff --git a/changelog/11.9.0_2022-03-16/1004 b/changelog/11.9.0_2022-03-16/1004 new file mode 100644 index 000000000..8d3d5e7eb --- /dev/null +++ b/changelog/11.9.0_2022-03-16/1004 @@ -0,0 +1,5 @@ +Change: Biometrical Authentication Button + +Added biometrical authentication button to provide a fallback for the fileprovider or app, if the automatically biometrical unlock does not work, or the user cancel the biometrical authentication flow. + +https://github.com/owncloud/ios-app/issues/1004 diff --git a/changelog/11.9.0_2022-03-16/1043 b/changelog/11.9.0_2022-03-16/1043 new file mode 100644 index 000000000..448636ab8 --- /dev/null +++ b/changelog/11.9.0_2022-03-16/1043 @@ -0,0 +1,5 @@ +Change: Poll for changes efficiency enhancements + +Avoids simultaneous polling for changes by FileProvider and app. + +https://github.com/owncloud/ios-app/pull/1043 diff --git a/changelog/11.9.0_2022-03-16/1059 b/changelog/11.9.0_2022-03-16/1059 new file mode 100644 index 000000000..d9a360d0b --- /dev/null +++ b/changelog/11.9.0_2022-03-16/1059 @@ -0,0 +1,5 @@ +Change: Webfinger / server location + +Allows using webfinger or a lookup table to locate and use an alternative server based on the user name + +https://github.com/owncloud/ios-app/pull/1059 diff --git a/changelog/11.9.0_2022-03-16/1093 b/changelog/11.9.0_2022-03-16/1093 new file mode 100644 index 000000000..4f25dff08 --- /dev/null +++ b/changelog/11.9.0_2022-03-16/1093 @@ -0,0 +1,5 @@ +Bugfix: Fix WebDAV endpoint URL for media playback after restoration + +Fixes a bug where media playback failed with a 404 Not Found error after restoration because the WebDAV endpoint URL was constructed from authentication data rather than OC user endpoint data. + +https://github.com/owncloud/ios-app/pull/1093 diff --git a/changelog/11.9.0_2022-03-16/1105 b/changelog/11.9.0_2022-03-16/1105 new file mode 100644 index 000000000..f2fc16e70 --- /dev/null +++ b/changelog/11.9.0_2022-03-16/1105 @@ -0,0 +1,5 @@ +Bugfix: OAuth token renewal race condition + +Retry requests that failed with a 401 during a token refresh + +https://github.com/owncloud/ios-app/pull/1105 diff --git a/changelog/11.9.0_2022-03-16/950 b/changelog/11.9.0_2022-03-16/950 new file mode 100644 index 000000000..9273a01e0 --- /dev/null +++ b/changelog/11.9.0_2022-03-16/950 @@ -0,0 +1,5 @@ +Change: Infinite PROPFIND support + +Added support for prepopulation of newly created account bookmarks via infinite PROPFINDs, which speeds up the initial scan + +https://github.com/owncloud/ios-app/issues/950 diff --git a/changelog/11.9.0_2022-03-16/972 b/changelog/11.9.0_2022-03-16/972 new file mode 100644 index 000000000..0198a5e32 --- /dev/null +++ b/changelog/11.9.0_2022-03-16/972 @@ -0,0 +1,5 @@ +Change: Rename Account (without re-authentication) + +Check if only the account name was changed in edit mode: save and dismiss without re-authentication + +https://github.com/owncloud/ios-app/issues/972 diff --git a/changelog/11.9.1_2022-03-29/1099 b/changelog/11.9.1_2022-03-29/1099 new file mode 100644 index 000000000..807cf6a4f --- /dev/null +++ b/changelog/11.9.1_2022-03-29/1099 @@ -0,0 +1,5 @@ +Change: Set App Group Identifier + +Set a custom app group identifier via Branding.plist this parameter. This value will be set by fastlane to all needed Info.plist keys. This is needed, if a customer is using an own resigning script which does not handle setting the app group identifier. + +https://github.com/owncloud/ios-app/pull/1099 diff --git a/changelog/11.9.1_2022-03-29/1112 b/changelog/11.9.1_2022-03-29/1112 new file mode 100644 index 000000000..9496168ab --- /dev/null +++ b/changelog/11.9.1_2022-03-29/1112 @@ -0,0 +1,5 @@ +Bugfix: Setup Passcode with Biometrical Unlock + +Biometrical unlock button no longer appear in setup view and after passcode was successfully setup, show biometrical unlock for permissions dialog. + +https://github.com/owncloud/ios-app/pull/1112 diff --git a/doc/BUILD_CUSTOMIZATION.md b/doc/BUILD_CUSTOMIZATION.md deleted file mode 100644 index 7eaf4c53d..000000000 --- a/doc/BUILD_CUSTOMIZATION.md +++ /dev/null @@ -1,76 +0,0 @@ -# Build Flags - -## Description - -Build Flags can be used to control the inclusion or exclusion of certain functionality or features. - -## Usage in Branding - -A space-separated list of flags can be specified in the `Branding.plist` with the key `build.flags`, f.ex.: - -```xml -build.flags -DISABLE_BACKGROUND_LOCATION -``` - -## Flags - -The following options can be used as `build.flags` / `APP_BUILD_FLAGS`: - -### `DISABLE_BACKGROUND_LOCATION` - -Removes the following from the app: -- the option for location-triggered background uploads from Settings -- the location description keys from the app's `Info.plist` - -Not used by default. - -### `DISABLE_APPSTORE_LICENSING` - -Removes the following from the app: -- App Store integration for OCLicense -- App Store related view controllers and settings section - - -# Custom Schemes - -## Description - -The app uses two URL schemes: -- `oc` for authentication -- `owncloud` for private links - -Both schemes are part of the app's `Info.plist`, which can only be changed at build time. - -## Usage in Branding - -### Private Links - -The default `owncloud` app URL scheme in `Info.plist` can be changed by providing an alternative scheme name in the `Branding.plist` with the key `build.custom-app-scheme`, f.ex.: - -```xml -build.custom-app-scheme -myscheme -``` - -### Authentication - -The default `oc` app URL scheme in `Info.plist` can be changed by providing an alternative scheme name in the `Branding.plist` with the key `build.custom-auth-scheme`, f.ex.: - -```xml -build.custom-auth-scheme -ms -``` - -The change in the `Info.plist` is only necessary when an external browser is used for authentication via OAuth2 or OIDC. In that case, the scheme must also be changed in the regular options for OIDC and OAuth2 authentication methods: - -```xml -authentication-oauth2.oa2-redirect-uri -ms://ios.owncloud.com -authentication-oauth2.oidc-redirect-uri -ms://ios.owncloud.com -``` - -Depending on OAuth2 and OIDC implementation on the server side: -- it may be necessary to also adapt the registered redirect URI on the server -- authentication could fail if not adapted on the server diff --git a/doc/CONFIGURATION.json b/doc/CONFIGURATION.json index a84949d7d..631d8df97 100644 --- a/doc/CONFIGURATION.json +++ b/doc/CONFIGURATION.json @@ -527,6 +527,33 @@ "status" : "supported", "type" : "string" }, + { + "autoExpansion" : "none", + "category" : "Bookmarks", + "categoryTag" : "bookmarks", + "classIdentifier" : "bookmark", + "className" : "ownCloud.BookmarkViewController", + "description" : "Controls prepopulation of the local database with the full item set during account setup.", + "flatIdentifier" : "bookmark.prepopulation", + "key" : "prepopulation", + "label" : "bookmark.prepopulation", + "possibleValues" : [ + { + "description" : "No prepopulation. Request the contents of every folder individually.", + "value" : "doNot" + }, + { + "description" : "Parse the prepopulation metadata after receiving it as a whole.", + "value" : "split" + }, + { + "description" : "Parse the prepopulation metadata while receiving it.", + "value" : "streaming" + } + ], + "status" : "supported", + "type" : "string" + }, { "autoExpansion" : "none", "category" : "Bookmarks", diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 000000000..7eddc201b --- /dev/null +++ b/doc/README.md @@ -0,0 +1,14 @@ +# Content of Folder `doc` + +## Content + +This folder contains the following content: +- autogenerated files `configuration.json` and `configuration.adoc`, which contains all app settings, which can be set by MDM or via `Branding.plist` +- `images`folder, which contains auto-generated app screenshots via `fastlane` +- `Themes`folder, which contains technical information about the themes structure +- other developer related documentation files + +## iOS-App User Documentation + +The general user documentation is available on [https://doc.owncloud.com/ios-app/latest/](https://doc.owncloud.com/ios-app/latest/). + diff --git a/docs/modules/ROOT/pages/ios_mdm_tables.adoc b/doc/configuration.adoc similarity index 98% rename from docs/modules/ROOT/pages/ios_mdm_tables.adoc rename to doc/configuration.adoc index e1f49cf44..eccdb58df 100644 --- a/docs/modules/ROOT/pages/ios_mdm_tables.adoc +++ b/doc/configuration.adoc @@ -314,6 +314,27 @@ tag::bookmarks[] |The default URL for the creation of new bookmarks. |supported `candidate` +|bookmark.prepopulation +|string +| +|Controls prepopulation of the local database with the full item set during account setup. +[cols="1,1"] +!=== +! Value +! Description +! `doNot` +! No prepopulation. Request the contents of every folder individually. + +! `split` +! Parse the prepopulation metadata after receiving it as a whole. + +! `streaming` +! Parse the prepopulation metadata while receiving it. + +!=== + +|supported `candidate` + |bookmark.url-editable |bool |`true` diff --git a/fastlane/screenshots/Framefile.json b/doc/images/Framefile.json similarity index 100% rename from fastlane/screenshots/Framefile.json rename to doc/images/Framefile.json diff --git a/fastlane/screenshots/README.md b/doc/images/README.md similarity index 100% rename from fastlane/screenshots/README.md rename to doc/images/README.md diff --git a/fastlane/screenshots/README.txt b/doc/images/README.txt similarity index 100% rename from fastlane/screenshots/README.txt rename to doc/images/README.txt diff --git a/fastlane/screenshots/ar/keyword.strings b/doc/images/ar/keyword.strings similarity index 100% rename from fastlane/screenshots/ar/keyword.strings rename to doc/images/ar/keyword.strings diff --git a/fastlane/screenshots/ar/title.strings b/doc/images/ar/title.strings similarity index 100% rename from fastlane/screenshots/ar/title.strings rename to doc/images/ar/title.strings diff --git a/fastlane/screenshots/background.jpg b/doc/images/background.jpg similarity index 100% rename from fastlane/screenshots/background.jpg rename to doc/images/background.jpg diff --git a/fastlane/screenshots/ca/keyword.strings b/doc/images/ca/keyword.strings similarity index 100% rename from fastlane/screenshots/ca/keyword.strings rename to doc/images/ca/keyword.strings diff --git a/fastlane/screenshots/ca/title.strings b/doc/images/ca/title.strings similarity index 100% rename from fastlane/screenshots/ca/title.strings rename to doc/images/ca/title.strings diff --git a/fastlane/screenshots/de-DE/README.md b/doc/images/de-DE/README.md similarity index 100% rename from fastlane/screenshots/de-DE/README.md rename to doc/images/de-DE/README.md diff --git a/fastlane/screenshots/de-DE/keyword.strings b/doc/images/de-DE/keyword.strings similarity index 100% rename from fastlane/screenshots/de-DE/keyword.strings rename to doc/images/de-DE/keyword.strings diff --git a/fastlane/screenshots/de-DE/title.strings b/doc/images/de-DE/title.strings similarity index 100% rename from fastlane/screenshots/de-DE/title.strings rename to doc/images/de-DE/title.strings diff --git a/fastlane/screenshots/de/keyword.strings b/doc/images/de/keyword.strings similarity index 100% rename from fastlane/screenshots/de/keyword.strings rename to doc/images/de/keyword.strings diff --git a/fastlane/screenshots/de/title.strings b/doc/images/de/title.strings similarity index 100% rename from fastlane/screenshots/de/title.strings rename to doc/images/de/title.strings diff --git a/fastlane/screenshots/de_CH/keyword.strings b/doc/images/de_CH/keyword.strings similarity index 100% rename from fastlane/screenshots/de_CH/keyword.strings rename to doc/images/de_CH/keyword.strings diff --git a/fastlane/screenshots/de_CH/title.strings b/doc/images/de_CH/title.strings similarity index 100% rename from fastlane/screenshots/de_CH/title.strings rename to doc/images/de_CH/title.strings diff --git a/fastlane/screenshots/el/keyword.strings b/doc/images/el/keyword.strings similarity index 100% rename from fastlane/screenshots/el/keyword.strings rename to doc/images/el/keyword.strings diff --git a/fastlane/screenshots/el/title.strings b/doc/images/el/title.strings similarity index 100% rename from fastlane/screenshots/el/title.strings rename to doc/images/el/title.strings diff --git a/fastlane/screenshots/en-GB/keyword.strings b/doc/images/en-GB/keyword.strings similarity index 100% rename from fastlane/screenshots/en-GB/keyword.strings rename to doc/images/en-GB/keyword.strings diff --git a/fastlane/screenshots/en-GB/title.strings b/doc/images/en-GB/title.strings similarity index 100% rename from fastlane/screenshots/en-GB/title.strings rename to doc/images/en-GB/title.strings diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (2nd generation)-10_ios_accounts_welcome_demo.png b/doc/images/en-US/iPad Pro (12.9-inch) (2nd generation)-10_ios_accounts_welcome_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (2nd generation)-10_ios_accounts_welcome_demo.png rename to doc/images/en-US/iPad Pro (12.9-inch) (2nd generation)-10_ios_accounts_welcome_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (2nd generation)-11_ios_accounts_list_demo.png b/doc/images/en-US/iPad Pro (12.9-inch) (2nd generation)-11_ios_accounts_list_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (2nd generation)-11_ios_accounts_list_demo.png rename to doc/images/en-US/iPad Pro (12.9-inch) (2nd generation)-11_ios_accounts_list_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (2nd generation)-20_ios_files_list_demo.png b/doc/images/en-US/iPad Pro (12.9-inch) (2nd generation)-20_ios_files_list_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (2nd generation)-20_ios_files_list_demo.png rename to doc/images/en-US/iPad Pro (12.9-inch) (2nd generation)-20_ios_files_list_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (2nd generation)-21_ios_files_actions_demo.png b/doc/images/en-US/iPad Pro (12.9-inch) (2nd generation)-21_ios_files_actions_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (2nd generation)-21_ios_files_actions_demo.png rename to doc/images/en-US/iPad Pro (12.9-inch) (2nd generation)-21_ios_files_actions_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (2nd generation)-22_ios_files_preview_pdf_demo.png b/doc/images/en-US/iPad Pro (12.9-inch) (2nd generation)-22_ios_files_preview_pdf_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (2nd generation)-22_ios_files_preview_pdf_demo.png rename to doc/images/en-US/iPad Pro (12.9-inch) (2nd generation)-22_ios_files_preview_pdf_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (2nd generation)-23_ios_files_list_multiple_window_landscape.png b/doc/images/en-US/iPad Pro (12.9-inch) (2nd generation)-23_ios_files_list_multiple_window_landscape.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (2nd generation)-23_ios_files_list_multiple_window_landscape.png rename to doc/images/en-US/iPad Pro (12.9-inch) (2nd generation)-23_ios_files_list_multiple_window_landscape.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (2nd generation)-40_ios_quick_access_demo.png b/doc/images/en-US/iPad Pro (12.9-inch) (2nd generation)-40_ios_quick_access_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (2nd generation)-40_ios_quick_access_demo.png rename to doc/images/en-US/iPad Pro (12.9-inch) (2nd generation)-40_ios_quick_access_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (2nd generation)-60_ios_settings_demo.png b/doc/images/en-US/iPad Pro (12.9-inch) (2nd generation)-60_ios_settings_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (2nd generation)-60_ios_settings_demo.png rename to doc/images/en-US/iPad Pro (12.9-inch) (2nd generation)-60_ios_settings_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-10_ios_accounts_welcome_demo.png b/doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-10_ios_accounts_welcome_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-10_ios_accounts_welcome_demo.png rename to doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-10_ios_accounts_welcome_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-10_ios_accounts_welcome_demo_framed.png b/doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-10_ios_accounts_welcome_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-10_ios_accounts_welcome_demo_framed.png rename to doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-10_ios_accounts_welcome_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-11_ios_accounts_list_demo.png b/doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-11_ios_accounts_list_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-11_ios_accounts_list_demo.png rename to doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-11_ios_accounts_list_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-11_ios_accounts_list_demo_framed.png b/doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-11_ios_accounts_list_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-11_ios_accounts_list_demo_framed.png rename to doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-11_ios_accounts_list_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-20_ios_files_list_demo.png b/doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-20_ios_files_list_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-20_ios_files_list_demo.png rename to doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-20_ios_files_list_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-20_ios_files_list_demo_framed.png b/doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-20_ios_files_list_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-20_ios_files_list_demo_framed.png rename to doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-20_ios_files_list_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-21_ios_files_actions_demo.png b/doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-21_ios_files_actions_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-21_ios_files_actions_demo.png rename to doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-21_ios_files_actions_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-21_ios_files_actions_demo_framed.png b/doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-21_ios_files_actions_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-21_ios_files_actions_demo_framed.png rename to doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-21_ios_files_actions_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-22_ios_files_preview_pdf_demo.png b/doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-22_ios_files_preview_pdf_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-22_ios_files_preview_pdf_demo.png rename to doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-22_ios_files_preview_pdf_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-22_ios_files_preview_pdf_demo_framed.png b/doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-22_ios_files_preview_pdf_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-22_ios_files_preview_pdf_demo_framed.png rename to doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-22_ios_files_preview_pdf_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-60_ios_settings_demo.png b/doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-60_ios_settings_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-60_ios_settings_demo.png rename to doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-60_ios_settings_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-60_ios_settings_demo_framed.png b/doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-60_ios_settings_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (4th generation)-60_ios_settings_demo_framed.png rename to doc/images/en-US/iPad Pro (12.9-inch) (4th generation)-60_ios_settings_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (5th generation)-10_ios_accounts_welcome_demo.png b/doc/images/en-US/iPad Pro (12.9-inch) (5th generation)-10_ios_accounts_welcome_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (5th generation)-10_ios_accounts_welcome_demo.png rename to doc/images/en-US/iPad Pro (12.9-inch) (5th generation)-10_ios_accounts_welcome_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (5th generation)-11_ios_accounts_list_demo.png b/doc/images/en-US/iPad Pro (12.9-inch) (5th generation)-11_ios_accounts_list_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (5th generation)-11_ios_accounts_list_demo.png rename to doc/images/en-US/iPad Pro (12.9-inch) (5th generation)-11_ios_accounts_list_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (5th generation)-20_ios_files_list_demo.png b/doc/images/en-US/iPad Pro (12.9-inch) (5th generation)-20_ios_files_list_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (5th generation)-20_ios_files_list_demo.png rename to doc/images/en-US/iPad Pro (12.9-inch) (5th generation)-20_ios_files_list_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (5th generation)-21_ios_files_actions_demo.png b/doc/images/en-US/iPad Pro (12.9-inch) (5th generation)-21_ios_files_actions_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (5th generation)-21_ios_files_actions_demo.png rename to doc/images/en-US/iPad Pro (12.9-inch) (5th generation)-21_ios_files_actions_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (5th generation)-22_ios_files_preview_pdf_demo.png b/doc/images/en-US/iPad Pro (12.9-inch) (5th generation)-22_ios_files_preview_pdf_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (5th generation)-22_ios_files_preview_pdf_demo.png rename to doc/images/en-US/iPad Pro (12.9-inch) (5th generation)-22_ios_files_preview_pdf_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (5th generation)-23_ios_files_list_multiple_window_landscape.png b/doc/images/en-US/iPad Pro (12.9-inch) (5th generation)-23_ios_files_list_multiple_window_landscape.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (5th generation)-23_ios_files_list_multiple_window_landscape.png rename to doc/images/en-US/iPad Pro (12.9-inch) (5th generation)-23_ios_files_list_multiple_window_landscape.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (5th generation)-40_ios_quick_access_demo.png b/doc/images/en-US/iPad Pro (12.9-inch) (5th generation)-40_ios_quick_access_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (5th generation)-40_ios_quick_access_demo.png rename to doc/images/en-US/iPad Pro (12.9-inch) (5th generation)-40_ios_quick_access_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (12.9-inch) (5th generation)-60_ios_settings_demo.png b/doc/images/en-US/iPad Pro (12.9-inch) (5th generation)-60_ios_settings_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (12.9-inch) (5th generation)-60_ios_settings_demo.png rename to doc/images/en-US/iPad Pro (12.9-inch) (5th generation)-60_ios_settings_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (9.7-inch)-10_ios_accounts_welcome_demo.png b/doc/images/en-US/iPad Pro (9.7-inch)-10_ios_accounts_welcome_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (9.7-inch)-10_ios_accounts_welcome_demo.png rename to doc/images/en-US/iPad Pro (9.7-inch)-10_ios_accounts_welcome_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (9.7-inch)-10_ios_accounts_welcome_demo_framed.png b/doc/images/en-US/iPad Pro (9.7-inch)-10_ios_accounts_welcome_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (9.7-inch)-10_ios_accounts_welcome_demo_framed.png rename to doc/images/en-US/iPad Pro (9.7-inch)-10_ios_accounts_welcome_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPad Pro (9.7-inch)-11_ios_accounts_list_demo.png b/doc/images/en-US/iPad Pro (9.7-inch)-11_ios_accounts_list_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (9.7-inch)-11_ios_accounts_list_demo.png rename to doc/images/en-US/iPad Pro (9.7-inch)-11_ios_accounts_list_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (9.7-inch)-11_ios_accounts_list_demo_framed.png b/doc/images/en-US/iPad Pro (9.7-inch)-11_ios_accounts_list_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (9.7-inch)-11_ios_accounts_list_demo_framed.png rename to doc/images/en-US/iPad Pro (9.7-inch)-11_ios_accounts_list_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPad Pro (9.7-inch)-20_ios_files_list_demo.png b/doc/images/en-US/iPad Pro (9.7-inch)-20_ios_files_list_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (9.7-inch)-20_ios_files_list_demo.png rename to doc/images/en-US/iPad Pro (9.7-inch)-20_ios_files_list_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (9.7-inch)-20_ios_files_list_demo_framed.png b/doc/images/en-US/iPad Pro (9.7-inch)-20_ios_files_list_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (9.7-inch)-20_ios_files_list_demo_framed.png rename to doc/images/en-US/iPad Pro (9.7-inch)-20_ios_files_list_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPad Pro (9.7-inch)-21_ios_files_actions_demo.png b/doc/images/en-US/iPad Pro (9.7-inch)-21_ios_files_actions_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (9.7-inch)-21_ios_files_actions_demo.png rename to doc/images/en-US/iPad Pro (9.7-inch)-21_ios_files_actions_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (9.7-inch)-21_ios_files_actions_demo_framed.png b/doc/images/en-US/iPad Pro (9.7-inch)-21_ios_files_actions_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (9.7-inch)-21_ios_files_actions_demo_framed.png rename to doc/images/en-US/iPad Pro (9.7-inch)-21_ios_files_actions_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPad Pro (9.7-inch)-22_ios_files_preview_pdf_demo.png b/doc/images/en-US/iPad Pro (9.7-inch)-22_ios_files_preview_pdf_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (9.7-inch)-22_ios_files_preview_pdf_demo.png rename to doc/images/en-US/iPad Pro (9.7-inch)-22_ios_files_preview_pdf_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (9.7-inch)-22_ios_files_preview_pdf_demo_framed.png b/doc/images/en-US/iPad Pro (9.7-inch)-22_ios_files_preview_pdf_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (9.7-inch)-22_ios_files_preview_pdf_demo_framed.png rename to doc/images/en-US/iPad Pro (9.7-inch)-22_ios_files_preview_pdf_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPad Pro (9.7-inch)-23_ios_files_list_multiple_window_landscape.png b/doc/images/en-US/iPad Pro (9.7-inch)-23_ios_files_list_multiple_window_landscape.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (9.7-inch)-23_ios_files_list_multiple_window_landscape.png rename to doc/images/en-US/iPad Pro (9.7-inch)-23_ios_files_list_multiple_window_landscape.png diff --git a/fastlane/screenshots/en-US/iPad Pro (9.7-inch)-23_ios_files_list_multiple_window_landscape_framed.png b/doc/images/en-US/iPad Pro (9.7-inch)-23_ios_files_list_multiple_window_landscape_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (9.7-inch)-23_ios_files_list_multiple_window_landscape_framed.png rename to doc/images/en-US/iPad Pro (9.7-inch)-23_ios_files_list_multiple_window_landscape_framed.png diff --git a/fastlane/screenshots/en-US/iPad Pro (9.7-inch)-40_ios_quick_access_demo.png b/doc/images/en-US/iPad Pro (9.7-inch)-40_ios_quick_access_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (9.7-inch)-40_ios_quick_access_demo.png rename to doc/images/en-US/iPad Pro (9.7-inch)-40_ios_quick_access_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (9.7-inch)-40_ios_quick_access_demo_framed.png b/doc/images/en-US/iPad Pro (9.7-inch)-40_ios_quick_access_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (9.7-inch)-40_ios_quick_access_demo_framed.png rename to doc/images/en-US/iPad Pro (9.7-inch)-40_ios_quick_access_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPad Pro (9.7-inch)-60_ios_settings_demo.png b/doc/images/en-US/iPad Pro (9.7-inch)-60_ios_settings_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (9.7-inch)-60_ios_settings_demo.png rename to doc/images/en-US/iPad Pro (9.7-inch)-60_ios_settings_demo.png diff --git a/fastlane/screenshots/en-US/iPad Pro (9.7-inch)-60_ios_settings_demo_framed.png b/doc/images/en-US/iPad Pro (9.7-inch)-60_ios_settings_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPad Pro (9.7-inch)-60_ios_settings_demo_framed.png rename to doc/images/en-US/iPad Pro (9.7-inch)-60_ios_settings_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 11 Pro Max-10_ios_accounts_welcome_demo.png b/doc/images/en-US/iPhone 11 Pro Max-10_ios_accounts_welcome_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11 Pro Max-10_ios_accounts_welcome_demo.png rename to doc/images/en-US/iPhone 11 Pro Max-10_ios_accounts_welcome_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 11 Pro Max-10_ios_accounts_welcome_demo_framed.png b/doc/images/en-US/iPhone 11 Pro Max-10_ios_accounts_welcome_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11 Pro Max-10_ios_accounts_welcome_demo_framed.png rename to doc/images/en-US/iPhone 11 Pro Max-10_ios_accounts_welcome_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 11 Pro Max-11_ios_accounts_list_demo.png b/doc/images/en-US/iPhone 11 Pro Max-11_ios_accounts_list_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11 Pro Max-11_ios_accounts_list_demo.png rename to doc/images/en-US/iPhone 11 Pro Max-11_ios_accounts_list_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 11 Pro Max-11_ios_accounts_list_demo_framed.png b/doc/images/en-US/iPhone 11 Pro Max-11_ios_accounts_list_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11 Pro Max-11_ios_accounts_list_demo_framed.png rename to doc/images/en-US/iPhone 11 Pro Max-11_ios_accounts_list_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 11 Pro Max-20_ios_files_list_demo.png b/doc/images/en-US/iPhone 11 Pro Max-20_ios_files_list_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11 Pro Max-20_ios_files_list_demo.png rename to doc/images/en-US/iPhone 11 Pro Max-20_ios_files_list_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 11 Pro Max-20_ios_files_list_demo_framed.png b/doc/images/en-US/iPhone 11 Pro Max-20_ios_files_list_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11 Pro Max-20_ios_files_list_demo_framed.png rename to doc/images/en-US/iPhone 11 Pro Max-20_ios_files_list_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 11 Pro Max-21_ios_files_actions_demo.png b/doc/images/en-US/iPhone 11 Pro Max-21_ios_files_actions_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11 Pro Max-21_ios_files_actions_demo.png rename to doc/images/en-US/iPhone 11 Pro Max-21_ios_files_actions_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 11 Pro Max-21_ios_files_actions_demo_framed.png b/doc/images/en-US/iPhone 11 Pro Max-21_ios_files_actions_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11 Pro Max-21_ios_files_actions_demo_framed.png rename to doc/images/en-US/iPhone 11 Pro Max-21_ios_files_actions_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 11 Pro Max-22_ios_files_preview_pdf_demo.png b/doc/images/en-US/iPhone 11 Pro Max-22_ios_files_preview_pdf_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11 Pro Max-22_ios_files_preview_pdf_demo.png rename to doc/images/en-US/iPhone 11 Pro Max-22_ios_files_preview_pdf_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 11 Pro Max-22_ios_files_preview_pdf_demo_framed.png b/doc/images/en-US/iPhone 11 Pro Max-22_ios_files_preview_pdf_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11 Pro Max-22_ios_files_preview_pdf_demo_framed.png rename to doc/images/en-US/iPhone 11 Pro Max-22_ios_files_preview_pdf_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 11 Pro Max-40_ios_quick_access_demo.png b/doc/images/en-US/iPhone 11 Pro Max-40_ios_quick_access_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11 Pro Max-40_ios_quick_access_demo.png rename to doc/images/en-US/iPhone 11 Pro Max-40_ios_quick_access_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 11 Pro Max-40_ios_quick_access_demo_framed.png b/doc/images/en-US/iPhone 11 Pro Max-40_ios_quick_access_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11 Pro Max-40_ios_quick_access_demo_framed.png rename to doc/images/en-US/iPhone 11 Pro Max-40_ios_quick_access_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 11 Pro Max-60_ios_settings_demo.png b/doc/images/en-US/iPhone 11 Pro Max-60_ios_settings_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11 Pro Max-60_ios_settings_demo.png rename to doc/images/en-US/iPhone 11 Pro Max-60_ios_settings_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 11 Pro Max-60_ios_settings_demo_framed.png b/doc/images/en-US/iPhone 11 Pro Max-60_ios_settings_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11 Pro Max-60_ios_settings_demo_framed.png rename to doc/images/en-US/iPhone 11 Pro Max-60_ios_settings_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 11-10_ios_accounts_welcome_demo.png b/doc/images/en-US/iPhone 11-10_ios_accounts_welcome_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11-10_ios_accounts_welcome_demo.png rename to doc/images/en-US/iPhone 11-10_ios_accounts_welcome_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 11-10_ios_accounts_welcome_demo_framed.png b/doc/images/en-US/iPhone 11-10_ios_accounts_welcome_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11-10_ios_accounts_welcome_demo_framed.png rename to doc/images/en-US/iPhone 11-10_ios_accounts_welcome_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 11-11_ios_accounts_list_demo.png b/doc/images/en-US/iPhone 11-11_ios_accounts_list_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11-11_ios_accounts_list_demo.png rename to doc/images/en-US/iPhone 11-11_ios_accounts_list_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 11-11_ios_accounts_list_demo_framed.png b/doc/images/en-US/iPhone 11-11_ios_accounts_list_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11-11_ios_accounts_list_demo_framed.png rename to doc/images/en-US/iPhone 11-11_ios_accounts_list_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 11-20_ios_files_list_demo.png b/doc/images/en-US/iPhone 11-20_ios_files_list_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11-20_ios_files_list_demo.png rename to doc/images/en-US/iPhone 11-20_ios_files_list_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 11-20_ios_files_list_demo_framed.png b/doc/images/en-US/iPhone 11-20_ios_files_list_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11-20_ios_files_list_demo_framed.png rename to doc/images/en-US/iPhone 11-20_ios_files_list_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 11-21_ios_files_actions_demo.png b/doc/images/en-US/iPhone 11-21_ios_files_actions_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11-21_ios_files_actions_demo.png rename to doc/images/en-US/iPhone 11-21_ios_files_actions_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 11-21_ios_files_actions_demo_framed.png b/doc/images/en-US/iPhone 11-21_ios_files_actions_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11-21_ios_files_actions_demo_framed.png rename to doc/images/en-US/iPhone 11-21_ios_files_actions_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 11-22_ios_files_preview_pdf_demo.png b/doc/images/en-US/iPhone 11-22_ios_files_preview_pdf_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11-22_ios_files_preview_pdf_demo.png rename to doc/images/en-US/iPhone 11-22_ios_files_preview_pdf_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 11-22_ios_files_preview_pdf_demo_framed.png b/doc/images/en-US/iPhone 11-22_ios_files_preview_pdf_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11-22_ios_files_preview_pdf_demo_framed.png rename to doc/images/en-US/iPhone 11-22_ios_files_preview_pdf_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 11-40_ios_quick_access_demo.png b/doc/images/en-US/iPhone 11-40_ios_quick_access_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11-40_ios_quick_access_demo.png rename to doc/images/en-US/iPhone 11-40_ios_quick_access_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 11-40_ios_quick_access_demo_framed.png b/doc/images/en-US/iPhone 11-40_ios_quick_access_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11-40_ios_quick_access_demo_framed.png rename to doc/images/en-US/iPhone 11-40_ios_quick_access_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 11-60_ios_settings_demo.png b/doc/images/en-US/iPhone 11-60_ios_settings_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11-60_ios_settings_demo.png rename to doc/images/en-US/iPhone 11-60_ios_settings_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 11-60_ios_settings_demo_framed.png b/doc/images/en-US/iPhone 11-60_ios_settings_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 11-60_ios_settings_demo_framed.png rename to doc/images/en-US/iPhone 11-60_ios_settings_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 8 Plus-10_ios_accounts_welcome_demo.png b/doc/images/en-US/iPhone 8 Plus-10_ios_accounts_welcome_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8 Plus-10_ios_accounts_welcome_demo.png rename to doc/images/en-US/iPhone 8 Plus-10_ios_accounts_welcome_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 8 Plus-10_ios_accounts_welcome_demo_framed.png b/doc/images/en-US/iPhone 8 Plus-10_ios_accounts_welcome_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8 Plus-10_ios_accounts_welcome_demo_framed.png rename to doc/images/en-US/iPhone 8 Plus-10_ios_accounts_welcome_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 8 Plus-11_ios_accounts_list_demo.png b/doc/images/en-US/iPhone 8 Plus-11_ios_accounts_list_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8 Plus-11_ios_accounts_list_demo.png rename to doc/images/en-US/iPhone 8 Plus-11_ios_accounts_list_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 8 Plus-11_ios_accounts_list_demo_framed.png b/doc/images/en-US/iPhone 8 Plus-11_ios_accounts_list_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8 Plus-11_ios_accounts_list_demo_framed.png rename to doc/images/en-US/iPhone 8 Plus-11_ios_accounts_list_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 8 Plus-20_ios_files_list_demo.png b/doc/images/en-US/iPhone 8 Plus-20_ios_files_list_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8 Plus-20_ios_files_list_demo.png rename to doc/images/en-US/iPhone 8 Plus-20_ios_files_list_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 8 Plus-20_ios_files_list_demo_framed.png b/doc/images/en-US/iPhone 8 Plus-20_ios_files_list_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8 Plus-20_ios_files_list_demo_framed.png rename to doc/images/en-US/iPhone 8 Plus-20_ios_files_list_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 8 Plus-21_ios_files_actions_demo.png b/doc/images/en-US/iPhone 8 Plus-21_ios_files_actions_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8 Plus-21_ios_files_actions_demo.png rename to doc/images/en-US/iPhone 8 Plus-21_ios_files_actions_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 8 Plus-21_ios_files_actions_demo_framed.png b/doc/images/en-US/iPhone 8 Plus-21_ios_files_actions_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8 Plus-21_ios_files_actions_demo_framed.png rename to doc/images/en-US/iPhone 8 Plus-21_ios_files_actions_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 8 Plus-22_ios_files_preview_pdf_demo.png b/doc/images/en-US/iPhone 8 Plus-22_ios_files_preview_pdf_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8 Plus-22_ios_files_preview_pdf_demo.png rename to doc/images/en-US/iPhone 8 Plus-22_ios_files_preview_pdf_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 8 Plus-22_ios_files_preview_pdf_demo_framed.png b/doc/images/en-US/iPhone 8 Plus-22_ios_files_preview_pdf_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8 Plus-22_ios_files_preview_pdf_demo_framed.png rename to doc/images/en-US/iPhone 8 Plus-22_ios_files_preview_pdf_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 8 Plus-40_ios_quick_access_demo.png b/doc/images/en-US/iPhone 8 Plus-40_ios_quick_access_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8 Plus-40_ios_quick_access_demo.png rename to doc/images/en-US/iPhone 8 Plus-40_ios_quick_access_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 8 Plus-40_ios_quick_access_demo_framed.png b/doc/images/en-US/iPhone 8 Plus-40_ios_quick_access_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8 Plus-40_ios_quick_access_demo_framed.png rename to doc/images/en-US/iPhone 8 Plus-40_ios_quick_access_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 8 Plus-60_ios_settings_demo.png b/doc/images/en-US/iPhone 8 Plus-60_ios_settings_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8 Plus-60_ios_settings_demo.png rename to doc/images/en-US/iPhone 8 Plus-60_ios_settings_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 8 Plus-60_ios_settings_demo_framed.png b/doc/images/en-US/iPhone 8 Plus-60_ios_settings_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8 Plus-60_ios_settings_demo_framed.png rename to doc/images/en-US/iPhone 8 Plus-60_ios_settings_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 8-10_ios_accounts_welcome_demo.png b/doc/images/en-US/iPhone 8-10_ios_accounts_welcome_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8-10_ios_accounts_welcome_demo.png rename to doc/images/en-US/iPhone 8-10_ios_accounts_welcome_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 8-10_ios_accounts_welcome_demo_framed.png b/doc/images/en-US/iPhone 8-10_ios_accounts_welcome_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8-10_ios_accounts_welcome_demo_framed.png rename to doc/images/en-US/iPhone 8-10_ios_accounts_welcome_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 8-11_ios_accounts_list_demo.png b/doc/images/en-US/iPhone 8-11_ios_accounts_list_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8-11_ios_accounts_list_demo.png rename to doc/images/en-US/iPhone 8-11_ios_accounts_list_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 8-11_ios_accounts_list_demo_framed.png b/doc/images/en-US/iPhone 8-11_ios_accounts_list_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8-11_ios_accounts_list_demo_framed.png rename to doc/images/en-US/iPhone 8-11_ios_accounts_list_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 8-20_ios_files_list_demo.png b/doc/images/en-US/iPhone 8-20_ios_files_list_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8-20_ios_files_list_demo.png rename to doc/images/en-US/iPhone 8-20_ios_files_list_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 8-20_ios_files_list_demo_framed.png b/doc/images/en-US/iPhone 8-20_ios_files_list_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8-20_ios_files_list_demo_framed.png rename to doc/images/en-US/iPhone 8-20_ios_files_list_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 8-21_ios_files_actions_demo.png b/doc/images/en-US/iPhone 8-21_ios_files_actions_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8-21_ios_files_actions_demo.png rename to doc/images/en-US/iPhone 8-21_ios_files_actions_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 8-21_ios_files_actions_demo_framed.png b/doc/images/en-US/iPhone 8-21_ios_files_actions_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8-21_ios_files_actions_demo_framed.png rename to doc/images/en-US/iPhone 8-21_ios_files_actions_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 8-22_ios_files_preview_pdf_demo.png b/doc/images/en-US/iPhone 8-22_ios_files_preview_pdf_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8-22_ios_files_preview_pdf_demo.png rename to doc/images/en-US/iPhone 8-22_ios_files_preview_pdf_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 8-22_ios_files_preview_pdf_demo_framed.png b/doc/images/en-US/iPhone 8-22_ios_files_preview_pdf_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8-22_ios_files_preview_pdf_demo_framed.png rename to doc/images/en-US/iPhone 8-22_ios_files_preview_pdf_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 8-40_ios_quick_access_demo.png b/doc/images/en-US/iPhone 8-40_ios_quick_access_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8-40_ios_quick_access_demo.png rename to doc/images/en-US/iPhone 8-40_ios_quick_access_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 8-40_ios_quick_access_demo_framed.png b/doc/images/en-US/iPhone 8-40_ios_quick_access_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8-40_ios_quick_access_demo_framed.png rename to doc/images/en-US/iPhone 8-40_ios_quick_access_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone 8-60_ios_settings_demo.png b/doc/images/en-US/iPhone 8-60_ios_settings_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8-60_ios_settings_demo.png rename to doc/images/en-US/iPhone 8-60_ios_settings_demo.png diff --git a/fastlane/screenshots/en-US/iPhone 8-60_ios_settings_demo_framed.png b/doc/images/en-US/iPhone 8-60_ios_settings_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone 8-60_ios_settings_demo_framed.png rename to doc/images/en-US/iPhone 8-60_ios_settings_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone SE (2nd generation)-10_ios_accounts_welcome_demo.png b/doc/images/en-US/iPhone SE (2nd generation)-10_ios_accounts_welcome_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone SE (2nd generation)-10_ios_accounts_welcome_demo.png rename to doc/images/en-US/iPhone SE (2nd generation)-10_ios_accounts_welcome_demo.png diff --git a/fastlane/screenshots/en-US/iPhone SE (2nd generation)-10_ios_accounts_welcome_demo_framed.png b/doc/images/en-US/iPhone SE (2nd generation)-10_ios_accounts_welcome_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone SE (2nd generation)-10_ios_accounts_welcome_demo_framed.png rename to doc/images/en-US/iPhone SE (2nd generation)-10_ios_accounts_welcome_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone SE (2nd generation)-11_ios_accounts_list_demo.png b/doc/images/en-US/iPhone SE (2nd generation)-11_ios_accounts_list_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone SE (2nd generation)-11_ios_accounts_list_demo.png rename to doc/images/en-US/iPhone SE (2nd generation)-11_ios_accounts_list_demo.png diff --git a/fastlane/screenshots/en-US/iPhone SE (2nd generation)-11_ios_accounts_list_demo_framed.png b/doc/images/en-US/iPhone SE (2nd generation)-11_ios_accounts_list_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone SE (2nd generation)-11_ios_accounts_list_demo_framed.png rename to doc/images/en-US/iPhone SE (2nd generation)-11_ios_accounts_list_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone SE (2nd generation)-20_ios_files_list_demo.png b/doc/images/en-US/iPhone SE (2nd generation)-20_ios_files_list_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone SE (2nd generation)-20_ios_files_list_demo.png rename to doc/images/en-US/iPhone SE (2nd generation)-20_ios_files_list_demo.png diff --git a/fastlane/screenshots/en-US/iPhone SE (2nd generation)-20_ios_files_list_demo_framed.png b/doc/images/en-US/iPhone SE (2nd generation)-20_ios_files_list_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone SE (2nd generation)-20_ios_files_list_demo_framed.png rename to doc/images/en-US/iPhone SE (2nd generation)-20_ios_files_list_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone SE (2nd generation)-21_ios_files_actions_demo.png b/doc/images/en-US/iPhone SE (2nd generation)-21_ios_files_actions_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone SE (2nd generation)-21_ios_files_actions_demo.png rename to doc/images/en-US/iPhone SE (2nd generation)-21_ios_files_actions_demo.png diff --git a/fastlane/screenshots/en-US/iPhone SE (2nd generation)-21_ios_files_actions_demo_framed.png b/doc/images/en-US/iPhone SE (2nd generation)-21_ios_files_actions_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone SE (2nd generation)-21_ios_files_actions_demo_framed.png rename to doc/images/en-US/iPhone SE (2nd generation)-21_ios_files_actions_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone SE (2nd generation)-22_ios_files_preview_pdf_demo.png b/doc/images/en-US/iPhone SE (2nd generation)-22_ios_files_preview_pdf_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone SE (2nd generation)-22_ios_files_preview_pdf_demo.png rename to doc/images/en-US/iPhone SE (2nd generation)-22_ios_files_preview_pdf_demo.png diff --git a/fastlane/screenshots/en-US/iPhone SE (2nd generation)-22_ios_files_preview_pdf_demo_framed.png b/doc/images/en-US/iPhone SE (2nd generation)-22_ios_files_preview_pdf_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone SE (2nd generation)-22_ios_files_preview_pdf_demo_framed.png rename to doc/images/en-US/iPhone SE (2nd generation)-22_ios_files_preview_pdf_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone SE (2nd generation)-40_ios_quick_access_demo.png b/doc/images/en-US/iPhone SE (2nd generation)-40_ios_quick_access_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone SE (2nd generation)-40_ios_quick_access_demo.png rename to doc/images/en-US/iPhone SE (2nd generation)-40_ios_quick_access_demo.png diff --git a/fastlane/screenshots/en-US/iPhone SE (2nd generation)-40_ios_quick_access_demo_framed.png b/doc/images/en-US/iPhone SE (2nd generation)-40_ios_quick_access_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone SE (2nd generation)-40_ios_quick_access_demo_framed.png rename to doc/images/en-US/iPhone SE (2nd generation)-40_ios_quick_access_demo_framed.png diff --git a/fastlane/screenshots/en-US/iPhone SE (2nd generation)-60_ios_settings_demo.png b/doc/images/en-US/iPhone SE (2nd generation)-60_ios_settings_demo.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone SE (2nd generation)-60_ios_settings_demo.png rename to doc/images/en-US/iPhone SE (2nd generation)-60_ios_settings_demo.png diff --git a/fastlane/screenshots/en-US/iPhone SE (2nd generation)-60_ios_settings_demo_framed.png b/doc/images/en-US/iPhone SE (2nd generation)-60_ios_settings_demo_framed.png similarity index 100% rename from fastlane/screenshots/en-US/iPhone SE (2nd generation)-60_ios_settings_demo_framed.png rename to doc/images/en-US/iPhone SE (2nd generation)-60_ios_settings_demo_framed.png diff --git a/fastlane/screenshots/en-US/keyword.strings b/doc/images/en-US/keyword.strings similarity index 100% rename from fastlane/screenshots/en-US/keyword.strings rename to doc/images/en-US/keyword.strings diff --git a/fastlane/screenshots/en-US/title.strings b/doc/images/en-US/title.strings similarity index 100% rename from fastlane/screenshots/en-US/title.strings rename to doc/images/en-US/title.strings diff --git a/fastlane/screenshots/en/keyword.strings b/doc/images/en/keyword.strings similarity index 100% rename from fastlane/screenshots/en/keyword.strings rename to doc/images/en/keyword.strings diff --git a/fastlane/screenshots/en/title.strings b/doc/images/en/title.strings similarity index 100% rename from fastlane/screenshots/en/title.strings rename to doc/images/en/title.strings diff --git a/fastlane/screenshots/es/README.md b/doc/images/es/README.md similarity index 100% rename from fastlane/screenshots/es/README.md rename to doc/images/es/README.md diff --git a/fastlane/screenshots/es/keyword.strings b/doc/images/es/keyword.strings similarity index 100% rename from fastlane/screenshots/es/keyword.strings rename to doc/images/es/keyword.strings diff --git a/fastlane/screenshots/es/title.strings b/doc/images/es/title.strings similarity index 100% rename from fastlane/screenshots/es/title.strings rename to doc/images/es/title.strings diff --git a/fastlane/screenshots/et_EE/title.strings b/doc/images/et_EE/title.strings similarity index 100% rename from fastlane/screenshots/et_EE/title.strings rename to doc/images/et_EE/title.strings diff --git a/fastlane/screenshots/eu/keyword.strings b/doc/images/eu/keyword.strings similarity index 100% rename from fastlane/screenshots/eu/keyword.strings rename to doc/images/eu/keyword.strings diff --git a/fastlane/screenshots/eu/title.strings b/doc/images/eu/title.strings similarity index 100% rename from fastlane/screenshots/eu/title.strings rename to doc/images/eu/title.strings diff --git a/fastlane/screenshots/fonts/OpenSans-Regular.ttf b/doc/images/fonts/OpenSans-Regular.ttf similarity index 100% rename from fastlane/screenshots/fonts/OpenSans-Regular.ttf rename to doc/images/fonts/OpenSans-Regular.ttf diff --git a/fastlane/screenshots/fonts/OpenSans-Semibold.ttf b/doc/images/fonts/OpenSans-Semibold.ttf similarity index 100% rename from fastlane/screenshots/fonts/OpenSans-Semibold.ttf rename to doc/images/fonts/OpenSans-Semibold.ttf diff --git a/fastlane/screenshots/fonts/README.md b/doc/images/fonts/README.md similarity index 100% rename from fastlane/screenshots/fonts/README.md rename to doc/images/fonts/README.md diff --git a/fastlane/screenshots/fr/title.strings b/doc/images/fr/title.strings similarity index 100% rename from fastlane/screenshots/fr/title.strings rename to doc/images/fr/title.strings diff --git a/fastlane/screenshots/gl/keyword.strings b/doc/images/gl/keyword.strings similarity index 100% rename from fastlane/screenshots/gl/keyword.strings rename to doc/images/gl/keyword.strings diff --git a/fastlane/screenshots/gl/title.strings b/doc/images/gl/title.strings similarity index 100% rename from fastlane/screenshots/gl/title.strings rename to doc/images/gl/title.strings diff --git a/fastlane/screenshots/he/keyword.strings b/doc/images/he/keyword.strings similarity index 100% rename from fastlane/screenshots/he/keyword.strings rename to doc/images/he/keyword.strings diff --git a/fastlane/screenshots/he/title.strings b/doc/images/he/title.strings similarity index 100% rename from fastlane/screenshots/he/title.strings rename to doc/images/he/title.strings diff --git a/fastlane/screenshots/id/keyword.strings b/doc/images/id/keyword.strings similarity index 100% rename from fastlane/screenshots/id/keyword.strings rename to doc/images/id/keyword.strings diff --git a/fastlane/screenshots/km/keyword.strings b/doc/images/km/keyword.strings similarity index 100% rename from fastlane/screenshots/km/keyword.strings rename to doc/images/km/keyword.strings diff --git a/fastlane/screenshots/lt_LT/keyword.strings b/doc/images/lt_LT/keyword.strings similarity index 100% rename from fastlane/screenshots/lt_LT/keyword.strings rename to doc/images/lt_LT/keyword.strings diff --git a/fastlane/screenshots/lv/title.strings b/doc/images/lv/title.strings similarity index 100% rename from fastlane/screenshots/lv/title.strings rename to doc/images/lv/title.strings diff --git a/fastlane/screenshots/pl/keyword.strings b/doc/images/pl/keyword.strings similarity index 100% rename from fastlane/screenshots/pl/keyword.strings rename to doc/images/pl/keyword.strings diff --git a/fastlane/screenshots/pl/title.strings b/doc/images/pl/title.strings similarity index 100% rename from fastlane/screenshots/pl/title.strings rename to doc/images/pl/title.strings diff --git a/fastlane/screenshots/pt-BR/keyword.strings b/doc/images/pt-BR/keyword.strings similarity index 100% rename from fastlane/screenshots/pt-BR/keyword.strings rename to doc/images/pt-BR/keyword.strings diff --git a/fastlane/screenshots/pt-BR/title.strings b/doc/images/pt-BR/title.strings similarity index 100% rename from fastlane/screenshots/pt-BR/title.strings rename to doc/images/pt-BR/title.strings diff --git a/fastlane/screenshots/ru/keyword.strings b/doc/images/ru/keyword.strings similarity index 100% rename from fastlane/screenshots/ru/keyword.strings rename to doc/images/ru/keyword.strings diff --git a/fastlane/screenshots/ru/title.strings b/doc/images/ru/title.strings similarity index 100% rename from fastlane/screenshots/ru/title.strings rename to doc/images/ru/title.strings diff --git a/fastlane/screenshots/screenshots.html b/doc/images/screenshots.html similarity index 100% rename from fastlane/screenshots/screenshots.html rename to doc/images/screenshots.html diff --git a/fastlane/screenshots/sq/keyword.strings b/doc/images/sq/keyword.strings similarity index 100% rename from fastlane/screenshots/sq/keyword.strings rename to doc/images/sq/keyword.strings diff --git a/fastlane/screenshots/sq/title.strings b/doc/images/sq/title.strings similarity index 100% rename from fastlane/screenshots/sq/title.strings rename to doc/images/sq/title.strings diff --git a/fastlane/screenshots/th-TH/keyword.strings b/doc/images/th-TH/keyword.strings similarity index 100% rename from fastlane/screenshots/th-TH/keyword.strings rename to doc/images/th-TH/keyword.strings diff --git a/fastlane/screenshots/th-TH/title.strings b/doc/images/th-TH/title.strings similarity index 100% rename from fastlane/screenshots/th-TH/title.strings rename to doc/images/th-TH/title.strings diff --git a/fastlane/screenshots/tr/keyword.strings b/doc/images/tr/keyword.strings similarity index 100% rename from fastlane/screenshots/tr/keyword.strings rename to doc/images/tr/keyword.strings diff --git a/fastlane/screenshots/tr/title.strings b/doc/images/tr/title.strings similarity index 100% rename from fastlane/screenshots/tr/title.strings rename to doc/images/tr/title.strings diff --git a/fastlane/screenshots/ur_PK/keyword.strings b/doc/images/ur_PK/keyword.strings similarity index 100% rename from fastlane/screenshots/ur_PK/keyword.strings rename to doc/images/ur_PK/keyword.strings diff --git a/fastlane/screenshots/zh-Hans/keyword.strings b/doc/images/zh-Hans/keyword.strings similarity index 100% rename from fastlane/screenshots/zh-Hans/keyword.strings rename to doc/images/zh-Hans/keyword.strings diff --git a/fastlane/screenshots/zh-Hans/title.strings b/doc/images/zh-Hans/title.strings similarity index 100% rename from fastlane/screenshots/zh-Hans/title.strings rename to doc/images/zh-Hans/title.strings diff --git a/fastlane/screenshots/zh_TW/keyword.strings b/doc/images/zh_TW/keyword.strings similarity index 100% rename from fastlane/screenshots/zh_TW/keyword.strings rename to doc/images/zh_TW/keyword.strings diff --git a/fastlane/screenshots/zh_TW/title.strings b/doc/images/zh_TW/title.strings similarity index 100% rename from fastlane/screenshots/zh_TW/title.strings rename to doc/images/zh_TW/title.strings diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index c902891e9..000000000 --- a/docs/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -cache/ -node_modules/ -public/ -build/ diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 314e19032..000000000 --- a/docs/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Building the Docs - -The iOS documentation is not built directly; instead, it is built together with the [core documentation](https://github.com/owncloud/docs/). However, if you would like to build a local copy of the iOS documentation, to preview changes that you are making, you can use the following command within the `docs/` directory: - -``` -yarn install -yarn antora -``` - -**Note** these commands require NodeJS and Yarn to be installed. To learn more about how to install them, please refer to [that documentation in the docs repository](https://github.com/owncloud/docs/blob/master/docs/getting-started.md). - -## Previewing the Generated Docs - -Assuming that there are no build errors, the next thing to do is to view the result in your browser. In case you have already installed a web server, you need to configure a virtual host (or similar) which points to the directory `public/`, located in the `docs/` directory of this repository. This directory contains the generated documentation. Alternatively, use the simple server bundled with the current package.json, just execute the following command to serve the documentation at [http://localhost:8080/ios-app/](http://localhost:8080/ios-app/): - -``` -yarn serve -``` diff --git a/docs/antora.yml b/docs/antora.yml deleted file mode 100644 index 32a158af1..000000000 --- a/docs/antora.yml +++ /dev/null @@ -1,6 +0,0 @@ -name: ios-app -title: Mobile App for iOS (iOS 11+) -version: master -start_page: ROOT:index.adoc -nav: -- modules/ROOT/nav.adoc diff --git a/docs/bin/cli b/docs/bin/cli deleted file mode 100755 index ece53e55d..000000000 --- a/docs/bin/cli +++ /dev/null @@ -1,245 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -o noclobber -set -o errexit -set -o pipefail -set -o nounset - -ACTION= -FONTS_DIRECTORY="fonts" -MANUAL_NAME="Mobile App for iOS (iOS 11+)" -EXPORT_FILENAME_PREFIX="ownCloud_iOS_App_Manual" -RELEASE_DATE=$(date +'%B %d, %Y') -STYLE="owncloud" -STYLES_DIRECTORY="resources/themes" -LANGUAGE= - -ERR_UNSUPPORTED_LANGUAGE=20 -ERR_UNSUPPORTED_MANUAL=21 -ERR_UNSUPPORTED_ACTION=22 -ERR_LINTER_NOT_AVAILABLE=23 -ERR_NO_LANGUAGE_SPECIFIED=24 - -if [[ -z "${VERSION:-}" ]]; then - if [[ -n "${DRONE_TAG:-}" ]]; then - VERSION=${DRONE_TAG/v//} - else - [[ -n "${DRONE_BRANCH:-}" ]] && VERSION=${DRONE_BRANCH/v//} || VERSION=master - fi -fi - -function usage() -{ - echo "Usage: bin/cli [-c] [-h] [-m ] [-l ]" -} - -function clean_build_dir() -{ - echo "Cleaning build directory..." - rm -rvf "build/" - echo "...build directory cleaned." -} - -function validate_code_files() -{ - case $LANGUAGE in - go) - if command -v golint &>/dev/null; then - echo "Validating go source files" && \ - find ./modules/*_manual/examples -type f -name "*.go" \ - -exec sh -c 'echo Linting {} && golint {} && echo' \; - else - echo "Golint is not available." - echo "Exiting." - exit $ERR_LINTER_NOT_AVAILABLE - fi - ;; - - json) - if command -v jsonlint &>/dev/null; then - find ./modules/*_manual/examples -type f -name "*.json" \ - -exec sh -c 'echo Linting {} && jsonlint -qp {} && echo' \; - else - echo "jsonlint is not available." - echo "Exiting." - exit $ERR_LINTER_NOT_AVAILABLE - fi - ;; - - kotlin) - if command -v ktlint &>/dev/null; then - ktlint --reporter=plain "./modules/*_manual/**/*.kt" || true; - else - echo "ktlint is not available." - echo "Exiting." - exit $ERR_LINTER_NOT_AVAILABLE - fi - ;; - - php) - if command -v php &>/dev/null; then - find ./modules/*_manual/examples -type f -name "*.php" \ - ! -path "**/vendor/*" \ - -exec php -l {} \; - else - echo "Golint is not available." - echo "Exiting." - exit $ERR_LINTER_NOT_AVAILABLE - fi - ;; - - xml) - if command -v xmllint &>/dev/null; then - find ./modules/*_manual/examples -type f -name "*.xml" \ - -exec xmllint --noout {} \; - else - echo "xmllint is not available." - echo "Exiting." - exit $ERR_LINTER_NOT_AVAILABLE - fi - ;; - - yaml) - if command -v yamllint &>/dev/null; then - find ./modules/*_manual/examples -type f -name "*.yml" \ - -exec sh -c 'echo Linting {} && yamllint -f parsable {} && echo' \; - else - echo "yamllint is not available." - echo "Exiting." - exit $ERR_LINTER_NOT_AVAILABLE - fi - ;; - - *) - echo "That language is not, currently, supported" - exit $ERR_UNSUPPORTED_LANGUAGE - ;; - esac -} - -function convert_antora_nav_to_asciidoc_list() -{ - local filename="$1" - - while read line; do - if [[ ${line} =~ \]$ ]]; then - level_offset=$(echo "$line" | awk -F"*" '{print NF-1}') - revised_line=$(echo "$line" | sed 's/xref:/include::{module_base_path}/' | sed 's/\[.*\]//g' | sed -r 's/^\*{1,} //') - echo "${revised_line}[leveloffset=+${level_offset}]" - fi - done < "${filename}" -} - -function build_pdf_manual() -{ - local revision="$1" - local build_directory="$(pwd)/build/server/${revision}/ROOT/" - local book_file="books/${EXPORT_FILENAME_PREFIX}.adoc" - local nav_file="modules/ROOT/nav.adoc" - local pdf_filename="$(pwd)/build/server/${revision}/ROOT/${EXPORT_FILENAME_PREFIX}.pdf" - - echo "Generating PDF version '${revision}' of the ${MANUAL_NAME} manual, dated: ${RELEASE_DATE}" - - mkdir -p "$build_directory" - - asciidoctor-pdf -q -d book \ - -a pdf-stylesdir="${STYLES_DIRECTORY}/" \ - -a pdf-fontsdir="${FONTS_DIRECTORY}" \ - -a pdf-style="${STYLE}" \ - -a examplesdir="$(pwd)/modules/ROOT/examples/" \ - -a imagesdir="$(pwd)/modules/ROOT/assets/images/" \ - -a module_base_path="modules/ROOT/pages/" \ - -a partialsdir="$(pwd)/modules/ROOT/pages/_partials/" \ - -a revnumber="${revision}" \ - -a revdate="${RELEASE_DATE}" \ - --base-dir "$(pwd)" \ - --out-file "${pdf_filename}" \ - <(cat $book_file <(convert_antora_nav_to_asciidoc_list "$nav_file")) - - echo "The ${MANUAL_NAME} manual has been converted to PDF format." - echo "It is available at: ${pdf_filename}." -} - -function build_docbook_manual() -{ - local revision="$1" - local build_directory="$(pwd)/build/server/${revision}/ROOT/" - local book_file="books/${EXPORT_FILENAME_PREFIX}.adoc" - local nav_file="modules/ROOT/nav.adoc" - local dbk_filename="$(pwd)/build/server/${revision}/ROOT/${EXPORT_FILENAME_PREFIX}.dbk" - local docx_filename="$(pwd)/build/server/${revision}/ROOT/${EXPORT_FILENAME_PREFIX}.docx" - - mkdir -p "$build_directory" - - echo "Generating Microsoft Word Document format (.docx) version '${revision}' of the ${MANUAL_NAME} manual, dated: ${RELEASE_DATE}" - - echo " Step 1. Converting AsciiDoc source files to intermediate DocBook format." - asciidoctor -q \ - --backend docbook5 \ - -d book \ - -a examplesdir="$(pwd)/modules/ROOT/examples/" \ - -a imagesdir="$(pwd)/modules/ROOT/assets/images/" \ - -a partialsdir="$(pwd)/modules/ROOT/pages/_partials/" \ - -a revnumber="${revision}" \ - -a revdate="${RELEASE_DATE}" \ - --base-dir "$(pwd)" \ - --out-file "${dbk_filename}" \ - <(cat $book_file <(convert_antora_nav_to_asciidoc_list "$nav_file")) - - echo " Step 2. Converting DocBook version to Microsoft Word Document format (.docx)." - pandoc -S --wrap=preserve --toc -f docbook -t docx -o "${docx_filename}" "${dbk_filename}" - - echo " Step 3. Removing intermediate DocBook version." - rm -f "${dbk_filename}" - - echo "The ${MANUAL_NAME} manual has been converted." - echo "It is available at: ${docx_filename}." -} - -while getopts ":hcmdl:" o; do - case "${o}" in - d) - ACTION="BUILD_DOCBOOK_MANUALS" - ;; - m) - ACTION="BUILD_MANUALS" - ;; - l) - ACTION="VALIDATE" - LANGUAGE=${OPTARG} - ;; - c) - ACTION="CLEAN" - ;; - h|*) - ACTION="HELP" - ;; - esac -done -shift $((OPTIND-1)) - -case "$ACTION" in - BUILD_DOCBOOK_MANUALS) - build_docbook_manual "$VERSION" - ;; - BUILD_MANUALS) - build_pdf_manual "$VERSION" - ;; - CLEAN) - clean_build_dir - ;; - VALIDATE) - if [[ -z $LANGUAGE ]]; then - echo "No language was specified to be validated." - echo "Use the -l option to specify one." - echo "exiting." - exit $ERR_NO_LANGUAGE_SPECIFIED - fi - validate_code_files - ;; - HELP | *) - usage - exit $ERR_UNSUPPORTED_ACTION - ;; -esac diff --git a/docs/books/ownCloud_iOS_App_Manual.adoc b/docs/books/ownCloud_iOS_App_Manual.adoc deleted file mode 100644 index b2c4b56c9..000000000 --- a/docs/books/ownCloud_iOS_App_Manual.adoc +++ /dev/null @@ -1,10 +0,0 @@ -= ownCloud iOS Application Manual -The ownCloud Team -{revnumber}, {revdate} -:source-highlighter: rouge -:homepage: https://github.com/owncloud/ios-app -:listing-caption: Listing -:toc: -:toclevels: 1 -:icons: font -:icon-set: octicon diff --git a/docs/fonts/dejavu-sans-bold.ttf b/docs/fonts/dejavu-sans-bold.ttf deleted file mode 100644 index 6d65fa7dc41ae8ffae77a4a843a73ba31ffd78c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 705684 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

sms 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%0A7@OA|SSm%P*^HlXor5<@yw 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 o#l5yK!`U{k*W& zUT^|YV6DdTQCy(FTFsj@I9_N z6uC!{dlb1xk+u5h&pWLddZ#sNIUc7qvN;}tV>_shi5Tf2A>b38jL=z&!ata@iV>gP6dwj-O<1H^h~J?iJU zob?R7vmUi5kFy@xD4Vk$*(jT{9@*UH&U$3y-&v1r{5$KB&E?^&M>glhwZozJZ?4sH zUYrHV=DfIiD4X-*Y)Cfe#o3T-&Wp1l*_;<=L$Vovt>;CyC9ybgNUkj8k;C_DuMO?V zdUVzo9zJ~j&b{|3jCoikO)Vqby7!Pa-gti5xYc`bEHB$BQo!=?-IHsQ+k?{%`(`h= zfxX}c_JC9CIUeBm0KcV~7Y=kB$47Uc6A66*NQmskWkr426RayuyuN+s_VGgNgPXg0 zabwv=^Wf&{;MTJ@efR3){Umz?KE7~tOUI+s zXK}L9#1R+_fO{Zs8pxXluswk70c;Oodswf}Yuk$B!)KJ}`N+tj2GBi#?g4ZUpnCw_ zgV25V==r0Y2giKh7uW#02hcr$?g4ZUpnD*r8bUa)gznknle6;(ya#ftft+d}ry79w zKu$G~Qw_j;Ag3CD`T*1ia;kxxY9OZ?Ao>8&2Xd+bq7M*#Ag3D0sRnYYft+d}ry62? zj7F1(0YK@ z1GFBX^#H90Xgxsd0a_2xdVtmgv>u@K0Idh|F74&k?gm|}CnDFqi_5LC7|Z`1-aD^b z)^GTl$?<|NLG8`Vr%w%kn$K|Gk&z8#WCI!5Kt?u@kqu;IgUgk}KxW1+Z?Y|L1DTn2 zdD~rfvQ_MQ^TZ*ZHhR10uIEo~-aEQ;^L;)N!U6Vs(_gaGvEQ3)PL@=2iWyZ{TAGTcwitN z7>Ebh^=)9+H?`HV*|koe_SMI(b+Uc+t;yln6hYaqPVf63WWPGuKKt6Q?iDIy z!c(bG8wh~P7>zwSmyMS1c$jy6q&R$;SufcM*nG7 zRJN59XdQ;IeVJ^ZdA2K)%~4RJT2o6}lZLF(oi)0%CJkAmJ8N`jjqa?`oi%C5nlxlh z8nQ-l)?P3X@R=PHXH5pOMse0C&YBEljpD4i^VZyXY)hp8f$?!A^H`b!1J47GGYve> zq}F!`k2A^k70TmGvOUi{&LrE%tH+rh{wl2=FaNdEt>*@oj@CnmJ@Vjy0tO!$4-Uxo zHP1#7vaLC66d`+~Kd8d`Cp`F6u~URTP?rzrnm;`RFJdbs@tE}J!SVqW>CZmEAy7lo zvj@uuwAIzw2RH@_NP75S`9OZ)0~~{Yr6yMv9-cl>6lCFLw+Pwo5cWRt2Z%*^{6HUI z7APX|_<{PZJbL_KIfC(lKfniL7^52R200b)@$qR}7=R-~>i8>NbAn1e40l_FwA}9jnG!TsPB!c=m3xuLPiJ*R8T0Ds$n*$&u zxHs zzubnkd|gj`_${4sKJ&m8ObpBR6$-mKImEDRU!gFZlY>68eTDitImEC$_Mm=F4lyi` zJ*eM$%k~wrIXT3zZTpHlS8v|F_MBCLiW84LC=FOYq&mz4@8h4f18rZidhlKDPvX?& zp@^H$JorA|iBp${B98C*{hRl+ZTj`YRr~*ku0Q8?DFE|Ggj#%4s*04VBC%Y_WHY#_n270P`qx;$GU+oH>}6|$N9Y46-y zRg(+r*2HD4aryJdN?kp>P=bdm^hM4-ap|(z$GxQ*b#lj_r%uIYA4xc}E6p_Mv1BND!B@*@ybq{{GEpd^{AW*!)B9qfHd3;{M(_ zCl7qw0ZF7T757V<7|P~s8KM*tNhBI&%MkT5?L?z&8KVAl{^-^VS5Gvz_m)==x+EfH z+Yo)0Lm(pMF&6bR^F*XP)}l0)`;Lf|Z9~)_ca_ZDvQv+app>0M^bsz8g3?G(8mU4> zg3?G(8VO3-VMLiT(?C$l4kPMk8VE`wL1`o?jRd8UpfnPcMuO5vP#OtJBSC2-D3yS< z@r*1jjVvvVgry^4=}3Su5|)kx2qR(XNPsXBmX0hjjfABmYx5&v>B!pr$lCl!crX$k zj8v#bDpVsCs*&hm^e@Wjf7+) zq3uX$FcR91ga#v_!N~g3NN6w;>W-ADM#@wpk-LP zjRXcGfx)=mJAYR-sl#Uut{tjo_QILb-92$(dn7a%sZ)*XdhQwh(@qrX#?bFPU^qS; zSza0m3wZEr;K47o)}E0{)ks({5*Cb9sz$sZ@VOAj?mQz8@62QZ9 zvN;D-uy|Nb{RYrT1#5)H$mFcJZbL;xcZ zz(@oz5&?`v03#8=2>C~{!;$Q8Bs&}d|44Q?0{)Tga0L7#+2IKIN3z3_>~JJI9LWwx zxIdB|j&Og3`y=(Jk@cw&?vK=?Mz}x1{gH*K5$=!hdxYO3b)}I7rV*+oBt9YW35icg zd_vk2(w>m^gtRB5JR#)?DNjgwLdp|To{;i{lqaM-A>|1vPe^$}$`ewakn)6-C!{j!LPe^!TogtCiC4M51_=!M5zZ3eMNbM4-U1FUfk=i9vyM%@(G(3^oB~}>{ z8lKSbgoYO_i=ND&e#LLx;-qzH)= zA(0{^QiMc`kVp{{DMBJeQ2C>UJ{f*w_>tj9h98*}A;XajM=~7Aa3qr=WKx6-S2A45 za3#Z)3|BH-$#5mZl?+!hT*+`H!<7tIGF-`UCBu~rS2A45a3#Z)3|BH-$#5mZl?+!h zT*+`H!<9^mkl{;)FB!gM_>$pEhA$buWcZTdONK8QzGV24;Y)@u8NOurlHp5+FB!gM z_>$pEhA$buWcZTdONK8QzGV24;Y)@u8NOurlHp5+FB!gMa)b0_NGJMMLDZ{4>pE7*P@F~Nm44*Q5%J3<}rwpGme9G`C!>0_NGJMMLDZ{4>pE7*P z@F~Nm44*Q5%J3<}rwpGme9G`C!>0_NGJMMLDZ{4>pE7*P@F~Nm44*Q5%J3<}r%Zm3 z$qzF5L55oyZe_TY;Z}xQ8E$2`mEl%~TN!R;xRv2nhFckKWw@2$R)$*{Ze_TY;Z}xQ z8E$2`mEl$wov9LL)~z#D!c3Jg^Fy^vl`zBY%n#Kv`BbJ#nE9bvW_2&a_YB`N ze9!Pb!}ko|GbO@Ii7-Scw!=+n20AP;)#iPVj`ZHh$klEiHUe(BA%Ft zCnn;FiFjgy-xGR2q4yIyJE5}^Iy<4W6FNJgvlH_erw!NBhU;lVoVy{; z-4N$)xGy%`7aQW-4RP*<`((p?vf;Yia9wV=E;n448?MU@*X4%ma>I4G;kw*#U2eE8 zH(Zw+uFDPA<%a8WLmayyj@=N)Zir(y#IYOV*bQ;jhPY~<_-CK^XP@|IpZI5=_-CK^ zXP@|IpZI5=_-CK^XP^6FpZj5-_-CK^XP@|IpZI5=_-CK^XP@|IpZI5=_-CK^XP@|I zpZI5=xM!cZXP>xdpSWkA`*EN9ai9B<&B*k#Dc5D$j7+xcvTQ~s+v{>{Mkd=OVm2d_ z?WG|$Ba`h_SvDh+?WGAeBa`jAFq@I-=UVvZdSEj$^*ipJxgOY*O#S%hdSFvB^?S*l zP03_?b&O5PWP4qZP0950Fz0=2N+#Q@V{A$$8~@}zY$l|iclr3)3`jro@_exwkZjKv zoB3$Z&*n#M8>ialM{WC_+U7@X`<~iX|5n?^skW^_sBQDCwvAJ5n`gCcoNC)Vt8L>{ z+m2IvjpE-tl8t}!NH+e>BiZZP{5!sojeo}%`nPTH@AyJC{vBV) z#=qkW+4y&SAshdWFJ$B2+?0)f#}~5k@AyJ{tK#4Bg>3vgzL1T7#}~5k@AyJC{;j97 z@o!Gc#=qkW+4y&SF|nzv+IIfcHvekd`B&Tgt8M3BZS$|Toqx5>zuI>G)i(cX+xb`9 z{Htx}Uv2ZRww?cpO=Z>M-}5gU|DJ!@`1kzF#=qxZHvT>TvhnZvmyLhVzij+_{wFq< zRm=E2|FRjs=U+DC_x#Id{GNZ=jNkJwoAG=8Wix)}pEbUTH9oce`>gTF_V-!Cn^?0` z>-l1hPPXTVH9FazAFZk_`S~=O7ivA8(Y%n2Kl4Ji$D^e+zUT3b=7rwpc+3mg9uMnI z6YEZD`JCgOiFG8k9H-+I+4jSF$HaPvTKi4bYuWyLtbfS1U)Db+)<4vGUReK-?dyp3 z583w5`iE>^C#-MC_I1MghHPIatY=KDXQ=f&vYsK^^T>LJY|kU>8nPLW>rNBv7HT*ysj(B=6GFKlI`=#x`k}!&vhl)jK_5)*^I|^CE1L}dBeoIg<6i^btBmvzw1V_ zIeyoTWHbM+6HTm3sAW8^6Uk;gt_#WL__+>QSD08=P|N#{$0zFYYJDA3mzT}B9cRmC z++26m+ttr;I=-H$vrp8+)pES9E6C<}U00CJ{JX9oo8xm`K{m(Xx`NUP<_$fd-m89& z4?UpXtA38p4<=~?f_Nz|k0|NYc=jrty~<~=_Re0#OX2Jrr?1jkUp{-aclIh?TAzL6 z^i?|R%V)3l&R)e!!`U}ZU!}9YeD-SZ>{YzfpMB%>RXXd-XRr1y^*eg%>G?%&v?)-- zq1tv@)V9;Eww)HW?Y2|fPK(-h*{W@)MQxjYwe7U1ZI`XuHvMXwjHqq5h1xd#YTIq0 z&4}=C8Ig^D%ZP0JTSjE#-(jC@{5uVhjepCGZ2VhhWaHm5qfL%HbP`1ZUnx@Thc%Sn_nx=jq=Z1OTFz=*i z+Dr#O%sc6s`aOQqGua;hhI!w3nL+(N?;GZS!~AcU{|)oMVg5JF|AzVBF#j9of5ZH5 znEws)zhVA2%>Rb@-!T6h=6}QdZe_W5ToS8c}Q^Uq$cvhAO}TxHuod%4QCfA(^f zZU5}$D%<|q%T>1hvzM!E{B!=*7q~J?#TB2tX+4`ooGjh%a`5s zZU#4NH^EJGGrCD`vYS0Olbem3eK&Y0wrXp7`|Itmx4+*0di(3`ueZP6{(AfC?XS1L z-u`;~>+P?%zrl_MI~we0SR83v>*YN)%X?~;_tfy-auBkXgHW>^gqr0b)GP;~W;qBo z%R#7F4noax5Nei#P_rC_nq|=1KHL7)#8?-F@YlD_$e{ImR?5_=4mi@Is%d)>V zXj%5x+g}^IcI)=|+Sp}ze1rWB9^YVpgU2`6-{A2L_BVKZgZ&L2-(Y`({jEJd?LD-N zcJ1-4?Qd;=Yx`T<-`f7x_P4gbwf(K_Z*6~T`&-+eeuKDp2=*83FW8@cN!{MFzhHmC z{(}7l`wR9L>@V0~u)kn`!Ty5%Mf;2P7ws?FU$nnyf3f+~KLolQiypsl(>&;3eX!L$ z=!uJFc_@0$!dCm!%a6jBuZQ-h^{+hGYKNS#yNjz&8geek0Xezd>Q{>C;h`WG9TztR3i z`y1_Vw7;?W8$C<<_382j{rJ>P^EZ03^wZPU>GM8%vXqv(v-wj}YODFv&rWSMfBMm> zE&KC%SGMX-`}28M>e@Pe-t}WsTWx$wV{O@==TkYXJMGW&sUMlT)BZf4`h}@G?a%W$ zdOklH>m-d>!wAbb{?X~ggr#=2i^QRy7*lPatvmRT`pMKP1%l;<&o9u70zsde4`hd!Oxnw)ffI zXL~R9UKa1mvvh8-mSa)pax7|=V^Omli<;$F)GWuMX7R3O@vdg^u4eJBX7R3O@vdg^ zu4eJRJbCBF{@T5%PWx;3rY!qw_ogiSYxkxs`)l{6Ec%TGU-J1&_LqGA zlKmz7OCDdcKW$FFoWE>;+5WQqW&6wam+ddxU$(z&f7$-B{bl>h_LuE1+h4Z7rajv0 z*#7qHuW64qKK8LRosi}Cnoh{_u{52Kp=p6EduUo9%O08* z$g+oh&*#29?0Y`-gw%54_C24oCvvu<*(YwcquD2Jwxii6uE~rxllF<5?WoC&I_;>* zj4V59GNav>n^JUkk`(*^JUkk{!;j;a;fLxaSwMbl+2i78SSsqx6 z#ASJ4EfSYy51vRp8PrB(xz}?+wmgHDdp#87PLIfPuP1`sX-Ag((Q-dp?j4G0duaQ! z+%Jy-EvL$Iui)$a^_yRIPa9+1zV+~|E&LAe9^G9{*R%oY>Z^}#>KV;1K0bKiaCcuj zhnzgW>|nCmUp~9qESsgCpKsqgxOPn&+FW?+$*pG(j!&Myd2n*?k@@z?)tk%i0WTb0 zSSpLU<}O`3xO@0ex%KQNZ7J|*D>sh$k~YA&d3g7(?Y;Tbex!X#lBR7Ec&&Qe<;-h6 z%5c`de5QZk^Oqm^{AvI4-P3-hiJ$b{eLlbRpZ@)Qy-pAGpZ*=~qI%|Qd42jfm)C1P zudDu(UY~w<=|BDa@_LWYFW)_Vy`=ML_v-OMdy@3_z15d4leN0J{Qu+2l;|H2y`*Q8 zZfj=m+@NRg6FrX}E&DMYK6g5e%d~3Gr%#$@*?SMZ-|HV+`DA;4b82_juHRmL$>QC+ ziCwy?bNHzTH}BjyxO7(!kKMdA9|J zp0hk%y1HDx%cV!p<`X@u_bp$0lznuyNm{#fAAiruvSFki3_I;v{;*cRb9?(YUjF_o zAOAnw?teet{jXPdfAYsqul{7){qfV?|GeJ)kB@Z!ecS!FH+28?wcUT&cK>z4Nh;zH9Zd4|E@U z|ISB$_f4yhzN`D_m7U*xQ}^$;-M_ov{o8H#Z|--$bD{g4_wW4nw)@xjyMOgk_b=l8pRw(b7uw)-dB?jL`o`$yaE zAHJdc2ixvfe`R;|tK05ZcDrA>vh&Now7dG{q5GxX?(g64e(_zeUj5>>`+N7hU%21> zyo{gUc7J!<{he+1x9@j9_p`5A{oM8LXJ6C(%#Am!e&%}j(^o$JQ%|ja`hNFQPjx@3 z5C7!-?k9fyiPcZMy8H1Tdu;XNPjo-_Sofnp^2q8(-`M@gBi)C8_^H*0?{`0}5B~5| z-4FfX6RRJ3Q}=^E@XFN>KGFTaE4%Mk|M!1-_qU$tzVAaHS$*HO`_Kp9z538cx(~g7 z=Yt=3YW2Z)cOSg6^MR+jzq#%H#`W%dzxJ`!_inqt{`&5FzWeFb_uTKk``(kQ?|!=b zuJ3%^>br*SJHO-c)px$G`;KpaboCvNci;YP7gpc?X!mUwy7$Y;`?uX++jf8TRozRk z?EcEO`^($zTesc&KCOH2Yr7X;-Myf{^TPe^M9s@5%12n!7J`$J_2bZ|d%B zyW89D*0y_ox4ZK3o7?WWZ@IX7Zrgp!#qL|K>>O!oj_!9i^o<*D=$=*c?EUVUZ@92} z=Jnm-w!41qBdhD%?%LINudaQhyY~K_tM7bjb@kod)hjy(+wPmc>C)<(x7{~g>b_B5 z{KhwQ@75RJ{l@MaE_7e7j<0`P_jPK%Zri;}!++PSyRUt$d*@T#U)pwGv+cfm+kMrx zd&jo>i`(w)U-{JP?H}&G@~Q4C9_zk*+kM%#d)v1A()-<)-0$9czI*GHomzfjRkvOF zg>K$|ay8%Y_Mhzbuk38Dcbh9a(@WizZTAm}1!}(FZQbX8-o@4Dzoz@V&)r>p-o@^7ce^(|);;w( z7gtXW-RE5FKKrvCTYdHu-Df@4edeQ2tv>U9_r{m)uHHCwpYfXRGp_8s;nR0lZ+Nu( z^xf|D=eyTm*?IDnPp_W*aQ8ZW^mW&}*KWJlT<<<@+r9c#-K(CsxO&y&-4hqPPt|8W z^~2pO^_f>b-95hT9@B9=w(TBO^XOCEMKu@S*1h62-Gy!U$hO-(zq{J~aCd&UJAY;8 zq5Ivr>)lEpSv}qDTzOr0QLnr1V)t?#)yrSgy==F8*_EA7`Lyn3zwxr`-}S+lz4=e+ Rzfbv7^85eY?Wcdr{{>0xQq=$e diff --git a/docs/fonts/dejavu-sans-condensed.ttf b/docs/fonts/dejavu-sans-condensed.ttf deleted file mode 100644 index 3259bc21ae08b3d72464486694dafa47d3478681..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 680264 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

S5Gt)O+<6iT67TkqKD`$`imiA1bpYQVuF|~ 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 z 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 z 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 U*;&G?WOTnu&3K9% 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

Q;I?})2Bdx8E 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¬3>(zox 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

9nyP&)%1+Th)t@ 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%0DpUJh6t z${IOdZwIUzcp)4Q-|A(H0Sn@=J`UJQl-(nKj(V{dq2sM&G14Bk6ELj2?J(g5;Kc#f z;DAlCjbYzQO@LwFRmU3z*moS(-vOiiv{n&0Yn@)Y2R4kuUcLu5gTpY_>-2oVAMT+? zzFUlS9Y*xDvadK`6MN6+<+U0djRS2 zIDeBLDW)EWvBL9pRsHSPP}c5lRDVzpvN9IXxVyG?Oa128UfW#1rIue$soK6_TRU}P zIjjTz<#dYs!J2;jm6wmFS!w)8cy8lG#mA5Xi<2T`!J#oDj}GM+@{Tc`e>R-pJtlr8&V-I(X%{iV0v86Wp~wkrz7*|1mWV9Oyap7j?`6CyJc1j*+k zlaD4q=}2nQu#qFao`EbJBiUv)WX9JcMjjo1_`6A|MJ%QBljA(1BUxNc+>8ZGa`xmL zSlL!$kv*4fRX?sMRd4_B?`65+;wLW-A0R5~IUk>AH;^nDooZLA;Wv4aJ<**I{n&j} zCM%FVTBeGlS~kA=s=Dw6Hb)+S0Pv6MIa`Bzo<%mGJUKQ0Mw@8G0Z~)8^OnGt>1x59 z8|@KHP#mJnS;%|_zR|Dj@#>k`843k}=p_c-ed6-r!-plCI%{5VuzJa2F36pF`02wZ z)mzNvB>vF&-DxW^?t}eH0pC$RbUlD(`@p*WG8m-(Hu8A~?Xl7{9~J3=9BIfBn6&G5 zczCbT)@<_6y7Z|VJ-uIj^1_UW^w!aj%mxqP)DXVyA`k1j56AEv< z-||Xi(?)Fcs0+n~EM()9kS1u^l+jNP|7Ov&(}SCacOP2FeBM=Wt9K^NjEq#zpL|Vz zR~;3aQepp5 zO%3Ye<|87THn~^J$I7YWryqYe(R<_*)CM1%{0ShPU_aqqSgqkY1C1<%leX5n9#5Nkrli9J|FG27QT)9nPilPXLt>KxF~Pe_tF8UBcM3iJBI zBQ@z2DX`-!ep{rcB0SPM#YRdo5pm&OPH7&_9pb~{ybT?~<6@A5GeGE&hCS%CqiM}Y zk9z0iX#c%(XotefL^0Q+X;*GGAN5p^VuRvb-gVls!v7SyMLS%uL|M~ma3Q^-H9d>Z zcv|XqBW~;FH{+V{2YJM}Z`Nt>=abbt>c;M0Hw;4~*};az)v(Zp$)6AId0_a$*AvCy zdeujyuU_t_o=SUl!SFZgdSNC}em~+K)-fd9&**5kFs~zX=Je=6 z{lVzTFy#^S2kf{HMgk(?Fu!=m-%*)6v2}NjEkuZQp1MdJqYhI=_05UW5OGG!cVa}# zs;64A#j2Kl;)$m?tpLz^0JJDZVzJV=f^kK3gkJcY9?)my*|$|sWr*s1F!$!6+*|5` zce8JaiLq@`*KhW;O~j*u^FUVTKhb9acs_P8{KrK^pWhLS z;c3rEsI43E7j$s%xoK68UX>+FDw+CP zfM0CS*z}YkrQI@Og8j47A808Ei$=0`Kd;az)fz8;@%->XIo9B)oQ5rPhR#gO2u23X z2dhSQHm7y1nq34Xqou=h5}Q z&8f)jI(_TB-bD%NV*hOakm$GoU-siN=3Y8J__dq68-@(&{o3VS&%bwJ{DjLzNn`6v zrjM>=QRA1E$LHjvba|5EBCzPnqtH16BEg-={y~4K({LdH7PE>rnexdCr%Zgw5~82^ zlopj46M;^RXVQb}BgaPNDC!ezlO(lB6DGj)7}qq0Eu6yGl!al9Yt%n~QlFl{zF?CS zC1=zzX>f^Ed8gK=lTU5!DAur`&+<{evA&}otizNf4IZ`MfUGfO>zZr>lxSogiRLi` zggCrN4-xCoeNJm;Iwkex7i?N6nXi?U&I|! zHKu;jr07m{h3e$tB{Qa!juh*@Xz5-S6>NBTUFdFdEysNRLgXVWWm8A%?ZS>`cK2Oo zaLba;N7b%X|5Wd;?Srr{`2a4E*$O^RgDKx8ZVL0kKffrtYrzMG7@>5`AyycnWR`A8 zXRe>Jxt}uE6X_dz%~fBRt0vQXuMO#JKsxK5zHM-__|6frPs^?&8;;P;&-6O7VZ(+a zwC=>2c*8@`9Y}D7mLPb zU&Dh7<}ufG>(onY)qkyjoPGRDa9mcSnk}g=%N`#6Z+^Mw==V5KXB`Cc3h_Auz$zes%$IEi;4N=J6G4k;ZarSqsP>LDNfkO2&Xs%xjVEc zV75eidBz~|verNNjH`Z$O@(@pd~Mj}Q8~HWzIa)^%tE%F+m>q`J-Fw<(r)El`q!y3 zol{=fo6yVJ(Zh(VsJ%U=9%2jFq>rb%z5Sliqa&GOOQ^AVvoR#L$D>kDkH|UAzpaP> zFLwJ$H{M}x^P_i(rsgF}FJM2%{YC)TS+!#l91di5u-`0_`36c!eKsd+I^d^|fd$W4uHVDsHf znX?t|BS*3;k5uOlPV_D+TBJ6Rr@~yiM07e56H)|SVqBL7qy7-G3}o}|_C75-Gs0Iy z_u-aZviv34x7p&ibANv)_m6s9Dz>y~%meD4gX&GP|G&fj|E3;pm^*c2zq!-Y9zTBf z5A~0c(}#)?`XnFqg!^u|+f~4X2&<;Sl)SN3GoV7(UELlx7^5r?42F?gd5vC{$){_yLEoHN| zy!xs}YW1g|uD%zK5g8MfqRsh4i4chz;YoK}l+HF4+4%H50?I)XqrdLrIu*7`dl^!3 zE%&Wk#G`z-K3xPTpk;}cF(t$m-imtd(H4Md;48KV}V!rE(>6RgPO8NPM$n6cXZ#{ z6{R=SUzz)j(g&rFl9Cb<2X}X!t-5vfsCr@X(;HY;YD5-eLH_UGdE1cLVpXpdMa(=u zGpa=WPe{P;?3G&>e!LydfoMkD!#~~?{4~po>yiA{X13zsi-G@8uUjfo&TP1S@&BT&x6}(cx815rgMwTP0ZUde zQ~DpS-SYebT#aG##qR%y2J`vovNDkSQM4=1qbx=*%;Z^C+C{TR6d2h!nuL^rV=pdw zYt^c^ru{r&?DzB4RcaO6$7am`zWJ=L@4kJ$%Hl_hx)k0WS6Ep3C>+k`^uWR04C$_y z_p9nj_0+50e6MOR)?bN=iv4Y~^d;>)3W!aCpQTvD?7*0ILKI-*0Uj*AsZXh}WBV9mnf5UT%Ym z72zuW_#H2C5GaGHGpKsux2yi!gx>@Y{sO!1F3AURTUn4G#UBs?JyTL?PS(OJNGyP* zFP{n^BOag7XL50ASYTaUU>IARl$e;55}%kPZom8OKbP*>wN#$&9n|pj(|v=zrI7`F zJ9w#Q!~9wf`$wqXdUZfVg?V&*U^7l<_-&a*W0#MkrO-bU)<+iZ&-~5kpphflqh#cs zroj~Qp(7RFcSyKt116+<@7~=jdiSol!kB8ia#d|%lJabu!4Mj7iw46TA46vIvMJN2 zO`SSz`jj93Q0s2pV!Qt!|AEH(4e%HU{@@WZOjN!!uk%}0klGz{mNwr1Ze$bh%48(A za&~o(3oh%!3e^w1*Y|sNd&`&q8|$o_=r_9b^T-i>y!`y9PGGM$KU(Qu@t)#zf9|>N ztwioAoRRP}uwMN0QuVYlTfU4F(}!_}_BTyG1?HqcKh30<*lCW#xp0Pm6*wEcNclsc zSSc1T-rDNHvFAjyDb@q@kL!oFSTJ#beNWmeqRkC(6gg?LrU;~MC9SNTCQrJbh4V2& zDZ7w%@saISQAyG15lQ|*qNl+a7ZYKL2=EFFjz~6Jlgr{m5@b_=*ZTc^Ya`*%hpLG` z9{p;1MQT`VW_&_`F`{EaacM+gfN#{;0lkt+gG1vwWp#?kUfyTx3y0#B3#BcuvXLhi zhR4LDL?rlzIm2lPj;Si0e)Y?bm#*p*RuB_!F_>dKmUIirE~?qilGR0@zg!fTkrNng zDfjGX$_>fQ>9dV_h%5YkI_2br<>yCrj*97+K5Xrbs$~991{2(D%rIN|Q^4^LX9P|) zX*rf*v{KM+8OX9~QZXsCa?pgC6_Z7>LbiENW2DTNW`&5qYBJ^vGTQX#^2gdk3Q!-* z$;*j}PDsf2u1-iO@0Kumcnn;uLCKA?GY58?ID=)YyE3D~0(_Gas^enQQm2>p zsJr>mnu7lQ$7jwhHODNp-t?GTQI^v%bkYAu+j{^;U2Om2JKwUKP2Js8T9Qp$0wI+o zq=yy)ArJ_J5FnI<-g}oSMNx`0L9fU~DIy{uaxHj8L_~@riWEC~RU&c~6_VY_`<(gi zZW1iN_kZvISibqzIdkUBnKNh3oH;Xdg;$t4Af(07kNE}1$q;M#HYvqwO${41w0q%# zRpUlR_6}>Ff1zL`+Rr)2J+hrrpDyOmg{{vVEp$C?@8SFUC z2dhKsS}5PpF~_k?T3Fjw`oa+o)`ZukPR)W&f{9B8-5t(Wnj$Teh;YtO>ebi>`QuZ) zeY$m={8ZQk{y6M=V`f&Jp16IFQQAkg$&B8?`328E?zo~4aO@dgx%08`UVt44p;Qmp z%>g?tB^X1(lw9_u?PZR+ZZH-3f_%)2jHC`Y+ z){<&Qv!Mp}hep$-&_5uyYx(O})|VHwZP7iUy&=lt5#6aUH#h(M$k?D}sNZ(c$!jXM zJ@H`z7lmI;1h_WjSGB%n;9njuAQGZNu9Q*)SKC zkNyhrVbuclXLtc63u0BJ@8dTLC-n;J53s~ zcxd^WlMl}wzj4HCgMR(2Hw?H$yPgus zdr?G4h@UBM+hg1E7CgFb;-Pj!+D2QmjOE@vgJawJ`${LyPoBHE>|vIdGpK!3cyLHb z$L3*Stt-YqdU(#Pq=b2+tGa|`Wc4dI$s4~~G&d@?OJZ_->!r>ARq4|yAU19=R7vOx z^*0dfB@Jb4A!d%SX9-Q3fv^Iv;w$zmTu^S5|1VsY61U&vX4prDiOte(a%M{kCqEk{0!OzFRe4#(!t!tob^x zuKjt?lOlhe1+f|ypdDB#_BRI0DNqLR6W)xjZGmVjMlzOT9HIjkm^AqWRz(D&V{DZ4 zjw7S{(HR#W#J>wOj&|qUj!`rsx&GUn?ChNBQ}c3jwa7&LKf^1Z+2yF+^-SgPoz5Ud zbzS@QWBJ$?y5CbzmVp;vLz~AMImi%b>exYoMMIB<9Kqx+J0NX;*)ft2U_VRm&*7eq z_H#7RF{kz=wWzkO+PU_16iR`9t24saN{p#UYuD+-{)B1mNhfcgW(|aTK@bD<)>K%{ zbxs#z8kc&cPRq@mmV385G7CG#;s%8CzK$=C@k;~3_4`?yef#*w`=kdPOXbaz&rh9t zUOHgcN&8Me`e|-f-4ggXmWfO0@>_iW$&;*~qw+NHVXz46zfsR&3}J!plL2nVv=CGa zve9GmLrhdyMCeRS)yS1d?}>q5W{@Q?KqBwX7(BA1Nt@Xl?|l66JBRo$%x6>Y{{4G* z{P}%sWjvtlgSqq1(?vNCXNk;kb;5+JFpa#=@6naAbL*N%w(@tSPii~O$!8e@4qTWw z_rid(vi=7y%$;{(K>6)SS9$HWt-S8)q)Atqx^)}VUqxL^;#q2E`8Uih!J5jb(WCj0 zkxVqW2sA0tZD~_V45&IKn@O0^WFaLqu_1Mz5s>`yr%NB~(*22f&sP^){9~#o_Ro!I zm9dYXnA>&WhQ*nApT~cYn&#zu+HV=+(5;gIxWr;g|@) zfEx4$LS`7pRI+;z=Bz!sBWb5=M9ukN^h-HuX*rzzw`%yA7jgtI_D6oZ}9RJ_4lZpm1awZjfmRf)R+=;R?-2 zd7HK4>+gi}wR{wNoQ-DV*`~|(*XFbQ`Sba|=Pzv8Z1?VFa!+xJ<_DPx*5nEJ=m|cC zuebA!{_Rtg=Tq9({X=OM)O^3=sr}6rx$Zwf&7PNr??=Z*W08Ec?`(Y(cyAEV!YRbz ze1iB`u8v65DfL8}_!J${AG;!JdLXLaS@}3SKPSDA`ZM|L+T~-)V~UGE|7J++$EVKD z>zP^**QJ%!=o1o@l9g1Rn7Cw6MSMn(t$9j%M$2w1jE&at+L zv31qQ_UkyNcX3>@`cFsZl!^;miz5Tfb$a%Li9i49fll!m>AB5YMWzG=v?<=WRB{^2IaFt z|CT*NDo0jSy!pte(bGC$Z}#L49g@m=;cxr=PFrZ_^et};4mG{;`lN)Y2Re3In3)vS zB0Oqciv3Oh6^lA|>=@W0V_>V-wyhYxp>6!@=V#5FIqP8PxU%Bn0c)i7n+FsZmkp{c zEuJ!S))P<8oH;%+ynKLf(u)J;FQ`n4f^l_PW#`FC>rN&7L~hogf#a69G!mP_`tvU2 z&_J{e57?TbwOKW&o>(vkVUrHuQkPi`dRn+>TGBhhzW<0cF=sdbiJ7P8ClxeX z{s14tRxcRiK=|s9KY4#dthC;-N|I(sEgf$LhxTOI#nwbp1U266349U15BP@b-Z`eT zV!RxI!-^5DFGjY$xb~qjHLfG@%pce4bqtGtKv`>XNJB8VZib6k92q#e;~0ozERMN2 zR^xaa$6g%&!tpMSD>!_Hyn=@8ii5Wz=lT9Ceth6C@*m>A@e{W z5s3G}cqSp)F&#Y(!P5{i?$(}S)J-Nn(qowr|DZG^lL4C^S&htUGgvX*b#nBSDWlnd z`A2`?Pf0}x4j2-vOr64pV}Oa-Nud{KQyER zo4x1#>Jk6ppPVink;V6~>NJO4**w;hZ%AUl^Dn0Bxoy}mHG#KgZ!}vw!}05=qPKV| z+pxC4)4nfbKU}^7*3CNYh)aGVD$F5W_a-^SR4msVpk9S^Y!6)Ao-kx-LQhD3=fYQx zNYgMwolZNjlUH)24T~x6_8k(N+%Bwb!L*?(9?n}-ICxxATzq&+-ssBZ3)82)@#~@` zcTP-NJ@GB|)fLu&;P7Sv9^Qd{x(pl<6#P)z7R|z21$+AV_scIC?#rMD86Brt@t0rn z!(z0eGdhINxmk=gpl;Ayp=N!hzjRHb7ehX0#~oel-%AI03ERxa(Ouo=iU(iAzu;@+ zC60s_})nGKUadp#4y}|zY_B>dgzQsk7#Bv z>V2`e#Z$5P-tv)jj|6%^=hLZ1)qk8>9V;8hdijhqkM#@0GMXEgYA)57ZkR1%PWBEh zdBYmQli<1xbB1$K#JdKQhx1V1#riSqss%z~t{i{imP&t9l_j%hf4y&Y&(-^<9@vjg z^Xso2$JtBu(7bt()M+g*W{20JvUI`qA?inl0I^Ddcm+#9nbnKgz_I?)H~f{Z;EHWa z*a_Ckv7g^u`Q1U*9&=8ipQ>LiJ@}4&0pF|az4r|p<~Y_>Hp(wKj#9rg7y{{e(NDYe zJlBqMipl;Vm*eZI)kynQIj8moImZ59XD__G&NgSca)smr)^PGzrLR7nd~IOIB>5W? zNxQ_+fB2K)Kj|tkR9GV;5}0}1)9gfe+0MuLIkuno!{H~7k)hLI@d;4zk?wcr*#tp%rh^edax z4-uzyI^k9SD}O~DqF$n1f6&Iz<+{5DZ5-n6LIN>?C-5UT6K&W};}q?^yN4D13cWVi zq&CeXD~h{Wi@W!E__;}ay=+_dFP?j6Qt{mN4>Q~HweS7RKRDR0Jir`e%}y-xnKpb# zUU^D1?73N^$}96Dtzn_*DJvE~7bJ%$ZA|7CogS*l?^2oXmHzOoW~OdIRTE}e2ilKZ5NtXev)Z+U#2**6H&>I}80liDaNh2JK5 zU1Pnd)+w5;CzGaz=U(dEaZF2JzD@dValZ0eGykwbZOiSeK_bu$dYahNNUw_*_n5mA zdp_uOEAfG>LkwyChx&MEp1o)v02bV!djz=YhlV7E(^iehATudg_m82YW!0_r;4LNb zHv@>PUZA@iknTRSuc*_Q;lq@tr&P_g#>OV6H1D2YkZ6yZJ)>V~>%9h#8jxa5PNmsOF_dTPIuF^*dTbq6JbCD&hxt6d{nWc` z^zql(=reUa{Qc#10R=<1KDA@(&@MNMQ~LGG9yX_2#bMU*g@df?fdl;2gZ|z9{n?a~ zl)io1le37>3zb)tE*LLj)|jQ)Vj{4zUro{nLK`H6FrzD3;Ln1MC?&bSpI`9nQ&Ly~ z->GNkra#-}*x@y5L8BJ!l#qKy8B`G1U)BD#GzZ^ldeSfmuhT6Y=# z0teeO-A343%3%}gjvMmRB<+%*Yr3_|IONnO1v^6x)po5W-WHq-#7OfN4r&5`G)!97 zZR9vCP?_GVtZmoigoHF(Gm|CUHh6OBm?7;ZjNS6~BP$C=Ef~3L!}yM7Db}#RW!sne zoBvv}EO*VSC5R+y&8=9qec1BAAX`SCey!U^goKBMwu+1mjVK(nX+giJ7J+%KT4fB` zw|z?aunvz|*oF>KPq%8Bw(!xjb0?!rMUa7CVYjSOlt~RrPs?L&gK-qJ41@J=Oke5P zds$7lLeX?uz?u?*R1~ETTaoUYVkt?o_Kbr)wsjBwqaT~nmw%O*JfL#ZhDAp!GLjX;tV_RubtR-AWIz1r7HGFqydcN)NK$LQ61PmG|oYD?RFXTpEqw&XFt#^* zBDfu#L`_z+2)e2~C9RRrck{cAcKH?TfwvZtS}p-d>9j^6oGUb8pfQH_$|m{iWl2`< zywhxZ+oX)dq~w897xizS5FVEjAL`>55R(>@9d5NojHt?t?Gey^_JB9GFCJadzoaC7 z@Pmu?yj?Rqv1QBn*sx~J4IYWjgK~om7E5z~TX_4t5Sy*@n7Na4+O}-2SWK_XoEM+m zlO?ftmR65UlcOWPu>Wf29_3@?rAHKk(PqaR&+ zoVC0D#pZpjn?=M2wh0Oe=@6LCpX9@S8Qw83#GIHG9?>~(U|RH~NzY1f$2`YB#t*u! zq|2)RkGxPG3{5*2RX{dj_z9y~Sj38VNleIEqz&D>3|rE@VE6+s9sS{_!#JcC(-!cb zF3y+(>%RZ@FTcls!ykr@hw5=O_%{f075*GXjOPLj?QQU)g5RXDYFJYtNU{HOYWqA` zBGFz0f}t#n3+)rUlz(HdSKUc=v>Th-X@7N2Sy#UMWWtElHQWA+r;^8iI^lE_heein z=c=S$i%XNG569)<+i?@(ay#S=X_ZiwHoE^K+yAS*bkrhAQt^^fq!K3!;c0{HSOnS8 z8aEh91P0J@<49`+VFr^pd=9}7@K_Z*6ADv(a**=<__nQfS^^^K?sT>8@C%NTn`Qdv zJ1*?$BB_{1X%p5``gq_Vp+mv)<+x`3$zX5ASZ2r+#6I%n6svH5%pB|4UvD z8nwYKHRn)f5wk!E_n|-zv+dK8Mef1!@*uAyiRE|(kBu_=R9V{a8C|T;n*F0#1^@T> zf`GV?)?qDJkFjCuh*h=#pJt04$@MyGpX{MkzBd2REq(_1*~!~FhGd5+nDD3mZ@+2+ z4lQvL5TxY~r$cj-JcA6%X(ih;xNDg?#9h`}jw^@)JQS>OV1~ETYg794o;$Nm zTi89dU!Td7@o-Yz%(-*r)VY9DQGePnQ#}pc2!L6jNoRG=Lo2~>(G7s5ItpxmGq2k3 z`Sd&lG{OYR#Ckr1`L3+n<65GCq363ioh9@0>|ORU?yQc;f69yPi&=Nfjvu=E!BvhS zCh2VjXh&hd#S_D4F^7r{3tbjGs9}A>X27(uwdlY|4|57ByhIma7ScbGwk1-keNFN0 z`$stT9y%lsU1v7gEM}8`Qd(;Fs{E|9mfof#yfVEkHe$}k&y`W~caBqidJc$7iCQUr zWnU*xaTvR`Y^kc=bAr9RVj}ysEgJrSPb5uJ=3Lt&nnpV(abXW?g9G^ z#0NTy5$v6Z)}D1_v2Dzkuj8lrYmTp^DqX#l^#RdDvuP`0tb<;H?2+gwFrXBcN2So= zoB*r+r~1*H+JAzEq#L&t-y2A`rW97PVmiAQrT$3Wk|RoI%${9oGJ6IS_z;3Zc{by- z^=q-;yGkCgM#*ZF0*N8O!$x=%eOY)Ar`c6{|k_9zmBCngEiVm zco+V_CjQghg}*QO_TKGJvq#6?bnH>93kvv9zMXI3BjC4|+O2@GpMGSLpNW5l@K9pk z10@~BP+fhhlFk=71}o9n4JQ)sA&!)V+Iy|bRTkD=lXj^e)wX4k$|2Ak!)uH$@8BB` zf`Nk5hw2?Zi0{X*Ldw973jvNAc^F)NA0~SVWSYJQz7K$838(#PnOGVC-lYye`^92& zdhjmHC{{VeUzO1l)0_-!$UWI+*6+zDM)YsyWwG`wUbyzf7x_UxPI~gr8^EzV|Hs{GZDk`iZ(T9%n#TDkYZ$-hYSNYD^SKR}C5iPl_%NoizX9k|iX z9NYOovyhj9rRbS#C;xy~hF6}Pd0}K{bMiCKT$oYPhaJa;;fL&}pUp~~7H5fVHR$$@ ziXM}@&ynndC3(11ey?U{)!=6)ZtV$Aw_VR9#Ipx#uY1Ymp~-W^cgf)0&ft*%Wj@+} zfbjAVq$31^F@@{InQ0P$h)N?7bf?g+X()`&%*s9vZ?&zBPcI8xpt?&B$b}=%jpFwH z(-y3p{*RDV%a|&+N@c591oK#2;p=VZ@~7O{#$vpN7B&ThJONoRq#9J;R2*jcg#7^fj`w1R zlwox{Smb;#;#2q>WBzK)MSLdWVa)%YR`%QE&E%KjRjOQeR6F1Q7w=tfVs!MK!K;76 zd#{y$cyH6qr}y}-mq)o94&n^m4@WoC31=ek5bw1o?(g)phSQ114}VPW)u&PVrtgss z`Mcg%0Dj~5Hth}k!)6Nj9scS)tqpbN&;KOpyfqw`tGmy#jvG(s}&b{obI8^E^J76?%Mwo_a46DJ2ZMqucAZN zzVpO92=b-pfKSiqy$7^P?Y%d8ll$J|J9AF`EXQF{dxLHu zMz;hX7Ampds%u|X+#B@byMcg1>F>C{Ge&9o(i6(TU=jJ_I}MIkC+)ovn7iNOJMp~> zKFVWwT6{N1+9J|x{^o0`3Qc{=P`B*J<^$5BSi&;bOmV^ep2cwQoSDutK{?fxx8Y8jTc+SccAz zeq{flA5Bh^3`gu7D8n%P1f{4B)^GPSbH=9N-`F{O_CTxX&kyd|<9hP!v#cN6{7}uk zyFWg}7d`aj-Fr1osKJqtzu%!h)FIp6e61NGUK{6R5H45jj1go4&D3z|ID@tA6PTE z*o^VF8Pqky2i6Q9STlTJ&G3OW!w1$3A6PSdV9oG>HNywi?DBy%!w1$Zd|(MDnsOuj zlCJAsK_3+_5N1F=0>vWaBgprayejo|}_ijeSg^`my5KV@C-nY^sD-}2iqnxAaFk9ojNP1%dl zavVf+q6K;MOLejo>4ZWdQ@?9m9Ib$&P`&?z$DV7-hmPjsa!WN*^v#(d`0kqDTi&m98Bt9sBQ}-xswtV1CTF(Chyd-* zE%z8~aE;rg6a;=|@W&_<{@`$dAX~R<`Mq#BhNTGWys!k`DqoMWHTZsy8~^_F^EEZ+ zr@ud*FK#Zoy}sSc#rfSX-R&1de%a{%8tWG}ryilFSP{s-T6b8Vr}P28Ejc0?^*T?} zrAud%^NsmZzA;fsG%7=q@;xQh$aH#jx89S&abl6>S<~6Xd~Xi~NAWOt7(5#ylwx#ke0|%-sDM zzOKgyO~9AkkFVm!7gqz}@nyhdui0Re16@d}*ZJ)Ar|RY?s|aBRE$Xno#7$qF-bG(R zY^XF;qpuP1K~ep?)&IBjg^Rm0WR|`}U^!P=2?C#9LpOrJpl|&>(3fn)pl<_SP3hZE z3}TZKspS6+eG$3Q8Qe>L+J&P*aK@^-IT}9*{w4bQ>8>6%cp4=?csqcBiHCiFnBP#( zp@!a`y$$*yNxFPLgQR%+!PDHR_$a%5@MR6w3ZG3jolORN_##4`uaB?KZUA%h?O(8S zzPFdBr_syX%Nu;`+30J%!Qi1+(N?HoxY~qZCf&g=z%o5`v5e6}E!AP?Hh!=gT~qe!3q zUGbB2_fyXd?({41EHQvn{qjBY4Ib2ln$XDAR_>!w!{a72YIxj)My|(NgZnEQHEDQG z8qqV2Mh%S-4o-q;v?jS~-KhqGY(UeTweFPtZF)ppBxjvkCuB;nZk2jMO##pASUR~- z7|4S&xJrt{a*g);>}Ss@?^m6(pWP?7=ih;*jlT~@`-gjqHB}wz{zOfwa$I5|BEh+) z-$!F5nw6!)Os1Y_^wG_#b0Qt0yt3jE)kWnw9i;(-V?)CtBEqaOy}RYu zA|h-#!}xbnT-CXu+S9W9+?a@n*4Z6_mnZPrrs36iH_rf`ACa4n+|xdVDu4J{dz*~RL75oVSK^zWG6IwB$_H@_^#N|+2i zC#rjxhGVzh+SAb3KdZkKS2xHao%MH|4Oo9l+U*z!-rak=q5kj`a+?hh^@jkHI@OB4 zk?N1Sra)Ks#8S~7yhRs9fSgIRuIf17yZ)4;T-rl$0J>41<>G@#SGR$c$WA`cQVo=L zJ5xD35rPHid@sRak3ja2Z5VKbC{J~XbB1UH@=hVA8l@1TT`TDxN4c}4F8Y*1#{0VX zn*|%uM%~PP429w+ST6E)6gDOfKfzBpsY7rEJNsh5(fFI(f?%U^70OWvY$%io(n!Y| zn42C7=tO3~akV#i0}&FiQKN;Rokps??PJ~2Wy|f`k|&91jqYjoUHIIszaVWi6bDU( zAXB&Wb(3#z%UkJQi1#tKZ;O0gx`fEr)f<3eQ`ks(i~H-b$hVfT(CB*xlaEdFMJ!;a zlmx>E${#lQR;jU%aTj4LW!!+XKX9fhbfFC}gt;6@Sh}$nsmIuN-6k0;xjQKbIw?LI z0A2KZP|ORu$R|8pwBdpcV}Zz2nnZTSh=A;fWT+3P*zRHS@%8r7W2LZ{x37<>Yx}IY zc8MX)+s0+3c4{q10UFef)ZBezZ4=9r2_xtzM0hKJ^yK;v5> zkDM+H&d~=3!YSYqVj9J6KiVZVE3R$xki>RzS?#+vMMm!7tOv;x8MevI$j_aFZ-Z}Z z*qHn@Y^W?$J%oqk6~dN^C}TZd8xPw zI^i0jZaaQ)5l`|FSg&qe0;#qsshp_tw{DZ5(Ac+FD~494jya-C0(XrkUAW(mvzSl= z@q)$d8IA9>vT2puikhC*5(6Vyy;h%2oSl_hT->;NSBt8ad|bTl_yw$9?5yFY6xQlH zr=wQiTICnJ$bL#|Pt=Y{cPs|g$=dP9?x^uYC#s;ct8Zwi9Cw$Yu`}DANPkBGLlON?sUGR&+uiifuFX3Z~at$Txq(N+=DIxN~-HG2AZwe<4wG^&0PVXb43 z#OS$EV|wX(#oH3)>Fw?5?b9wY+HA1|g-6AijP%6j=cQhD^3w)Yv0(DNO$=V?@z2v} zY7-L?V6mwBjn1kR?e9gDyOcZzi3T<01a$o~Oq-BKOvmsGI8c zKD~#(*^Iq2^BZXhA0VHIG^>$j zkTZ?hT@*80Ej{2yeVTH_jjA0cLXvM4^=H&L^i#+SQ*W-U%xautsVgh?C^kIr>CQ|( zN$FN2-5_T=%p|&anXSG}s*xtV9ZS`Z?Ah)tMSh>w-_y5Kei!*sum(+KwX#p8d+rY% zzbn7n%Usz@uOL5?6Y2<6M5bVFETJB{MWgge<1R-sIt-MbE{JW`uT3|l zXS;NoW%Mg=hlb{6_bA*~*dsePG}Mh%QgY?Md6QYdAamV%D4?1Je={(*_QiGYk30Bmbwg{Qa9|1U*0${ywDQo95%rz6ny&4^8q};i6Sj zoJhX@SJy(U^)w2ObZhuBu2@PVUAhxf2Psuv7#Hl#$5ukqaxs`YqqzsbxmrK9UhvUo8G(2fx@1dxDE}= zb!yKENl6LAht8PBf1WmD=x}kTn6rBJC@bBVlO7os7M_Wl(y|^svoyta{)oAOYijD?9gzE;~KS{ zdWcl(jOj}azgoRUHYYw?sug`7s$a*XuOU~+<^)(ylaKVZ2E=9U5#Nz+Cx7Vt4z&a| zN&ZlPQLooVkwJ(g6mXjf+^F9nc%Ty)N?%eNO|}?_Do>=~#CJ~pM8i+(B~x8?8>#lV z;31K3WBf=BMVpeo7EsjdWG4YMp~Jawvub7)`9lo`QA1fpw#n-HmEiM*CYv|BCRm)w z;0=DK(BODC3^J=sy^eP+=?UJ2y7&ESNJs61(jmPFSPmKkW}doGj7e;6`yLJcNN?`( zt;ijQz9UC8b00{*^jpoCcI1d49;gah429jlzjJJp?zX#WR8EmA?n|Ql_ASwe%y37J zXvR1f?X@xre1hHfFs&8>)p1%f*u{`cSjSK!z(){U-~+u)!w2-}uZ?Y@;k4wmQ)$$X zBz&4A`CKa}%vLC;FkXo&7Bv4AH1`8tF)yP#4%+nuy*mk-SJ%7VVXjfUBO5Z_xpl@q z@ce@1I2x;CTpfZI37-OZ%2oryD?YF#-(CtH0HI4I?`d1|5$K`1jQ#9*G05>^Q4zcM z!GOu7U_7FE>8SyecWmCl(u$CwEc8<1Qw7s1R#ZZ|&5A(9h>)obc~TIX2?IjGnvu3t zYuKuF z{dN=qR!=3M?>hDnj8A>*YLp`)y}OK*9z_vSKx0QDBBgOruC9it1_WGm((=dHIu`s# z>4Vdqd?(mY_QTix*6}%O_^b_mcQ9)aAyLzJ9iPP>TGv+z5Hxn_zAE_+5(cO}Hx;x> zB}D8aP-)z;(?t%c(uo$~B(QRFjtf21A8d}d1#L9gz|Mx%q>ToRXfA~0pCxC(MMd&I zii#YPJEu~2?m(3J)}PUPDsNM{$p(PcLFf!4aTUp~$)!^rq14&UlD?j5-#<=%)c*Wb z>8qJi+p2Sp(5Z4M>is3?Y%eOOwDdMcLn^Q|RMb>uuKmC?>8lx1o9gq9&?$0X`L6xI z6zQw;)s9QE=s~}6lwN05UsPLZ>ERHDok-wn2`fU1^7Z|nvw?EoX^zktj*qL)OJ7Z~ zA0Yg7R=uZ|J{opCIBLSe7l^Asso=h^f0Fx7afF_)rt%>5{&6V3+xFmx8w3|6euY$} zvsIs0*JqZJVGkZlt$nQgecd~=lozDrSv$sxN*cRk7OEaFTmywR2*G^>f??M0us#)S2lfE)yus!Y2-e_qEZquJPi*IW z`&UBULEplfS8-nToa9MQsf!cRot}{`z=aE7QNTNb;8LU`0sFh@&(W8xxzLp$(!T-} z^pW(CG7w@Gk3^e*WETB&Go2VSq@qM{^KmgAXhc7B$s8`LbhwexZt*DY;?j#O&6O8F zgIm;@Yfc1kvBb{_r$KMm@%^l@Y?lT>>b6#!Ztng6xv9H2+4vU1V>AMTWu} zM7z66d5f+do3hqMQ%}iK`FpTSdEUmqWUZGJHYwR{S8~BYgt0(qss8S+68JSvB<ahfsx+#8!%1pc=yX(UR2Sx|l8A5e;m2#{4ef8=!k?uW~3T$dMz@ozg7~tZZLvq1z z#)QLGOs?si51S;B+wpx&R~sctm_}40TpD|GGh5VKA_n~aqWnzd&0;f)pP2EL-S zO*HoVbaY`UV+~q4^*`ylO6ZQyLO-L_hG42-er6{_P`(3;^ z^jpVRDN2fhqn$+ZFAaS7fZoHI-e$n|8fMI(lE;}6*YtB`%EgNSa$}7Y<+x54lz$=e zFLCFej*u2$TP=U(*nQu#g9K38;<)a(&Z22MV&q?cPJIAxx@bfj zPYo`@22!{{n}He@$V7g>(tl1%vrZ4ac#(Z@+CJ?xzoyYvnPOixVS+s8;zdUQU%-}Q z5fNwxo^|7EfQ6rWGb|bZ3Nd7Et4i@8c@XMOmK?E9!&y! zW#SS0v?Kf{f#*-Wc)PBCGX#D8_GGra0n;@=UzxXgbKMFQg4&uJZ-Nh7jPQaNTw;yZ z2u<`(50)x8m<{Z89if=PVj#$GL~u~3a>fk#29beZcjQa|rb6JvqAp&XF@xGr<2;&? z&l6U~6KEok%~>30wz9}eJ_o2nLH}%x^t4f3L*DH~j&~*Sykq}_32UfuESmv0!hl#2 z**5U6RvUsu5NTMj4VrFV^y_KILsauT;k5kdX%?+>_5OPCqL3c!7bpL^ zWiG|`?MQkJ)Oie?>E|*vLWDZ!$($zNU{Z>sG}|!66onnnJ<3kghKZl*J;L&Gi+UXF zQIwk(=AmCOUvF&mz^K1Y9i4sD;MT3f{e9U3zW(8@NAndvmhjfC>6t~!CYc@>(7jV; zM0j{aW~c4}fwzN#8g7GvNDe<~wuA))1_p##_)&lVFpC-Y{!Pb>q0TX5OKr?Z{XWM` zlKz}n!+zg54D~_^s3AB|Ex$_`h>=+1zd-x$UDoBUHpn`KDJL=OG<(h6yNK)Q){)Sk zYV)BaSOlX$!xd%#cIh^Ii|5?taShW&*wqVjp$rCmG(~rv0ERa92Acsfg5;Vm;;=>L zu(xirOAS*+NRN3?eiT47JZStxcGgBVObvzxRmS}5$8N2l&284kt$cj^Z^Ff?E@?2G z`VQgenuCH);w~fgGwAn$DgE4KExcWQyRHs@I1GhOcN&66hbXbD)Q!tcx{m1cozTF5 z1YN^j<1c>91(?ySo90HMfpt>BNeLIeL<^EZP7GbcNGDv^C@)e8SyR2{hKndSv>VNu zdIL1kux&&aEf+U!sQ*5R{yQHno@VsT29+d^HIOZYRHj(?N)J|p?a?au6Nr$1oIh~{ zU(Q6Dh3+)yI;gUZSn9cX^PIY)NO2q~Yc_AbeFR%wcH$U>%OW2wjkhM1Y+6o~or-Lc z)=tTeOJ^v#{rF}9!IiBBh*f6;7szVKMH9RgP4HGU!CTP;Z$%Tl6;1F~G`YMLP4HGU!CMjDi2+ziLm&g-ors8@;#z@s zqCfU+v69)Cgb=*;%z-9t#e@~oi~Rn~BX;b-@G53UpVoY}r%&pF1>?poSdi-D$!53i zvx8sdS9a`>eqhtjy!-B`4Y6UMVJE6CJX+oq3TF@9VVi< zSiK+aPxenNmUS*A5erFzovSuv*UBvfCZnZVEOEm)masCrWx?x@{d3=#1(PRSnl#4N z!5BVtcj>~`vE4FL@{(eL`aHB0>$yf=kK%PRUY@#)|Le(lgC-q2yW`2O&HHxjJ~{I_ z`-|P%#Ye^U$?N|7nt`A(=nj8W@^g019gvDBe#)1$%EvL74sERuOJk9AfNx%_e_Huc z-yS_k5Ls^+!V_cFaU}G#6pA=44mky@;>=RrXQ3>{VF>*+wD-Q@{8u(|IM0-<%Z3ks zZ8&SCZ$GrQ?a-kbT!M`aUe3@#ng#M1R{xcLo{9*nh<=`oe6;O#OcFwrqQlVk-7(S9 zbwq~5>OS;0L6}%aH-ZI!n5RR9DnE2Twt8|w)w_Jv+4IMe_njJ8aJRvyPe4 z`wy>J$$u^B%UEA|jKdleyKLnO=Gmu|vC<56V8x#D1uyW2`1bckhqJ}kyBEI7X0eg) z)%{bRTeR-cjXQR1l>TF%6&ln{yQwC6LVbqr{211PNzh)y4?%%B#*F!1a;>V!RJd(AOQfenE*#AO#vo`OK@Mx_e#RHMvW!)Up4?51N zk2%h<@p4&Ro~k8ax=rncRKB zh+?g1Q436x8hY>(d%bjP=fc^YB20B2%3X6r=h^djIxI4KSow-#vsR6i|HS1r-&&~-~(hhRKH*CDtLfR<0!0jLvl z{`Nt=w7}U9=ax8|aHfFBRDbjh)n6dauKJ^fNg?(S%pFY^<)PT;bif>*lFO*D=~`iJ z%822E51o3KKmGyds6XuYG-W1lA+?|B7$E)L>Qg>gt3Stkz1V-W`cvOT1@b3Op5$Ap z0@<8eHs{`;+7tIxV3Yb|5c_5mrcTj$z?Jh-4R(F8*VH5@k_4sSX6#|P$FY&`QQy3KR3Cfq zvc@Z$`AoohLxUsR4GxvndZ<$j0M!5|+ip+v*T9%JvvO$yE9Wx}J?>rB$KE}v!HESv zB&)o1z4Jpn6>mwcGe~v1T7$QzD$Cd}n0cV}UOIXGe73q+M%N5C6y(ZlS{VXp_yLHbhOozRvPl`RvL61P|6;-O`(%l+J)+>;)*Hi^~ z!vaXN>zZDRAr*#c@&~sZze%3ABu~e04i8mgBWl4QG~E)%MZ!h*fuxr*ZN;yVO0q;<@+jL@E+Eqx72ws4=-?lwilAU1_8LVa9gfWTXa^n zuv;D!bqhp?CSkBe`zy;X4|5!v&CjimW@A3rAKG;96}eaXl0}XOQ6$h?;Aog-iSd4h zW&)u*cVR2=ygT7;80mVB6@q5A0(JhH?q%gC#S1Hf<017CJ}t%u<*-3-qfQ6HIt$kP z2tyD~(b(9A)Hck1EB`2-nw}NXBdgc!wUt#>12*F}JGdyb*X%w$R!>|tsfQ!@q4BFG zRP^3_F?oj32D`Q^;HGr!~&RE$_a@*B9Qw}6Wk>-f@9LDauv zNG&2~{3&Lv0T;vyhiXJlYB6#@!f4%)s;cr$ZUh@{`57ltBcwa-t9rt1_L~j3xo>!1 zCx+}5H>%);E4tNarw`NGa>S*EEL5-6^4p4KwJO!!!uuy-6IrWX67SJ?txUhR)>pyG zEc+w1wd$q17|{O7`e(#9f$&{O6_WK8v^;Oo_Nd(%p-K3?PeTT8ok7F`q^A^&-vj3m zv_?|0sD>azf&$?_Vr4P5^dRU_s7HAeg7l{FE6f!~ky_3Cx?0@_~?`bx+GNPb_) z3V&E02_MQI(=+M8(Sd4^WKaX64K{3so@S9N9c!i8InQOY^L$;2X91hSLsK4o2$3Hy zWO=bpEc+769WiwL_rJdW7=L{3tGA)2N*{guLkYtO6f$BDFPAnV29S&!*u&VKni_q= zUAW_dt|NH4d=2lsbXkpsmKG*a57KB-&WSApwlY!?wzM2gyQ1icw1GQ?p^OfeHrDVv znXI6Od1pyo?f!E0wvn&NHTEaAj|2_AMScSWF7VgErh`}wZ;t%7riMMJA!WEsh&ABl zubU4Q0fY*~L8<~pmDCa8P?-|0Np0}NwSvPF*Mzfzvp3EpnN1{-sgG50c;WC9W$?ma znaV&rp=(z@?kltN3L`$0NSkn^ZFhjn|@37U+0ii>}p4YQgWU|-{#UB`490O)~?P@;6F%hU5bk1 zBdP6~&b@o*yzls^N!|nxT7YvriO3+N@zA&ys01ZsuU^|Bo&`-_$#3#6=1-PpOKlw= zy`R&&cjq&7#s9fz`*zJ=Ae1L4BVad#XN2k$erQFICW+*VE}Y*DgleMnu-C*ci=HY< zvwUXB?r5lLa<^~WfV5s69tp}Atd4SD3YiS}h7!GpDaISh3A_l7O1E6kJ#*d9ZQ65s zkGX4-hpm8D74XVHBjUBqBKWWho%RkdK2YQGFLkvEXv1@T! zy5`-3&luM2X`AQ;$rH2Sm=m_JM|ebFlcEH86y-Yi=>C&;CCfrvL`Im*QC?>Leap}m zfr$~3UUHADlAg~O^;j6@W8fb>(La^3-Wh9xG8r4hf@N&>-J__iyoijQJ_crM@}B=F zM>tNIn@5!E+Fru?^-Hfg_TVN~64B(dY&GEd?D0>P8~uDvamRP09}gZqr*f zLVd$T!y=syp-lyfpU^mhg0W1*5S<<@M31dW#W{DBWc~L?M|R2{?Cou7!uoz~TbFn0 z7~Ou=q9OeK4s%0Vw@dT&EA*P!d9jeJ{#l*$odPX!0l|`WNO`}4+!$41N)tZ5U^GQ{ z>^N|C2mXQWfmO+wv26qOian&sH}W=LOJY(shOR=6o<*6mbe$nbVH~tTtmlz>nV#m9 zlCDAp8Q`NOglkh?M6ez#H=^z6MYtZMRQ+!p88q`1C5J_9;t zwhQ;2(C^34_>KeGCN`A>{zhZ@Vyupm;`~)LJIdFaFR>|E*y=GjxOq%V*2%X|k= zCS2~h*k&>|6|MhPR9k-5(9X+y%zY*$A>U>(@~@jvfqBb{ZE<dyn@o!ZT~>A!P%W7nt43Auv>J8UOnUDo3?9E zY;ZPv4MVN8b}h}5_$M)PQbKGzmSo4ZV4VY>RSf}gp)6z1umLaUbc!^2e%yp4Zz}qj z+=71DS-aV_%;3M7BYznyJrMrMb^gqG}Qesz+? zlAJ*F!nkUlZigD8wu6u0F4mdtqOEP3R{Ef4P65x5Rt=2Y#DTkEAR^*tvrt_(jaxPU&F=- z%?l$3r5;|-TN(YSyC%P2@1s1=7xJTgp|rh5gNZc**8mgw={y`iYiG8M&hk!&Lp$SM zPkDkSSL$EI_l3aW9pHek*vIS*fy*VwOVE|CutVxKxi9HkjCNLYPNY)QuT+F2a?YY+ zb-j9R+~Hy4*ZO)G{I*w??;kPt@bF2Gd8!`1+bhfV%6;#Ybj{SuvOcG{^iHp?S&D>9 z@*ivj-=9K?Q9CC+E+|;r(@n4|g$lnidT&|fc3%(G^RY?8504$Ozr1q0pTWa-?f7Ab z$H{&B7v&)8rIOXP*PYU0T*~^)t|frKf_dxX<$Dxu7D4Wu;SdZFw1kGxr^PB-Scu5< zWLhRgMU0VS1}-bit{yXjEq%MP=WDYTpC~L@)AWJV;)$UZ6@B|1EZA4o=h(beQ|2vc zbh{7?N8`=`>NV6OY$E;^YqCFr5Fpf?HG<;I$yZh}cDHe4L*(d+sQ@2dX0(39|B7S1rZC?jTDBrCB#Ucl0K(r zKe{q{eQDe1KT(OJWnfj%XxdlE2#S+K{R9^U*3g^&BV7w10nU z@A7&b+qtn~R-YlIBSmZRYxk4#B7aTF$j`ddyKCSRKk_SMkN! znsLg9KVo1X$LrX08y#;-H-^2YFg@@8n3U^iHT?BIMX2uaA3yx@q2+qbHxK{V@dY$g#yhphZSx>Gg7e zh4mSKOK*xnQ;4*(75JoXf{Xvbm#s*v$+yObWwc?Y7T$iLNeZl(yIXkg_K(<|(Pnp& ze|D&@onN-;QufJY+Ijs_&2<>MuirBL8}TCWWGuBmS97>eg!99KXB2mfgy>asHQ($jIuqLC4?>=iS}pG!bQ+Odq-^aH=OMx=wHehx!_M)oD}pY5A7?c z5d!b}Z=G#0UBun~Kg9d*%JhF!p1&?BTk$8wkplnh;hz<}NnHtf=hPFR4H7C3_(-Tantlb@DVnnu%aeR)NN}ZT+7>=V!-vs$ zP+CG_s}O}9;=lQ}OfMUFe&B$#P;bQdJ*HszW}z)w^yO@r z6Zdf7E>Qjp^r&gbgZ>@|Mf?$CXVLc1cj z8rn8KIFbUup;~D!Ab>i^RQdDPt@-aZ+shU+e^r(ED;5hYz)WL6a&~6>`25_&^z@Xh zn>Q14a}x!7VyrA+>t99-F<{0HngGpD(hq?`C^kAxu-Jr(OAFhDW@}0l^Or)ET3T3L z)xEHKWVcT960wT0UE+c+kG%1weC_7-AC+6x>qb;{E3Dp`l8`v3OXs=m5>wt_&EEw3 zQhOBj@wCX7bX!-h;lY71;NsNAc`)6VU+{YV?e`kod0Xymkv55T;Dn?9mw4mmgdrU`W48Pdcht5r4^z%O7Dd+C_*(HiGflk_Rty#}NTS-N0XB zMUE<0Sq$1(t5I3}DU7!2AFcYkK|IGsO_|C@^3SHidHDJi4Nx-c$M;|2`#=1U^#g63 zbz=C3C=LrRj2X zeHuX$A<0E`idf~c*i4YBJUY-ShBI2%N--E9Ar0CFsx&F6Z$-0{w=ke2e>YI1y3aqHN=u~ietSC);Q6B8EEMwPmB3<;>Jnvs#w zsX*YaB)r8v+J}dZ-!m?h|CTl;4gbeuLzSSQfhJS+pn)StCsPf`EkVsn`FAX& zv{@EppNszb9-^%68kJED&ag@jdpClMNz0*Yx}b+Cf#SUzf9a%kP}-0Y2UtW*QW`rD z);c-uz=816Y+)!;#!7B2M;3$1dJ={e}l~O#=KolzSSqdQf7I7=x5*>G;_Kt>0Us_8L^r^k6={lMX8IB~gtFs^H~pKGL`;c8+r_FV=YT}0 z#aHBD`=<3bdF27A1W~>>Z>V8=4m$S8*I3fRBWU&f!lFYz(sTyZi>?@b-%ElPut5+d zgeL*c?y?Ft{tc1(ZaOC26u3_Z?rVjl)3kpl0@?^5-Km?)YaGRL{QS3bFY<$v*&B{G zr%5l`FU)$U!&%mQ8qbqD%4cp~9W~?Tr5`TcygGV1&eX2z#{%}_PJG~R(HJAbtO-_x znSAvp>A=mpee{>g}TO@ zl87di%6#C$o62a`Se5LHY1d6J9=yt1c1c4I^JVgK> z8EEjynI(}t@Xb(JSZcJp1oDzwx8x&t_@SHp;4dE#Y*}};K1RK++!p1BR6#1C=eSV2 zPAD}-@9I^ScJ*pw4F3Qlg|cqQ{;CE*10*lGDw(dzI~pM&>8|n>0tbKLZHbi5dZoG+ z_3gw)c?F(sDcz+YNA3g92k^`6QlaC9^v(m%Gn0#YKyL8W9jij{_o-FV3r*?xKj)(n z>@V}N`w9kFa$hbDg^{oy&nt%a)Ix1-4+7;={Tf9z8{V_OBWKi|QPS-7t}CS%PvI%G zQlA>BPi?~`c&Z5;c#snySvzf1((2C086C8f(yP|-LXG2vT6T_|b6*O+Jjj2ROZ5Ke z!P`OvM3PrWq;MM*8z95TLv9@@H|S znH&KUMH{GTZ%X*{^){6OzAU{jyi3|4!+9c5e-~+nHVZR(b6b{`Aq> zFZmVfi^*Q`0ATt!VPbJNR^K5Mn;|vYKRQ)Kfy!CepDNgg_IiTJWM({;x!`-3-MV$EPRBitM3lTNs+ zjl`&$8lA3{Vfn{LyRcsz zWz;(}?`&d$T}2+HXwQ3KmxzMp(Sxitvo9@lU}Yoo5=d`K?Url>5NTBQIgI zUA)3}x~fLr-Iza`z|azt1CwBci>}0V@T;U+XFFX<*;g)b=qcal+3IzDHZTf>7Ojg; zxB0tTD~Je5;3N@R5~>%CSswY@r+3+4KDIr3k4q6!8%N22i`jfT+mX!UC4GeBymV~9 zMgCO%*IP5%D?Sx>hJG^smlf;R-8{k{OR-n}GW^oSJ1f^ecJpO6I>m7%E42v@s=xsf zBRzp>O_yrYi4f!|kRXJlEd_aOOpmnJ)g4!$^;PnC2w z$JbKXmnJ{yV?Upy$L_qymZb3cqi!zW_3)jCR<8f$AYYWi9vpjf#XleYj46kifo{PiBj-Id!#?!_qhTY*9`Q<%@RCHzVJ<@J_&D8kKUz1aag1bM60@ z%dj!$626=*03J*13*}{OQ6fLZjvu-8pmejoe<=o#U$QyNYZkD@e}EYb$)&JnSkvIf zYrkCfzsUO&@G6Sse;l6fSxyo{wv&xD3CT$a1W0nS5Z18BzRA9eAd9jHC;}RGL_k0k zSKM#`0Z{{@B3=;`6-Bw|bpbAjprS%h^eRGfrt|+)&&w`jxWsf&Db!~)<~Bo=3~XYT%GyJ)X}!0L(lMJ0>HQkH-n76TkSf0KCD zQyTuQE)2x~3i@z-)iT}hh<&9+FfFla39T|-H9n|Ir|kZzyGt#?m5r@Tk;|cTY5J6R z)BkO>g>50VdwKbhFjk6r|x1c-Vl+Bd5wzxw8YAC7(`cC9_Iu1tKwidn0D z2SjMU{#`q3v!|>OAHOWe-hBtyACw;ppR$7Gy*ioInEr2U=T@F( z-=kd|o>9{3F>_UWb%2u}Q}!#%^`Es_*j=E|#1)2_WU59_ma!J!rsnxRp7LmqUX$?f z$=8ckty-bYI%6cE#=qjeoKNNe}owQS-+2 z@A2pq-^bdlRjUtwvwGEup4TlaT6@AO|Niop`p;O?h_-gnq@}_b&zppEyMdCtlIvKj znZS}S-BtvcpD<(UH&a<(F<}FiPl_fV+y4A+iSXk!=dETBu3m-7i^orxzn>7#@ishc z?c`g#_ms5WU+zU-A6j|QN(r6thSThwjS+6_p^~#nSZ>fVth_`i@)9qzM>m-7^2HT- zY2%TZn6!EowC30oJnRG;VZLkrZfC|p9|d{ELNmre&QO?Y)J8#JbN^FWJgZFd14Osa zKGS1QNMI*c$ubeISTM0eM8c$z5hyRq5I}f+ds)1iS3&$qy(IBJ|NL{U!zzjKiJskW zom@eGRsR`%^19gbC-Jm9cjz6+ny{9(s3zPrCjn+@$<#J{vl(^$f!3Je7@E4}PFW{G z*B@ww$*M^kc`Ng-uyeEs#MW~`FJW7wP$8j zjT55%XAK6);@|c;@c8^T$$#ME@&Z{;iIqxE(jbc;C?RoLANHvE^49wbar#xC3Bo69 z3jFPt7jZ}SXP!*jlTFRwp`8W@yI|s#8n%76xWyc2V=} zTC^&54(r^si_GVCyDnwL&I6D5ZD6gOE@O#mRjS-iTg@hwdG>Z0jdATaqq|(%Je4)? z^nTYdv&M9#d0{6%*i~SfQqD>5Bw2I5Lyw|I!Y++R3?`JwUkE1Y(B-Ly3GcaL#o`q! zE{z3Gr?AB^mbKTYwJn%Y^v{-Hj-l*lEe><;Th5q`&XBID6b~3-qX@ikW3EGsL)0y0 z{m<~PC?fUQW6(wpu&h%2^Dmgh_CwXVK7ingNoOSNDrp?(#Kley)~tg*z{=<1?f{(b zhRp+TCj)TuPN3gSpea6KMyI;ixZCmMrfL{fA0~vIa?;$%TiDZkQBT4CO)uHXDYPzZ zo7Gmh)1z*GN}}voOMif2EK!aGELW?#oG7uj_^5Ba+rQs+ojP{xbX~vx6_4V2&9&jl$=7w-FlN+-PS+*l56`2<(DRv_Mvs|!$BJb$ z?-(=sCOqFUbJ>bJW{w&Ctep^Pfq0d&(6r@$Q5F?7cCBSdQLi0$bL2kkMyrDJe@TCI z&RsMnOUsYTzgFD&$z#V>I{&Y^2E@}&QowNP;0N$i1B{znlb;Ei1D2MBG$6w=o!@*L zv(+&C#$e1blah=jS1M`!&gxiTPj?&-y{=<}o(pvreD8K9g>rg&U)$4iC(&n&LnQU5uCMr*38lJ z?MCnxy_t1Z@lGo%)eo%LV-S0f$jbueCAdyQg2^`QCXIs~&+ zxk_);#O2q$+F;YsHA}rZ?2=1lXRon@O)tHew&L0gU7^b&F;|F*B8-gTJ%={PG-1}R zJL%{88nv5tN?c$;t;L^9#hUF^y(4$6KL8<8l8 z%X_fAhfuW8Vuonp*;^y-KD#yY?z5~o!U7d4;}F1Nqc{|C04F57xCTJs=ox^r0BZ7C zr)XitiUjp0HcV24DBNiS8F+#E5a`*^dAisGn56FVx5?sG^>kS;{m#-QTK$UFc90K; z2Y*_+h}tiwtd>3Q7^OI*S#b@NmGXR3!NShjVx?F|&T6Gn7HhI(uI#NrC|NL-EXYb2 zfU+T3*@c7^Bmv5(PM|GOWxLeoK0l7U{=R+Ok6ZPYrUV8)o7`ZZL?EQtNjq~ zIKX7f@Txztw1YLC(Vv(7S?uDZN`2*!Uc29zIeONaAuhxj;Z zZvtThZ`2U-S6i94>A#z|9nwBIbf^r|$(7?ZQ?}~B!LYJWk&TAS1uR%IV893&=H1Hi z5rOb6f$l5d=Blj@@s*`vfLfMw=#ULl-B`8kJjpy>x$H7d4I}^*$@GAyZZwa}n1^_h z+UC$9F_Sob3Yw&q}Kmc2Cu7}fyVVBlb&?P^vW4h?1xy)Koz3G@fwk%JRfc5^C2fyXO!qKBV5_dl;oF9q!aF z$znws`j$3 ztTB6p`dSX$NQ_+LF)KoN+pGKJW-S0Zwt``p_`wf9h_^rvvPfC3;ZF`ccVtV)lw8Yq<|Lx;;B(D&=p>6BQ8QyHy&<#MMd*-&AqMstqnT1(>) zSDNf%QzUf>W9y&3ap1$NrmV>*%xaU>ZI9S3#ljSHXo9vd6_(vHQQog~rn+aB)F;40yQXZcGqp z=gc&I_zW%FtLwx`{CkXtm*509Ys?-HpOHr`tfB1XhkdOmwz-$pH}BFz%->4DJBQ0h z0QP!3;rv2HA=3ma2Wgq0#rd=8d|EqEDWhs>$!x-HD{75pX=3w6(((?a@vQqvQ69YwUw^7&l|P3&uzVX7qq}8 zUZS6aUx@VlRFQ9GySmGxY9Pz zp(4P77+yAG7!XOO%*#o=Q*FU{t!9~bwqLpDos}JnGMmMzEWhCTJ}p}G?!!Y%u%9in znRkyk^^P~QS&I;+^l4GhyZ3E{nduI zVM%7bnCa?Pnxr3heDjO}{9%9}sU#E{HN)My8WnN$6a2?9kVdc)6{h38Rm9PwnXG{Z z_W+R*N>ZXCT?VA6H|&Iiir?947($F^4~gH!XWFsvO3H55idh$|Q@kv8l=aab`K|=z zsFF{VuDUDoQGx^V(X&&e`v51OXbt(W9JnN$DjpP%iHVqxX}12m677+)K4J%!DEhK4 zT5;LU$Ocqhs4UdxYx5;9qYcYX(}OuXuzlNh_~3=tVZr%Ayli7|g)KO2fNj4s*e~}y z2*{Mp$|-HIb`$J*F{kR-h*mWX4G**mCEd6Rb$W-)vlf7$&rAPQ^dVV(u-!ycH=FN) z6Od2ox*O)>qs99h**3Oqqj(?BPqJ1UMK957qkf0j?^`JL^SLFgB|garB^0+<^w~&# zfWzfaV!sV}#0aAOgV6cpbvam#)b_A>Kl%>)4#S=%%@;53<_p9RJkQ7Y0P{tCpZOww z>#}(BAc~6Ahs&P=oN;)vpb!xGUu}z|t<*H?BoH{hkP@*M_yDvDE%5K81@ICubO#1- zxy#%OG${QzKw5}%OEQ=70j13;F{{r$z(3kB=Ennb!B+n8d#hNfj9xHo@JB8iC{9+3 zFEgb0MlZtGimz>UFeMxV_l^DC?t;JBS)e6fW>*35g3l+THhgxVZFMI_xue`kikif4 zVzYiKy6b0lhiGt zpRl%@>^p|l?hx!#eOsJIxptL*4!+Aq>wsBT*1+BrO)k#l!Y1axT}CuEK+1(qJ3Im{ z$R)$oDeYPo434i=D}Hc6%XWOSxdz8!)o}#{x$5AkEP7ut|E_m(^73-txoiFkdcR?C zzlF&jPx8WkgKyxISx1?2p#*YNrfMhw92Ki%8Wu0pAC(Dq$+XfImH6;;{k*>%keY0X zWR5IQGQG<}SpGIhsxBQKAq%W_GS~3Qfw!fo3HyLjiB*-5m!%Rv z$x=yq!fs2PSx^_f=T@kX99f9$`dnH$%d#|Zh0&WGlqgG8DHIikcV4%4{NU!T7T!KM z^JtJ!H!2E*tQ{fgxckoepkvXloV=qcDGU1!-Zi-Y!Um~F`K0zOS`3bl&m4Svd-Fj) z3B|8#1R-roG`+5nDe`i570q8k@1q9yU6_JD02pOHXyIP|9nOm=w{f>f-JmJYMZ;&Y z5NJ+s%nC}B<*5`3w!*vi=p}Jy)AMqGJE&D1_cax`!+qO>0l3p^Anx=Yh&xC39kWD5 zXj7ZR*BYIwLt_W@TBx^K_Sk2Sk^mqNr8R7^KeYNp(pD8s zJ$bS6wNm#|w_m)<62-Ubr((N!y_Eg4g#8nzx$m!@*0xWj2{75ESKs%Ivr?9dtP+t` z%DO!+)2U?F8uF=aAB%BhB@0>GP&VWf-#)cD8OoZ9?*p}kG?^97x67E!23pTjTU8~W z$`-Vh3~5_fhP2JCpB9(4r>$&SOKqXR-ca~~Bb#s9-+|sLYl6;XOt5W{W)Wh6Z=SUf z>6n(mbKfs!3z(IO;xl5KYl>rFCfnD_FsVNqN!F1uI^D=r5j{T#oREOk1pw&l@dxX5 zhV}W+XZj|;nVPI$^!aSNbR_^pgk6hreFjH+bOaI zgB6hmd$9h#&1xZg*RjL0Y?!pR;s#hxv0bGt#CMFWr`RlPeJdyHDez|?OlcvO0+13S zY_blschy4QW_UA^c35POW!YtEZCMM^9yVTPi~dYg&)|5QjV z*&f>#N2wl{76&^rvO7x4)#CD{^^po|%~(D9!@1WLtXjGFp9gn|_d2rS-@&!>*l#{O z^yX(Q;)f1Cv6r1?eK$26HoUVjbBOpQr$Br+va>ibYjovx>Atvp;JSSJ~VTBYIgZ&?Q9*q4lb3+zNo_x&}nc~S$ zfsL*Fj;i_#C5E}#AyF0)sxVkYV zp8VZ?oBcBIe)Ihytl_9>ti{$j#}@8o2`7d(8g>dsS?5BIm33J zL9T2!*pX-|z5q>QX|Bq#D3E3#){)uyq^HPWLA;oLGkeH<^NY7`787~r`DTG0OSmU2 zKO=rxwfxMcDzF&2ZI}4Uoe3nNWYka{j5H;P_1)qJMEFTMZLT6@nBxZ*`tg_ zXqx!3ErW4JoMjoU{#fB0q1PvUDx4$q{)DY}l*&ZgOTtgrowa=={CpiBR-sz1DPK#~ zTp?fIbEJdR1((j%uYgE?JE0C{gQFm8S1fCV_Zkc+L$?1Jkqc9{9op8LwWeF64u)M?dbe%lXxr8RklSA+;AJN+>qdQ zMe44!$owW;3q5j?{o?y&SrPtlef2qc-_* zyttozcfP=tiUPDRJ%2{*IV}zk{q!_@?Bf#F%lz`M)w@RVR%EqzSYYq6%qvb>g2urf z9a|eoGhlz07i-W=kIbi?8`{hx;v8FRUU!e$-Zuid-LR*xILlXw5u2Yp+Sx>2=53ow z(2D%S%Ip1UdWiVXP;+kwHha+KG3DiIkMftb?fMDYQ_Jsy6_xzugUOnT-(_N`i}Crh z+;ELlm%+~J`OoYamc5nkvBJw=c8#nIL-$xP%0F{pRF6~snH5L(P#ox~2g`jyufU%< zcG;2x@HA)?nr^d4Qs_>SwvjWGa%`ogZXewt);z|OW3RiCZ}YEixM}OG1*Hq_tnS>L z`F*I9riUj>xhGsgPvPWl9vPBNLz4bvX{nFrzHWI-|4OWVe?OaPc~hVLdVwmm@n(=X zIJU#Z57`*Dlug=ovEAq|rM|R%h{K}Slb8a#KEDTXxx+NTi?}!(=tZ1wI(X0=Wcv|6 z&2udu)T@QI+V&%kdXav_UB^)`YpnX!W!Q1S&hlngcf{K>w<*@bY?Ue=>o1+e-#H78?g<0(lRif} ziT_HUaXIF%C>!xUR+ps913A>!qUhiSh##3+C9(TTOI1O7jSz4e6c5~3dgM5?YgZ;F*-)4U1 zyWjS~PJ04z_JPac@yn43gp7^gZt%k2d(vM(EPhF6`o^Qg3srdhvL5B7L|w_Ne6O-? zm+&ml#(>SGov49bTQnO;<4>$F%gg9jHeZ`hSLqV{PVJ;vR*L_&U(~|#&$TsLDrU*+ zV~t0FWj2{F+sk>>mm>R09X%>qK7*ScvY^8PAD{5*@|iPdj(zg!W5x6^{>i7=Qv-)> zm^3ke=76E=Crw1pf5Oa}vzESk?5U^5A;Pj(k3aR)_xTeiK00y0%-o3+AD-A>>h0+; zy6mg2Bbxy%edVHgis4(#57-9Yg5N22v2I_Lvi9QjuZV_=F@n$2_F}~Yv%6lJ>%>So z7$>9+=0kl2{HVBJtl>}ahUOvjrn77nP4ZG6Y=!!+JTH;*uun-;V=mZ74qec8(j(4a zx*+2sk4C(<#K&u$+O5W{xL_Z_$QsZS&hL{p#4E~w(i^FxBhqSOZ-Fm0!WD=43_iUg zOq=KrOOs*ktT-xKOW;+nyqNDd#?VN?lLv-|LZEwF90G`WKQgJ7clJ!jj*A!Ly574X z(%WVJ;*Plb6b|pYiiND`GN9`UrF>+|b?$=Rz3vr%t?1ehKYuOxV`2}+ghOGU??hYpqSL2ReFkx!yk zW%%4LAFXcm$0NZ*T>cL$0^fl6)YttJN;iM(6`VT(HdYHo6x<#d4-50fzVu^lg1Yl#$;BsV?KuqcerZ2$^oqZ+T{@2)+Zi#tj2+k6xy+s~(bq0x)lrv8X~W&}Qs;3p*(&^4sR*8g zS(Uzi|L$kBIF`Vth-0a6*wBZP$w!jPOQi6$8TbOq+4oEMF0R#i#5mvZYMp%L_7e|yA&3?#(oe+vv z?3#eSh{7_lsi`)t*Mjh<8A)dHw4its=^Xpge>+ctZ;&!tW z-^rJQ^E;}uWPtg~{cLykxpPD_4(mY04-1-t`YJ4m_WnGmVyTYWv) z5s|8H7peRUi1y%n>9Y~-JMTNMk1p#2WjnvK{I{*}2A5eD+1R$f6*ATONvfD(sc zEJdrjNjS*D$qU9aFwuMB0wP@4RxK~J=WNQ%h~5^Mg-F%lU_%ZeAcwP1o@mS!*TC#e zGOSu#DxKlO>jq__GE-Tk+^0OQyr8_Qyo3ErUn}1!zba)QxEq@k8{=5y4wy0;hE>$t zFpG9CdxSm5USV%x)cG&=Jv)aTJ>UeElpPZpot)GlH!>$KGTNJzjR(BKI=wM#(3<76 zMC%*y>tFS|G1b$}ga5}p-BVJ!dssK? zRdlj`k6AzL%}THK*UCR^m8Z;u{}(!3D(=xseXI;1EO<=aj9!=Uxx}_jt}X zO7VCew9v2#a&f)KqiG(G{^hZKwyww{BXUZoax*J)W+qR_r$<5v}K|zh7&r zYmLACzrxDG|KA?*jmPs)qNl6J>=FRSCJ*Uv9j%%Yvc0k%mg?69h3JJk4rVGnv5@T- z2KmmbA+Al^W|1-VIgVdK;3anzcE*29JNJQB@Njg)zB|orN&Wh`CBbr)m;-~s=H~|u?QVi`OY(eC4tt?GNkJB|=FAc(|_@QsGDW;NQZi^5Vt;N)^*xg%u5-E^@Eab3J#UAjDc|9z$SqTlVFV1P(-qq z)nTASU*CEVY`H~0BArRy5oNyVQ|G%TQ#pW71Q{s6&>)gMCyuS#xq&HLUz%t7POH<| zNuJ46@tFConCe?i<&kOGbLfMpG!}?Vnoox)x|W>3LxzcHz+EEY#z?U6Tmz>^&&^wX zpj=t|!8+HH)4mr)e^Fc9aE^CiU#JUC+c5q3poapt{hlB zk1(1iGR$4Sv1iyRwne4BokJh$$ICh<%BYG+Hhsa<1Z@txIyz8#*}p_9e#}%{Oa9s_ zjs;^}S0xV_hmR1+s$%+rda>m5_fedyE}tJyDw(M;s*qI-oB|lPnWy!^sOy?|NuqUd z>aEfsTwN(n-F4?NY~J~CT;nA3v}?wnD_4kfnVx4Pej9~LUFn)F`>&Y5kcM7lX&5O7 z?!XwMO1`(C@mO(cN{dsiTYpl0@{|}t2e!76>dK_X<1ivTdt}aCuGtr!dB&3|&aGH>(KW~}#~%_O@Ez3& zVb5xte+1tRT5dE?Bg`seBu2i;h(LStBl&HBDSl+`V|U(#63zik7O`UGpBL`6U<#MS zg@gk0XjDcFlw0BiTxm9dGUn&0ty(o4%W6+4{I+%LQ!S>5Q)3(RJxQ$|Wi|K?v;W>1 z*NGpP)~@JnvoFV)Vh_L@2JnF#5bZ3Q!&q4&g!vAB8>@ZSoFmveRSPsnDK}kQwt_|2 zG^a6x?B7!vL1A`+)e%bYlJj>$kL2Tw`?ly)29X}Y;q;Pcl4wg>?l?GD z`0A>IPnjGikIQq&|IPcCy}Sa0mo0QqIf@NfJE23q#QcY}EtV<-=KEh>{6Vd$hF$!@ zsKeLsl(JUr0@;HH>gfdZZ3DHmLPpWCG(Si44QX;m8jonh66>=F+E!albBif?dBaB> z88JK$f7Zjs<-f4_UzRWbMf~*3<#f5auTm09D>nO6EniN$+fddTN$RQyBWxMUs49KL zUwU8k_-oo;k8%h*>Fa^;r8==4KG2>`)Oc<_%rD0lKAM!6nAD?tQsUXsW46C>J@c#Tz3=KdbEB?zoBgGbgDA>ejvnRfFya{q~{Pkr@%iuJ*VKXuDWz0qO?wp%%Q`2wJ2Du z@)-LIgv3j=4H{u~yBEChe}pyr?l`-3=i6-jJ~MiQc;{7i55{9L?4E7n9kb-bk3LrO zTeBYj6j6Jy?JWOYHet^mv6g+kMeKSBCt2Oemav#tSm74mV?4I;2hGgHebwllqg<>^ zZ*s;(DA)U+rT0!8v}-5|yi5|7JCrP(hL+Xr3bjxVbH7fJnqo$Zo}1##>nqg;Eh*++ z!sDjvD(dD5cAv@EtxX&C?vQuSj0s!%?fhc!(yOVa`#=AHrFlAf^4n!}xR_Yyv3q(g zp_5bt<)b5et#W;}$JS#iQ2YPc`jS$X3n|NWq%0RwmJ2D%g_Pw&%5ouPxsbA4NLenV zEEiIi3n|Nml;uLoav^1OAaAbH9<{y`n{uqD9ZE(s2|WYQ;rYvw;S4~}0Q3w%&j9oc zK+gd53_#BS^bA1H0Q3w%&j9ocK;NMx9Nh_ZV)4~ZOpRDiSKz92tRNEM(?R&QN^|+# z7SGw3S)uRQc&>}*Y;uQsMP7r+uQdagmRlc+nf4SlX1Ugb-_(O9>8^tJifT>&?b}{Y zjJ)NZ!V$wp+%#|T;GW&PwVZXs$Va>GJ2>_#n9zLPn0Lc{55?8_E;BJN%bWXllcwp- zYI(k{0r2fy=#!jV)Ep1Yky^v+EBg>ke_vF86A81cdOas~#R#kLKVy~jk zVW7EL^y6)l-ue3U{z03@%vj#C->_l*yfbgU3J%!*`S74u9)GO3e#%d+8+K~bwo|Q! zRpc^FDP(t7=7a=jH7Z&HikxeAOmhEcxkC2v5}=Y3P$LQG;U%Dlmw+B#0(y7}=;0-x zhnIjJUIKb}3FzS^pof=$>IW13fQLVE**!cDILhO26gk%RfTKL%C=WQw1CH{5qded! z4>-yLj`Dz`Jm4q~ILaeADjmPMP~=zC6jmG103^+Z<|k5T{|y(Z&i**4d?pW^GF);8 zowFjNfWfYUnVz75OJ06Ht9RqgS>vY}d`jW7`@VVeljBT1*?I9*FqAo?uxr;sy4b-l z8`T=$k|n;h{Lwcr9{+*WfN5mqK1c)Q3s zad7d`cK=WM=sT4}f7_n)_u8QzY;39i>itdqK?~5_*;Uk#c~Gr|1;5WR6P9%q^>J#g z?|1$cX43-uerf1ubmvJ`rMk6WsDL8?1Jsyjid zJ3*>DL8?1JsyjidJ3*>DL8?1Jsyj)k_TV>5jw3u)Kl1;9rf3MKObN1=+Sq>Dp)|&^ z%sCZ#fv@u+HI4CGc=U=I1!n=EI!U86MZxH~DV{sn+NA@Y8{)Zx%r3`%x^iDLP~&*$ zEH}Feb2)ube93nX??6*_6|B9wRogbLTf%AUTeZgSfBUWd<7%Z${nx&aznHeqo1N+P zW@dY@g5k{`En2oLC}`QT#f`41{q9<@;I4jCUBN{oR;^jHYDCdnO`6i7<4w^0tkTZq zRBz|n*iCyIkf)j5F>i+F|IOJ0sV&=}ooj=3t__%^4cfUjXy@9Xooj=3t_|9`HfZPC zpq*=jcCHQDxi(Ha*9Pre8wde)V$zj1U<9czn?p95JF?LnA80h5?JPb1z4+73es0&LOZ)a+y0ojJZ&T5Zw_#6L*0;&F@X>r!6Ht|k-vrNIdt$-+ zKg(%WJ6#W&r!F*4!a-b?)++$F6gb>c0B$Le|GMHn1lP^DX5(6h>oHuLaJ`A^BV31Z z(dfAV5NSM>VnIv;#54zD8X%@2UK;o>4G_}+F%1yY05J^^(*Q9I5YqrL4G`0=%zxKD zr-of{u=ucSh@@)Ex9wd>_gw|onMJD~d}hmPk*0P`OK+5x)+jygz55p5ecyd|FTU?8 zSkCt_`!;o3BlDK+?D;j5C$D*U%9IBWoH}*jz>hzYK4ax2aD?@PO4jWPt-VES7x^t) z$h1#}MI(%6ffN-Js1z2>1`F7UF&>Y3`5mqW6vi;U%H!8zT7JQKSR667Pbg}T6NWka zeXP(9RFzi9?;)9=b-pX|#6VSESX@gs+gleQL4Ux5(nKQ-;bEn*=K1BlQW}XIAt;el zf#YQ0NGIOe>AaCDaHR|Q(jOw+Vm_?auec*W;6EAo2c$z8kej@yEW7QidSb0g`N*a% z*Bj#vxLW;V7X5qMDWNPQr3)-0r303M|NQAHmC~vXMMGJqpsbcXp5IS}Q`&$MuUn^2 zWzUL{7X;n4Ep#_G)1B&EL)oXG?3LgIl%A*%NC8mdvfw0-NZzv6{4m+ie!G90!~ttXNL;n;Bk;P~VC;gpvU&w}@?ymBco z^9%k7px?5k6sxYt%Z?KO#~%lDFMZI7qXq@SLGCGP-Kua9CjgE=jvo%F0oj9`NPMtF z5d*H|P53Q(T-ifgh&JGfz;NPPgh%U>%g&G#O4 z)B{qUfeZ8iI4_!KtOJi~6H9-Vbm7tda6qubQ5G$xbd9A)q|Dptp)KJ0wa@`T@T2|V zNbiI@00>XgDYCp|$%-7H6|52y%LFd~O(fG)j|XUyGIl)Bh8V}@8_*XP&E)-ws6#RP z5P>MJW>FeYtHdR_qquBe9}wK)G;N}dKt)`Mrv=rbI>q!OS`n`ze~D>D{&odOHoqhe zh#!E%BrS$29?>gcJ2=RgR4^L{JD$bQpn(-n)~w{{4CrgHy*80lw!`=Scz{Ma64D{# z$y$=So#4rOlx2eEM$BXxld8Lq4YTxTqG|QGL|2jnKy{*)Jw%dM5!a6=m0Y9{)Br>) z;#v74_p15Z9A(qA3J$Vdm2i;J%g#WuD_~cmsUHW22r{13M-Hx%tIRL7ixyLDvH6tp zmhGp5D`xE+ox_{RxPUp3VmmM=S-eV9w^TB#1}vS4${J|Zy&rA>o)pv0A22J$i}lw% zawnQ<7l}sz?}~U@`pV)b?Qih3YRb~dSH?3S{{TE~9ftB)x(EFb(mqgyU{I+GNq#?G~&`o0T9v?8vJC&hL04?xuBBgiq? zF-b!Ri&lKcIw{OV)3S%9;M;U19(CdqR7daH(H%CG1a zPY6rWrxlV)B$QeuuxwU3fkdQkx#t%H>akbas6?R16>@5x;6aR(<(PRJp#cm{0XIeo;bY@k)Jp={>`XkxDCe zZZL;}OIS7>THp)B(O1%lfPR zeRAx@Q9HZUpo+MtFNv)+@~9M@mzS_zXlLHF;Htop;lgOG%%xpaW4CM(8)=Zi*(|i( zgCS4WULoW!+v8a@4w7Rxd;Dhg!vN1}w}{J7BUazt5>)+N6yDb!bwMy?Tj<1v&VirJ zS?XkQ8-bNQeGMwd&R^n5_N_!KK((|D#W%gw)w&Hy-bH5|{3zj*yr9qS;6n0(`nI%& z)pu8S0hiJ?HeaZN(J!Yq0e!!?kfC$iy9hy#uj*Yu52k)mUvJfQya#d!;{PY(RA``{PzwB~@gfT&itGG*RCv zh2@>33%^CyyG<9X-lN$Hi!OwR)ytQ3fy-s$5s3>?5xpgOr}#wib>JxVowA3(RC*lc zP|+Wkwd&A?Y6&%p##wfCf)_~Ipi(6tN;=xUZ!LVJ)LZl+E+Ag?ccn2VmwYDi3U<6O zT2zwxq1G%)NglF^ReG9$*qmhZhoqUseKs#S{ai^nFoi`rbarhXL7vX6gQSBcGvanh zJ}|%4hqgpVQ7yi*>4#CgMM3l|?6i{V#9HedI+mW6>}3Ivc}f|n$TJ4A4|#TacxDdz zZxT&kdq|h$1zQGDE|Pvf=OZG7ka!>b>t}w6qpdF_Eh!D8QDR7`p*ehOB#tDIq(E#O zEmGS024$4>CsW&LrOuJfEO8(k3`Rk%J3Q#L{}xRwiLkhkL_)UWj!XbYTP9RUn?*;` zYEolSjmw;56|j~5yoite`4LTkBla@Mve^=X^3lHv4MN>ahez$YcNo>ey8=!i-hLX} zI5>p1gh`GR9fE8AHlJiFQu;lXy3f)#fu-U*qzaU}2kk5gC|fCc%-3FW2`Zdq&nc!O zd(e53r*op@26(vl;_;g|v)SUFA;Z8cQ`a-LWo{A7)n(f$=RmCZFbl?JaK> zQikn_cG)I?cFx=L7lZdXc0#EOQH#3? zCh2gbL;dY8E41=+^9jy;s*Z6+5l07sVT6J4JV$%)*JFjw4#pZ#G*`$}O!?f=^453}mfpP!}IB}{!HCY;23>Ci&y@o`SHN-mwQ*; z@B13=RS&=KOM9R9ru+L9AF4hYtB>JNeEbdmF6^XdmM8D|@}5iIS-!l>?^KqCR2C!6 z<*YMYzAPQEceOjt-07zbQifp<>lkG`_Oafo+=jiZcPaC*pYvt2ITKY+i4$y0ALtEwBGTM^w28=0|j9U6u zjh6a$N@acPnlINL&@f=LOUsK})rKmmkolzaDIMuZq)W445zI@br{s}FOL#Lrv4#))2QP!5-)y>FzoU1hoOa{-HN28S%#ip`q6Zw&T_p~ zY2RvbX}`MSsn}2+qee!gv&34A?kwYb_15pd_uc>fbT&}El?@b|efNvaL%;uCy&a!d zsJF{cE5v3hmy>@)YS?V3PFL7M_?^VIBjWC%Q?Mx`Znvj zB3#-+TIB*Me-vu{Q5@@!rAXf$@>Zr9b(YF>>d}`) z(`_Q7SY&JyO@TQZ~tNN0ZAu|Bv@Pv&7;PkCmRFj2e{w{ zAI??f6_+dP|Kv%wxXfIjCY6e4Z$J_N1&b*8e1*=M!`A-FKYn zt>%@rXY;hT%f9!iiRJsY#_D z&6b#Gb<2Fm`Mfewbkf3PI*?uDGZ(;9p0DhCrtO+n*5MhT_3bl2cg{0}MgT97zYFp_ z7zb)3VmnYn7`kM^*2mK1o&4?YaOFm2Ec%4h>cUH5ep+%QZZ7#mH?`6meX%caMa7Hr z5u2*5#JLq;WGs9}bmuGH^f)ixq~6Fb%AK7(cWyT8J$G*I147I#nk(p?m&kW>?wmQ| zwK;RlFxihBLL*0%Bcbn;thsZt2#p+J zS_l9Yg2qaUvgTP>Y@Y)xUN3U6*j|Jv3v!CG_}RJHk7Wg5LD-A!IXQD#{kd5P0p^57 z4xrD=viTW$SBcfOX=FE`#MOLY~&p_sG<)2OqS|6###!sG}8n+GYz7^uQc6pMxO2=m-@ic9T)p}vH3DxG$K z|EJWbd+~b>3iFdy^T1Sg!FFu=$s82nlBiqI(1Qoh0WlcaWKJR+GP zn@Zm~lE@rDwP@(`hb{Lh(#%D#@?ZBrx96t@QA77U1cRj`K15~0fj)w3g zv8{YGA5*#-#s6K~R+guIJYB}ER4#&}UJx3NAhCG55{+#**r$5E?^TM1(}jle4bx@Z zN_2L!NYEL!5D}NeN1oSkT6@2A5JfWAYM+>E?Wn{zep)I7N4$%m+Yt(9EZ*hK7nv5=!`AZP$t0VCf_}3 z)NU?tFDq9UpPV-Bq^}5fDNFET@QX16rE@8F6t;t33GC8g@Rx@pyA)M7bk4Y8=%^X^ z7>26{UDp_AF3p7$n#;otCCJe5U*({=?h1<4% zNP}P({OZqR3)lkO{}7=fRA1oxr|(0xK=YLSyX;?D6Vz8A-IS;+(CLROQ$}5Ld1_Ff zV|qC=mg#9|>D@3LPQxrgK4u6yL*tW&O&7VC)yctZ9LVH?r}I>teTD!Jz0#4w`EG2m zk26qT*khj`8|`A)$exy$9mn9*&y|{!&xSW{*|d39r-JUe>XGJ6bFy!0d0j#0Co)~8UC`-lkF`BgZ`eIb?G_O+B|6%C2JdO3cHiLJA>Z$7m6e)! zOT*08-DV`FX0&VFt3_r*EiLe4R;xDGwa;u=`bBK)lsa|z4PDoDO&j*{FtwX}H=nWJ zsh?j#Uz#KF$k`DkuM!8k=_sW*I-39!3{iUfv1S6l{v9STrJvx zUu)TmFS3U-8aK(vXwvw6yTaOWakUG-+uS#R8==8>g@=3MwDWPE@X1@vqg%CMr3YOu zJ=2v^7J_dPT46-E=UHFTR^9;mTMNLs4oqqD@h!qS>Nx^FB^tQ+Rm&ECYn&?o>z%PRpDyKM(rj7RDp{r%s_mZ zrRX%ZDq!tP@#=;R>ovwk4wN73kWuw zR|R%_6})YFfZUNuRpIki4pE5c=J=`wa7bfkQ;F^vb627d^0{0F{LxDR235dYqFkMW zHU_;uDZ!Bq(xg0eQ3o)%VQ)*X2+=?q5;95`W3YhMkCoq>?|+Q z%Z-L|9H=PKu{i(6n~hUta_BrY+SMt?9Dn9zdlz=?wrCvoX=g8bU}3ITtk$nnJ3hF` z>&?p>x2Rj!1vyxw@n-u{jaC=;qW#z@6jw0dl79<~F461-w0D);LYYI?D+{Bz!RY;v zY~>{DjY6N2TDg-_$6V|Z)bOuERBrV7d9=(|o)PE9h#=YK!^m`Sq{LyT(YvLMSmq4^%IOf4B-_+>|hWO;xKfufW{qC60Epx zJ1+VRR*{VN&?A_EF2V*qUE&MLp*|Ms-3?p`ZZ7dKur-Vg;MN%^vnU5lHjhxU<3?L? zaT1k`8tYZv@4Y8x&!FlM<%Bnh4bfKOf$>ASJk!OdWd(U9?$k>moT%Gk7sA2KDGT~Y zNysw__+?g5(k7!J^Cj6LY|+^fv(b&T<<=r3;Zf1gMpI>tA7r<-N|-aYxMWz8m9EOT z2!6EiUG~~JT%O&pp4Ub}Hqe{9glvQ{>>F$29H1G-$+b)>yR~t{^a(9m-rTowqecyH zo-v_O`t)3H?!pHaW#{BdQCaBCR&!@gNKbFvxbK8kEyg!$*tk)8!?O=A%=YHyW#=rA za+c@yBF`#whcvTDqf^X*pd0&yd6PBwro)@lmEx8T;*XnSFBcb1A8fdCibu~6h!~i^ z6OU2$)>NQBCgdG;V$FX_3jzmIW?^eK=3~q!%?v);e9B2wy4S+HqVF97ZGn{?P}SOE zj;&G{MDlrq+O!!saLz!HT`?qOx8M_f7Tx@VQ%Qpg-oSWGM{-C5vaULitsXZnDKeO2 zNLDQ-^-q?qX{-sQB)#!5mH6LruUQ_bw`B$VTOeFq|&8LvRlYT7!+DdQZU(rvAgiDRe zEvDhp)Fj<*`=Qp;BCEB}>au0xlQ?*5doGUUSZZCox@>v1=JZHd?`>ksbg^X{lsxzC z=^bV^22WrN=kF)QSIr+|1AFvlqY+m<{~61d(YQ_aZ-B=?%`D)tY+2R(M~3yLTt|(v zk?0-9#~}MF=g-mZK(4qPKr$fXuGT*p&U!9e#t+7cPY46H7}(^*na@_uUj2uup~(D8 z)*t0B^MDwv|5fErBUsXrE(SEJ(;xXuq9fDHWy{o)K%_eHW$q|C>(9Ro>ur_-36TFu z%D-al&S~`~pV5@vEX|s4Nz@)7R=GqxOcsDWo%sSxrcBWukdrA@;NhTHIiF;~TMl?q z8Ckk>wtiZB1vP-46>+V#Mx*s8+df^-jM4g*wbJuSKSt&>$LGYg)_U~Jd+adMm&f!5 zT6YT$$&3X@UF<8Gu1)BzF868Ll(GkGI8Iy(jy56L4y%GU@Cld?3BWs8c@YD=`S3;O zLhr;4?>vpLf3t#T#PHt6X#0C*+@b zzp57}Y`O66b$%oS^|qdd(bM4hEiSb7nZDNQz9u~L;EZ6wr(1Shp7v+#;TbV$dG|CJAJylgw_r)roTSioX^r$xI%GDT#ZUY+FuBg; ztem(mwZ{g(oR&2yOVdKv3=L|0ldK=yjeVePTS?0pZz4lJpdvgKHGsoo7e=Cy(c!E? zGluorxEOdV%x=pf;oA5-+k z)E|afmd1LGHX+!a`LBVIVJ?@};r$L{8bqrtcK^z7fTA%+)E-zfD7SZ_FQ-`eaq zHlB~FTiY-~#}+i=&$lixLcN7`T1WBKM~=@S*{H2-E-yD0x+bCpiG+K%B&9RHO6r6#F8QPS9auYtJ`Z)|=lCOu-~4Bh@! z?cX%}d~10od@LOuff2do&(DC{b5shZMsPG7ogl9zrqE#%07cUyacQaE>~K2kG&=@_ zqX@~MUR-Q)gWP=L%3M?%ai%w$;$CG`{Tjf!uO94Wu?5aoi*0nZm_Qb#i*a2I4b-bg zmS}L5Hz+WnV7351)udccfw=z(tW~&hZ^Js_~&K7Vd-eEOS>nA$i^RmGWfRc+i*RSQ$OnwG-($3=g=^74hEvG=xZ z)^5&-;mg}M$zL5)d(Gl3-mgb={H=pliSNZN;=B8X-x1^P(Zk%7Zm{r3ZZy=ih@i}7 z!4aC)(14Rqy`idy1vkxdh3l-L$Kdt30sq=zu8c;mP+d*dSXfOxMb&Bsr8f!+)6{V| z^CBz)aa6sgt8sczsKHZo79Of4Yid|fqsBqu>bNRUWQEiPU~P+W}4qL>!Td9YSHUJv3tR*O($I1kcl)z*T!?=83H3ewyv57J{} zH5XUiDhqPa2bUHbiv+`?H5S3}86Vs>^}uS^II2BGb*f!mrZ>7ycXenqlsC#y)$}GBPj8~CjWR?OI(;11Q{&d$fAHY_ zYsL*os~uN_WGYXn-h`> z!ov%aw8$E2OspCj&Xe*ZBJz`vwHh0v)`(=ECFInsnUkPKxb$eZ8XU&!XNQO5sOGR> z)g7(7BDyE!goVj)HO8&U@USo$uDMZl5v)%AZ2yO7H8@n;{vD=4T|ICJSdBt+GM;=}8> zTy-b`3#q}7K}4OPpgM#Rs}X|tXRL6n!LZN}7Rv0`P!QVF+1pVphLm)pb(6>SaeI;vb<(2)#b*b z9L{W@4hOn!=$`z)_@Gt=ivDNiT=J`asFrQDB%Z2C{{92+czx^W?3ht^#pa|oYn2{4 zeq|d`dULWK7FM{#J2o!yx|M&`3wPD7Q8T3`t6g(r!1MCD_#P6L9ER_Y2Ry3*&u<6B zXMqXc4G0K)HU|U*fGaApU=0F80yE)>H@?!jRqfkcO`7)^I=dijjv6|D-m=(;sP2QZ z?|LA!??`q_LTI>=5ar4UQxl?tSd03h;ja4bpr)aEeZ1x*goe55N4xCT)=uP_8X}wA zc@0w&-9f^K$VOt6s|iBgLEL45ay0>{7*}bfH%#*&ZbDR$j2m1QS}EIq)ep$k^`i|I z7$TRC{h81kSX8;Y{4e7L{UbR~n^Xt(8(7PC-^~Z@e2Whhb$5zdqV}8WYF=P|D4sU= zu~A|jZ%*UXigZ}Var;x|t7y7dQVo_=1xCjeYzCYED7#P8zJHa7uL?3o^kSiK-~KL( zXKx4$8VR?}il&~yxCbj1EH)1F=`7#v$!9cTWO-_F0S`9kuwMqIiu`ZQWA1i*O%44^ zo|h78-fjMIN;FR!$#df3>a^ucU>Iwvlz|#F&1#UxPa4`=ZX5qIH8wJT{_9-E4Ne)Yp-*4*IYHs* z#aM{5LTe(IRAg$qn6{iw>k+W&ip=3yisQQN2M6=E^_j6ybdye}yM4qa!0GgaeWlab za-PYSZx{c-xoQ8{u8y4CtLK#Lp(BUFz6w#hW9$T{(>KHE^kaL)Pjqk`i{HxHzx=Y; zwYAse$-T0MW@Zkha;pl4W5(0^XRx!2g|MFP#6zuqgMPY&Yh9I^zB_QjH%^DUg~{4v35$zHq@CZ!B0i{QBVA9@}o3+aH^* z4H_xWz4hQ~(UOENU4+|*9$A7DP0PPz=J3XFDRBjBP7v(PZkU~zoOeII>v->Q#3+~tmX zR2(?`wK%|<9U0Z1g{@U@*tdGsM<1s!3$SW3Ss>>(TF}{LS+&gR2rgNJ%s;~aOapUY+n|!-c zMfn@AiE^dcaFN{NCj5JSbeqLG< z+EJHoq~+kGdF%@SlhQ#ASc`AXuu~wwg`Oj2!~NA)1oR^Y^y0PMWDjxG-rGphqD^lRI_E#_;hSTlCQ>TlY2RFREZ`l+5 zW;7fkZl0Egae|`uSpD0N7YF@x?*11`)^9xh^QIS0^XZF5j##v4#K=WYC&tGmCB?-j zI=`RQHr(!}VPQdTH+w1E742>kUekpyA~ZfatWj)itScy;56+0ePi!pi<|`O;xuRp^ z(}P@mQ7rB;v9XN}SB!`WYwC6fh1aAzn;+i99qp1YF8#~M$DiN)Y{-h*y=Mmn&F)=$ zMaZ+8pMOl;(f!fx1viM3B9@(I^=~M^t9k5&)2Cn9P*Spf#Nx#xZdkPFo7e^oV&fY$ zFmH=bN{Wwb(0~t0ZCW$j<&FlP=Fje?z!o@%)il_RLFpMWhN}@Y&U=J;Opq%bWDS$J z8o=@oLe)YzT|5}&su|WSGTK!$yeW@x{I!Is_d|fj8ap9}K5>8i@nsf>-$k3!q-X0$_wn&j#243 zy*xvA;(`-A0nf~d>oQ^kpJ}sCW#;wRoDJ9>SovTO8E5l+KDv`}I?aZqc+z5$H62r1 z`JTMA)O@uhD}4WZ`jdM<4r(q+H~esF{c}H_V#6Vr!tS!2_|EOD{vyfb(vCLNU#lWx996%*}pr6ZgVFdD_k7u-b~ zcaxgoK~cnnL2NEw!oZ8DUDv=RCk|cw=+KEJVpFG?9mj~}^E=kOaDjJ-&y45oFI=eE zah_Noh6 z>|<7AP=5Xsoqvd!G}663_=^w6-W$nSR05)}GYisV+y>gRnb$45mEx9*YlDmy@!wq*Z1`L>D zI-&Otq4(ZH?>!`h^i4=Y0)&K6Hgyv=g|toE?1DA?-(1^3c9ZwsChz@V>0aGBbLPy< zDc?D>xi5^ncT$0YYJ1H#u~RxNq|am|5Khr$&fc-7&Q1K6_Xhm(z0k4%vZ0Xgh&4@TsG6 zELv1Uex{!FpC?Fl`M>?cLvr=s05N*tvg7 z=s0r!6!TX65#v0$#)tG2)3GXLa8E@V7b5-oSuPz5;e&gL>BPbX^-`oYN@rY$ewQs@ zB$qwRTq@~*+0D#5Ke_A{&O6TH8;3G3DZ+W@;LGg$%ggR&E^U)AidL3+DU?zA9`Q;p z!{OTAL&w$o8AQ>ehb@9@aM%RT_*8$VFE{hW^P?D#x5X`bfjvg%CzI%)Cx5;>J?YvQn`d)VKGLAHljzrk=wAJ^iYqG8 zANBbmOuj)F)6G9THjw_UM0wP`S8Y5gFaK_M?ts7h@}z&dJ0EMX_V6kvA85t>*5-1p zr8X+LiSAih&O^4A+VeBiV=3%eh&z!aV-VTKyoaE7Hpw8)`$vs!yU3M~j)k@3AV!G} zS7l6AkiS~vkS|E^4-HNZI1MR3KRei8t0&c*i;v01F+SGKH#CNfkIw!qJ0>t0woNZC zAS5<0Imja`HXzyClfyR97NA#!X2&x6GCsRsE)DQFMy8?|p)oT})o3s>oM79oO*+VW zxdG!q`_aSM(PVr~h{}zR4M+;fj&T7(Uai;q2W90$DLI7~hxjKxDtBnqe!*EWu1dK% zHak?M53mKsW_fU)-pK*j1|hB2oR2N?|+1$oh}OQMl(cNki4*3@wKlyqB7 z4M}%@zozD>CcCC4s23rrO)>Etws{wE>6E2_*`M@{`08Ta9=a#HGp5)2I~d_1;TLINS}c{v`2*I1g{HEN#{zsl$90#>-*J%+uongWyiIi zBx6dF!bm@dp10Q2xHu(guCAspxqpc2s&i0XR;^O`VYa!&lMJ?{>*Sn^)w}hou@hZ{ z>T*m@;NVey`=Pc-wx?j&x3z7s4l*ViE8C!T&_<$8qu?1MF*X9gUg9CfN?@9H-Vqi2 z3pF)4>Z3I^AG(uFlxwS!qct_HH}wv>Ei9=dvXrq^CnF!rO5@_?WQbJ0m-$<4dstq| zHs5tAEh{eVitjF^T$(MS^~(^xA})t37}SDx7}TP{2Q4#bFp=8g-apY6FShZMMr%vP ztOmYe8!YFH)tsdbS_f@pF=N#@mbC=yg=@iCDh0D$*;Pycgl5yWInnm|;u(HD-5(&0zu?GF z@Ka%c*#J6W3<*<+0_Gko25#_&H05Y);WmQ+p6#2RMdFA)xC-=$v-8wdtHzh4g7liGaGb!5OIn#P_`r? z$YK{GFmqtO$RQT9B0^!nB?h}yy4-1Z;&JT5?;}a8(g%qC;*QvGx17R&oQ8j7-!5z_ z>m=F=o-I3ktoVv8);qPVq3jlYb6A0*P;NCQX3q2X8fqqrL3C50BGgl<)F{I=8w&X zPKj^I`EJ1L{@eg>n`KHtWD@-;?%K^5(HI_7IyUt=FGXCCht^+oCy)B@{z^Z(PEEoC z=r#X00=XgsKPF7;zsG-!e|ebepQ9B8qPM1Svq|$5@1y=CS?854hBmo&@xlK7qj0^C zzcxU)@xrj$g3$wei}v{JthA|ilV6--Sd_ZIQXOl_9zVWUb91wQw^5zrVwB4M>a~hg zWnkW@QQ2a*UL$XO+BcX^96zpdRBEy&c3J*@uihc4v3|;c-g)7r=eDS0w4SDeis{x0K9-=ZDD=58KU>o?a?^=!e-ql+cSeb6Y)mm?$mJq7gyWpDqQPT@+$_GGWL z!EyExiEha|>HJr`pUaoi?bcZtE5r&(9tl3lLQ;}jf|$i+ zh)(>=vI)uFNvVpYq$z@+NlQ#^sR)h;>DM#ACDtdZB}Xm1kt901B#TMLBt^1cvY0GS z7Hl5L3Rv{+%VIYqdvlvGMId6pMFa4I0I><&?UaGxQ>^SK*wrS_TmDGORM#-izWj&^ z_ra}?JvJ;b}Vzl5Kb3>tHu}7QP_8b;^RJI$o}EElqO zAHDkJbYeL9A*s9FKrSzjF+Kq&&HE$`n`As~nC!wa8x2Dl9 z)v5mZQSpk(G;Og}xIvd5yGD<@R+BkLPX-}0lbd_E7rlAnMf%dmi%5@kdwod5NAkPH z4P<0!FkRK1kL=PU_>!=9m0om}eEG`b8S%;fWLQ^vxiTWY$Pro{VG+8d1~D2jee+d} zi*4~?y+q}SvWf4`^bI$0xzW3}&sgwoDE`|f%wIx)d|c>U-a;Y{b7+xK{Mo=xw0 zY(3+#I#uwTDY05IcY-0Now@gM$VfWQ}2WDB3TB zyFq#|)LY!FVV6pZivkusyt?fqt|eN;x>%ehZvun}%zF=gDcDcUPni7-jtGOp$+qoZ zp#)P6u9v>7q5;phD8j5T8zMl>&cbGx;NB)GgO6-lv(5o~qG+`6-Bei~lROfdx-<@p z?)rw3TUxl480eg)P#;p?lBY9$>lt9;j}dupd~7D%d1=5Gj4h#`%ek2;^#|nwd0do_ zO{!FqsuH@I7al1e{I0hL78DoOwd~^V(H{gds<_K8PRGlS8q-e;+_{pH5;~J8FXlOi z_bh2jfb;&Es5qK4k|;PYI$mv5hlHq1YVy?B*uXyP$5^79rdO(xvS-;zmUR&CpT*@S zr(`C%l!0+$8e^j^WBO~8RF$>9hz=^r&GP4?hGeEBE50o0JcIt!P)nK&4II$!T7w}f zn%ORSo{;W-q<3ut{b`Ck%3TwkQyM$xq@YP2$|fOn-HP7k}Xb zFD%&g_|d~>b~n?#&HK+BKKi))gA*KgV%Z}-Mh|%M2afyU#l~j(vy)J-$B!O;ocxIV z9qtTJ4!n~dcavO)}!k~j%SNC@`j&AinP6DV@NnC%*5Ib=I5(;63d z;fX`jrn!?fw0Zdga)QjCPlu9q<98RqXZ&Lk{fT%AxtW=^fdhAT$J)l9FWxebenXy~ zzj-!!8xra9$lS)Wbb5T` zm9aBm9au!hU%Gnr(v?5IM+cFk?|!=%HlQcyi1);Ot{W?e+X{fGw|{u!<_GlR*EEkt z5|Q3Phx!7mbw7^z8Fc4Q3fEcu8L-eg{3%c`d>bWHGjPvN)D+(~JGp*H+#?8kNH}oJ z?}>**`hg%2(=I{SC0rLai)+M3#0^60Y(e0s3mfobjo4-@1%2Uneuu5bErF@cARo-7 z>;*{a#9ql>1ES*HR@Bk}n&T9Aj+_z29S}o?i1>58yaVqo5ygY9m&7q5{RT=D9$rg<;n*TRSHG2(46tyN*RJHZG17Tx=AiQm!#S9+8B1zQN&v8c)sL|1+@m&`xR&EUoCEBia0 zIKbx}en+^m%0Vw|J4;txD@qoJ1|UHG`7g-9ZyKoWHGcXz)zqo{YtwnOwOo=-=78t) z&`A18c~Y2E%|1v+#(=+2Au!ITFx@X%$J@f8{@O9EGG{SEQA_HD0>&GZ5cgmhN9l8L z7;=oAhlO3L6{K^WWF}#uXen&;l`uUDs2C7^BDmXux$(w`*gT!U6@hz~w0OQ_rvFs^u(z94@^>VnKk_1&k1H@{*WM=UUX-IFzSn!k$M*2_Y?CYiY@ zb7vpl+YpMS&aVkKOA{0G6_G+lpgcBqfa%d(PoXvJ78&*8W&S$-GMs+*ex6>46pYQy zK|(a2JG$h=2|_*%6`uc??)!=3E&2n|VnOp8-+pn8EBRvRAmVkgP|k&ndg4aH)iYJ1 zqWai#92t&OBCPLX+z>#=TI6~g1pZ1k_%I;^qM5<0gRK^VrPHQY`SKQK>%dR~C5g>> zqJe>VK?NvkOs8^KVNFnQupHP75>DHIj?UDl1i(48a97|#V3P=T4*T4VylRcl3|s0& zGI7$fN2lq8+iNFwO`RCVDWh{08NKDTEh8&8DRQE`H_79x$8NeM=%!uEnKa>zZ~T0_ zU3yer7~EisB%@57R)APOQ{yYE>R#zrI&s$4^T$VP_E1VcqOTo|3tAl+m*F1uP13sJ z@fn$8%UAV!E`rO*-EWLM0@bwqi@lmjNAK=RP){uF`Np$@rmBlw_g0k*UR*QXza(ep znC688v-WR5A3p>cZx;GE>}Ty_D@=eAd+8Wd-@yU|z(inpNVanDKI|y%TBk|j;6MNR z%DQPUF5%Daxp{CoZROvP2apCo#UtCETYqzyc+UL`5;*qR(kVf!&}uE*4HI-tP098D+n1BOZQM6{zGxO^l0 zDQ&nE>06^U5MGpN^oBs&Wrvxu%N; z=|Xbu5&GEM+-+P0$PO?m7o?@o9efFQiyZ;0yR@BOWR`zs}?36{}vO{Qyn= zfKJi+omjj4Jh_l%iO$T7j?8csWK^f8S7-2l?WjaYrMQ0tEPLVdCZZ=B=oI?e;gipA zi%L(AVz;qRRC=bhqdINXWBJS4us!dnEk6B_2EfX`x%s{dwiT4^=jRAlA39ICc{q<5 zQ1JSd`*B=4|M2}7!|&hUah~1Z@%*;)IMV)p_WXap|Ci@~>wX;njrX_R-*H~LABdY> zG7)(zUWVr&6E^%}Sp`Of9LD9ZQ?KXaTen!0=poj;2;uT;5qPjBdP-j@f_~Q#Dvay zy%Q5Ubxv^2NKG9TIJDNMxq9l06^e|YH#le%j%pvNQw{98=uAb$oMK{g=>VZu-~D4BXFtB2jCqTtl@U^l$o7?zEf!jcOGgYmD$(+DvBz zTc7N|l()=HNa#H`F){H^6%|iRt!(nG92ml0q90l1)2C|E*@}web1Hj!Rt*YfM-9WP zJbPA7dWytqJcv?Xe$Yd6zCuT{Edhu6%yx#DJZAKxr6JL(CG|gAOos~@~X$y)L`fCr<^w#OU7|tt{>@Qy2SPUqvSb~NwUZ@v@`t^ zeTjDZD%^I4lb=bB7!zz=Mu#nn4B}lGRaN7w+5LE6P0dZ*kHk^Z{XcSEWGrs$OrDV* z^E}OUMWosuf83T@9BW;+G!oLu|4QAMz#8TRdttf=vs!*<-Npn(E+cD}S%b$!B%g7$ zp0WL>bvsxTV_mk)8e1&Yv29%YMY}P?RU6C-y+RZ^fJr9ANjzvZCNi+fu(=PFu|8ZI ziNrt+{aZ_sF~8C>={42k+ggRS4n6tHrfGc863eVH;H@ge8|6bIdG}mMD zgUJ;!C>TQ~6SH;^PA%o+*3~#5aNTKoH;Fjv&6%e8$gN zT%dG!_-y}zsbNX;-E&sKbR$y8rle_0Hh-@ppzmV@^!e@2?RmO|1@u*l9e?yQ1oY*& z`Rr>Ot-r4-iR7*#E?kBI=@MEuD?&yxfKvpK!Uh+!J znNpdIVKDf>vIYYhilGou5IwV+qkt%$(~eOX{1l8bL5({`$I)*GlO?B4B*jMQqPB_dywm7n|ZHS2!r z+1s7INLvS@Jjd=O%F~5+VRLEoeM-hUn6-6~3#~}!3tnc@9c<#vd?z8DBut954^um{ zsQa=>1G#kg!I9zf<_=qa<2@v4x^RJ8J$&B0@JJk6)>t>rGPbU9*}QQ2vt?Xe;}f%I zJkeMuuMY^H^UTvz`+HyE`jZQ<_q(DTF!kwY=7b0MN6dNJ&0}_t8ik^!$7~O`r{_d8 ziBSGqkr7_rP|M0q?+J0jpTYORpsOVeg$RR%l7VxX=r1LwXZn*-Wsy(1P0xMy87a8k z`r!dmN!}$j2Ns+rzVvUW>EDR&UsT}}Tnl|de-5_gr< zZpZF@BE*T$w!aVW^2IwNfbhioKpp8#Ofxg8^rZPVDfsL&dhRxFJV1}r2zmtXqu&u9 z>7|d8$+vEiBKAIjrA{Ng%m;WMahu98l~d`@ZSUK8?-MAIf5Q8Kbnd91Q_Ef_LzE62 zkMI}~y?j_l-A>cTsgWKg4dlWoGMFx1LcUo}H;v*>qH<5(xUu_l`V6@6JyNF{M0zfx z+T~;%-7UoqlNt=@*Cm|~2s;KW#W;uHR0bvmgjB)usWVdY%E<>0Oqq0O9pRolN4Vji ztY6=%;WXsX_lFKm;?5E|Z9Thx3jgw*NHTRj1|RY~o;w83WjZrhsm)ZNa@dm9aQCto zKuTrr(y7c`^pGJNPM#dO;pDeV)-T;Wa>S+$@}=ufpI%R=4X5;6kH1dh=6+K=<-ozo zv`}gfK&ixJJllgIVjn_7TQLwz(<|dymahOAsm z()HZi8)ykcVth8Z;?^@80gkMEhhj;tUnH>_B}aipbU&`Ppu{mPXC%7?TcC1l;?J;%}Ew{d4$T1u#1=3PgR0_i zswJC^!t6l46Pb#yryn1fl{R#KuU)Ii*g0;3+-)uyvwCN*`9o7@@24Mg-FbnY+d22K zx#wE%CPhXj@xnR$*hva72eBp2FfK4`-8j_C4J&@jfX8OTf-f_VWpl`j!-2qPf)2Da zDu@Q+%yw8<4SIg<>Pwf1zBxKWe9S6(^X)h3jWtn;e!5wQCZBz( zxUQVWuYdr#A|L>ElFwwhVu3Ic^$vt(Fcq}84-Suo?_V!x z!#KJgQ1spet41?b46qI_V$fN_8AU`1OAVB)koWA|xlO(2A(7atbj?N=7w0wS;_9`R zTDtt*b*T5ec~8%u2aj(W8Me{oy6V|}>L>2WxzEm<_bm0|B4ei38Y717TYVWc8dP4! zotcXVK06m_5~E_K)EX_r_O7{ndCjUp<>lbR5YR;_^<$QJM#AW#fCfDSiZJn52EBtN z%9Bb5fV(taNU5dR)YvgOISdX9*JWL&;NUf-rMbZ&UwnA_@So`i^7%#mJIxr`yn=-8 zo;zb*ynlp`#Wr@_tpt6bZQR_Me!a-Z^fe`=oq|JFm6mbOJ@(rB$InrZ(^L8G#S&$D6CJ6UnWN)vMdSn4ro-uS@mZAJAw~70w35af46hK1 zuEM0vAa9}qo*k=7ZS^J!45Plxo5OZe5EXC82HG?nz!QZ@o_1FN#*r0AV0iw(#ljh1>JR>Su+A6ZvxpSh&@$&N;i>EFoR$q6l&ZQ(lMf!8JgH8_mU5F7 z+{+X{2mP4k08NSs!T`|+raB8SW4}Q!#uV*j)(Eme4Gr?wUzaz$o*(O``*%7Ow38Ir z{gKni_#?;U<0qDkM+ob06Q-S>uDK}R0hap!WDSFc z)qNP*PyaNGWN|MbX{wC7L0Vlo(gEp|bP@N$23H%MpO?mg8!{^}m9R3YCH$P!`&flx zNp)y2AYqtPq;OVyvL5O(m;?eAMk*6uGqlQo_@5?iT)u4cD<+iMaIWPO(xxX&3?>Xv3-MZqjj77{bvWQJTz~|b@_NACle7WO@l6TF7h|A?+5hj z*Sa^ZYqhL=c`L%d7!MpLX~5v@#8}-4JRpUVu;Ll?>|3M2;EATeB!O=k4AAWtJba#= zx_`lOQZOb?nRKK+)!}D95)(Cbc<;W4>qq(94|j8>`X|x@f}P}#jrC4C+--!xaX2Pw z8oL1J0#a~~v;B&##3ypuvZA78%fjhPHWIX%-IMk(9QE zJ-zv&VP@NmVm?3RsR^CN|^!KA>ng>@4sh!!Uv|3vNP~!O)3}3_8XTt?&Xf zSznJFWJo@P9GPvNKM)_IHl`^_m}qmgFkoCTOp*7!f^gu5V8rQoj5pyc31_yIY*}4f zU4kceTT1Hv^d(g_tGkw$q?kBDbGV;c&qiAchK)GiQdt%rrt}>)Z1AvQzRJ*uvaT)X zM-0O{?XGM7aR$FQKl~Zu{P|aqXCNr5YHelJsz`4)H}A+*Rh4V2qJm<0-t{ECLK5gK zGDE3%yfk#!%R@rB@Eo;5?kMcmt=4m8~Tz%ucWi&)BI!n@#hz3FvKw| z_s5`93FySEQPr{**;L?;_Q=kGS{5tZpHfX>IO1V`zW*!m7w7PcIZE5Ft)WP=9AbT(u|4dA0F2o)fY!Bf(Th7J@I|4%-!JhR!35f}|2Zh*! zmCZX^ck$tm)3r}<*IVzEf2L5VwOX@QZPsbS)fj^u3(kpxN}~x^Ys^|rxCSW`j*RI8 z$Vr4oXV$30wOX~Cf^O=csPR?l^d^luLZi_jUqDpLUQbWlqBCi$laL4tU%!zRe%ob4iWD$J$sJ1J7WyPzKtR72f6ik zzu`-&TmM|$h1Zr4*L309=FK}-taClFcKNPd%QtWZ>sRdDeCMvy$={yS)HKI6e(cPd zV^cUI=XxJ%JvoB{jt3t*fhVn!^=ACrp*ld;@}l z%VVPniH=D3bPElG3K7tn3&^6^;ltw(AKsZ6S6*8lm-7L)h?WN1(;dMfj-cQavc{g4 zX;*n$a*8YEm0e1sy*<>y)~J}$=;+dzC~L6B-8;Ip3r-bhTYOaZ%yb7Yr{l;PQZaSd zRQ%DCab036qM|Bdavk)r;Gb>}3bLo$0|P;uD$wR#&?W>~rEIc#HWFH#dNw38A;ZcD zU{wJ20!f`=XV6VE;BsO(L93lNXo0}ZBA6PjB0N(HJ&5I0uyIi@GXN#Ukz`0G(n&r< zY3s2mlH87KbtuVd_a3S7+uUOphxYJJEY^8hS{yqg`iLAHoLTgl){&{{J4k%)7=D>r9}-_l z!rmS}Im|R8a3cLOC*GVC;zLK7Q+z@UN!-3ZmLd73zRll%S>{(<;;}-0WG#JTdP)l6 zoiHjW=adX9Gcle?#QHdQVWU!lqbq@WfeoGwN$ul~sQH(noq&ZEpcv!^Hj%VV4%lfQ z{MuNG0jC+240ESA-Ky?V-X3x1k8>!;40g-Sr1Oq1Y2v%qod1ft-)IG(fXR| zQcJL}wt~L9BRy^Iv_~e*nKS8;X{f*3ugse*vPrT<|DR@0Mq&o*zCQy4KBTjyfH5DM zbB+H8b1d2I8gq$$u2yB`YO*Fv(XUFa~vOTFm|4j*LjlB3lV7#&CzAx)BnAhSzyC6`~1QA(iO#BcSZeg&1kPz zRM2Z(yP|(~%r`8TUj)nDDisz5hK|IRQvNSy9r-oaOzuMKSFOJ_*U0Dg?%kR&a-?AX zjcEq@amSpJBFn|Q+YPb*o8X~+syd^q%M&RUoz9C7b#9SKX7wls< zak&lj2bY|e+cA8Xaf6ueI*H`eh z!I{_bqEAv%$L+`rx{#N7vBe9XZFhQLxG2sf<;eN=T7LQ$HC{n{QNf5R@7G$Oz4Ihb zMxg7ygZ{<*`rrYUUlLLP6QDX+030B}u@1K?Q^UdHL^MG+j^Kz8@t5?aOF0as$v*(?&yZ^ax&A!Nx(Bqd$ z=-p9<9;|Z_8OA$t0_bdLr!yc3n06)67_rF`Rk_I5$3}C!x#zG)uS^en)Ag%3eJas1F(VFBkHQBT$oMNnpt@ZP1u zw7Gi?;`P+@xBGkHJ??a$#6?}@^z+vX(!Z?s5=Wh|ygpb%Pi=m~i+8`;c@1D{*1%2^ z=g)gKK1Tc?k$2}8jwp{k)6MtQyO)Cc@XdPGujH~D_oxVxBlAZvkb=b^^Dm=(Biepy z7^=={aR|RWtC3>+kjX`oV38wK)avnzAy2H_c;!m&4TUmzSE7$_kaSY6;x3BH;GIc6 z@`I$I?N`t)_A7S(vuBZ}W(_@Y_L=?soYMMzB%YLzguV4;bA+^<&CRnXy|#C(t;f_Q zU-$WX{mbX*rMo997C&>XvFl6I9^2%4b7kJ>%_nF`(uux(j`!<#yieZ~Nl^vm`#03p zZP;I4fVm$0c=+DmxhK%C;FV+Ebg%&%M5T-Z^2duX4S&u5-YRgE0-2!aEn_cfQ^ny<0wA&!@{h58Qos zVXE6^#blGbv;1w=&YbM&dw&;M`Ps^TGJ=;N`Xt2(=qIcyz}knQ^msmysCIFvoZkIH z{l)OEeCr;!@8y(!)xUdpzEOBbepmgDZucVZ3~sHf$K~)7(|mK~f4RJ63w!?K_wMi} z;1XHJVc6viDjPs{8cwjPG^%iw!)iJgC-4VXexeO_o#Io;PUT}0gqvb(&euG>N4vox zbnWg{y0w`k9d?(>dl(N*#PgIL&x?@ovns)i2ihW3<`YPHS;5I81$vB2;Ycti+!ScS zW8B?lfOP-5L%zthkxpBIl|4Aw5exwB65<8=kq|A+n#IkSg)Iq#S22ve^vz6esUnjuc0MZaOs~u3 zM74zO#imo{GBJ}(NnPBDgv#ag4#LV^>*%A4$ymmZ7|SYArUJAE@ZFec?2MN%n%FRc zckE$AXI2H6E!+1n2QwrrZU8nMfIORIT&-yA+TJ#I-{ zdN4m{*W)#*My|Qp)8AQjX7@uUkodaMoRQ=#lrB5gei>&>t$F;IbXlP@DZ|`IX!YYY zX<>MR^k}q?D4UD&Z$bG*w2&6QstmSf5~4Ps(6gvIrq#l-2e6opH{&2MWj08HFb-gC zL>A$}NX^`Nm{;+w2a1(hN!Xg-u$YhxePV*4iTk>@AtAv)=f@_*#^uM6xXy90@o|b< zZ+c9g?D6KU=|cv$v`oMCrsw3zp7_-=xMj%n5o5!*%jN{+Nm`F=HpftBBSIX}^L0Ex-7xTSOiBQN9|HuZFo#?1Q1Jx>y6S9@ zi4zDhXxMzBfN;kUBCIWWyFp_^P+2}07}kf5i2^0u}wHcLI#^X1;RiMVQCqF*wbM$gUKwCEKUzD@8>2Q zp}Pfj^*P`8M+~E!A;*1r`nG(VDwe1(=r1${J>si%h5T(tc6m^3EWcU3R;Vb=x|ODU-CEo zNpL${?IzG`V-u0opnFr3;P#n5VK5!)=6W(q?1B7F!r`L%sS0{k5LD=0g_uYL<;)}x zdR}!H$y@{6;td?#>cbyVzN{4Jl|{s`Vj%rVMZb;GmUl*4f-J#@-X0W?RPv?w&DvDcI^=g!iF=l-$?Q46TRkj9ootIsZiM`T{8IgV zlntdRCDiW^(Mq`AM>xz2NMt8Tl)Q~+eFS!JJ3KdkfMw2n`l}@n+K~q8QuUf3&yH=umZTYFe(+pVs^_syyHUcW{7H zR>H7DK=8CHf9x?y7lB0y;8?Lx^&#UB?nen z%3JsRPU>(^cN(9YJH8X~9;o;3V^>6u>Dx4R&${JDy;@y=j6@IdjR@H(p84s)H^hvk zU4MV~;C6Bx{o86vcTvmltTO*pvg<4t;F|IW>&M?82ek0+r6Z%s@2}RYzq2ma{$L$M zN%or|JO7#u+W)No@qSYGbX^D z?lz1e5ywS9J7y*(&X&Pu&hY6heoq7Ic`|HMC9@trThX+SViESK?jOJ zH_-tT!GwGu50Q*RV=*%u2_j2$kanFAc1;2_0m!5_h!8e|l|W6fL7IZVYDeBYNYero zA9h~FFAOA$f9Oj`?s|f!m7mv;%x4!`Y?A`hiJ!+@zNV{M7vNg(y}NvEo*_87$zGx4 zW^EkbFJM5F_uY$?rJjp}`M_oFWsAu_9~Ecu;^FYDA-vv1UlLunI}HhFMPjX0M=y)C z>zBx^wSSkLG<`Q`H9zzcuMg$j{uaAX??pdc>_fU|Meg`clQ((Ye0kg+@8B?rE&b*bs78{J96o|nmzk80@N?PPdC zzgYeaGoM1OUWN6P&hULFAdK$&*l3td+#7!6ljv<7qDj@NpruLlt0pbtZ{5YZZrinB|Hg454%W8)c!DLISry!xZ5SbRxMdmI1QCX@QInqSYMn zZE%j9k^__)2?_g`oR8+ek8 z6Yii7`bn7BWUvOyngfQ}S?OhKGfn8OlIs zW=pqP7%YmdfW>{Q(S`=bLX^=HIH# zZyb;KjpM)hoZr#Tt~C$Z`~lVeJD!k-{t5+LPyNcl`+WZpbN{!@qxd)aO+Z%e8UG(B z_*)eJKc+0b@PPIY+T+0(@qa*X`qQtp)`MXO^ZCQ$P7z}8C&-o~ci;NoZiV0CS47_b zN8{CRJ?FpUdpSUN4~?|#9Oqj5YeyeY`N7#==lb8*Lw>L(nCt5X}SZZWF2sfMLoV){+jr+gyrd`(>^hcRxp72kvwqZoB={7_MLBwC&McFS|V;a>K$rm$Kv^6|VDm zD17v7?_jYO|N30A`(K_V?EaSr5NJk3d;#%Qh&4o{2JGZcgVVqwHl(c{`H>AoL9F;A zyn~)Pd;Y2TMKNbzev*`v@+Z$f8M`31z)at!r%sZbv-JB@C+YW3BBEnDsXF({x!6Uq zPrdpSJx;owd-Yu0qS#01SYoHgPSL-eCAp_h)92436ddh!{@z#O0L0SzNcw&|>vX`v z0nTQ!tR2=&Q#eFg1-u`LOw4V|mc;Oe9peS0UMRr`Q^Y`z|JDfJ$_|OVueI0`Sri#n zWQ{0}{KPE+D*!4K2k7hdzRGU!^>q`w%pE=sNj)Y6_-E%AOzV_4wWuH~Fn|oFtNZt! z3*dG)k`mI*OI5#ljQpCl*owbM_I>AjWC-K9KHXKmok;1_T(Nsnq=LIT%jV{7nz~?G ze*UxtQ%&CP$+KJu9;T%3SZ;7CeY}&es(X@|wH=T(Vkj`#35@n|L_)vN8xYL`?GuhW zhtm@p7W8>k0cK4^y0Ha8kpv#WGz$J>qAv|>^zXGLn-eo*isV}dHChMe{rG*}087u7 zEhHzh6j`vh^a|)1SW5J+Pr|qb+#)0Q41F^rb6l4*d~URf$h+=Ob<0yF5fl9&NtNf8 zy1&>nIJ}HM(`8&{21(@Ru63nz=UJ*b$kEg8eZ{|nHsX;*B^qA2T2w&)i)?XFexzh^ zu+3Nyq995H-f%{W=c{v>~SH4q_I;yFxImuJb{eb1;))6(Q zq}R&@ou6N{-Vp7F0P)=+F`jqV?v!ct8JmKfic?zj#m3g_lhN z@1B(CqJUl`i*QGpc6>1U!Ilz3(2#Aj1C~)HdT)Y;grnBXyo^XIZLkFrn^SB8YV0eb za~-2!W2@y?UrUX8k!ZQZgdq}*2(l`Hew0~slEBVlJ@x1QYQNPex!umC z6!{_PC-pO$ghy-Lt$Qn~(lVB&XH=dl9-Pyeewa@-x~97JlIt{zeYhTsmpFp?A7+bp z;jv&b$PcCpn5i{7o8IBXT64_R?zDvSSV*KTp&vRqn`^;j+^5NniOG%0DLs-B8qbLv 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ℌ*IIa96aU~;z(a&l-;O~SwKYI>8=rHgfLY=eO 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�f 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�BvDr!|Vt!C6}YIU`ST2rm1)>iANb=7)meYJu5 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 diff --git a/docs/fonts/dejavu-sans-boldoblique.ttf b/docs/fonts/dejavu-sans-boldoblique.ttf deleted file mode 100644 index 753f2d80b1f9a13026d641b6fd4cafb8d85dd479..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 643292 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�K>t5e2w`pONAN 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 _Sm54x-B)eBi#i*b3HrW76GSwDCbw}ep<%0yB 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 z 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