From b242f83a58391ba3cc2599fc344fb24b7ae4caee Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Tue, 20 Jun 2023 09:13:22 +0200 Subject: [PATCH] [milestone/12.0.1] Milestone 12.0.1 (#1220) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * - BrowserNavigationViewController: - add ability to show/hide navigation bar (with or without animation) - add convenience accessor to UIViewController to access the enclosing browserNavigationViewController - Branding+App: fix SwiftLint warnings - PDFViewerViewController: hide navigation bar via browserNavigationController rather than navigationController (fix finding (7)) * - UIView+EmbedLayout: add new .safeAreaWithKeyboardAnchorSet property for laying out the view in a keyboard-responsive manner - CollectionViewController: add .compressForKeyboard property that layouts the UICollectionView in a keyboard-responsive manner - ShareViewController: adopt compressForKeyboard - ClientItemViewController: adopt compressForKeyboard - fixes finding (1) in #1095 * - fix finding (9) in #1095 * - fix finding (12) in #1095 * - ComposedMessageView: apply syntactic sugar - MediaDisplayViewController: when streaming, show a loading indicator (fixing finding (5) in #1095) * - fix localization typo * - MediaDisplayViewController: show download indicator when downloading video files (fixing part 2 of finding (5) in #1095) * - BottomButtonBar: vertically align also the prompt label to selectButton.centerYAnchor - ClientLocationPicker: prevent usage of .keyboardLayoutGuide to fix finding (13) in #1220 (misplaced select button after navigating to Files view) - CollectionViewController: use the .safeAreaWithKeyboardAnchorSet for the whole stack view rather than just the collection view * - new build and version number - added in-app release notes - added calens release notes * Calens changelog updated * updated fastlane release notes --------- Co-authored-by: Matthias Hühne Co-authored-by: hosy Co-authored-by: Matthias Hühne --- CHANGELOG.md | 22 +++ changelog/12.0.1_2023-06-15/1220 | 5 + fastlane/metadata-emm/en-US/release_notes.txt | 27 +-- .../en-US/release_notes.txt | 27 +-- fastlane/metadata/en-US/release_notes.txt | 27 +-- ownCloud File Provider UI/Info.plist | 2 +- ownCloud.xcodeproj/project.pbxproj | 8 +- .../Media/MediaDisplayViewController.swift | 176 +++++++++++++----- .../Viewer/PDF/PDFViewerViewController.swift | 2 +- ownCloud/Release Notes/ReleaseNotes.plist | 19 +- .../Resources/de.lproj/Localizable.strings | 2 +- ownCloudAppShared/Branding/Branding+App.swift | 4 +- .../AccountConnectionAuthErrorConsumer.swift | 1 + .../CollectionViewController.swift | 9 +- .../Client/Sharing/ShareViewController.swift | 2 +- .../User Interface/BottomButtonBar.swift | 2 +- .../User Interface/ComposedMessageView.swift | 2 +- .../ClientItemViewController.swift | 2 +- .../ClientLocationPicker.swift | 26 ++- .../BrowserNavigationViewController.swift | 53 +++++- .../SegmentView/UIView+EmbedAndLayout.swift | 4 + 21 files changed, 277 insertions(+), 145 deletions(-) create mode 100644 changelog/12.0.1_2023-06-15/1220 diff --git a/CHANGELOG.md b/CHANGELOG.md index e18cc6069..f6bf0923b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +Changelog for ownCloud iOS Client [12.0.1] (2023-06-15) +======================================= +The following sections list the changes in ownCloud iOS Client 12.0.1 relevant to +ownCloud admins and users. + +[12.0.1]: https://github.com/owncloud/ios-app/compare/milestone/12.0.0...milestone/12.0.1 + +Summary +------- + +* Bugfix - Several Bug Fixes: [#1220](https://github.com/owncloud/ios-app/pull/1220) + +Details +------- + +* Bugfix - Several Bug Fixes: [#1220](https://github.com/owncloud/ios-app/pull/1220) + + Fixed keyboard, media streaming, full screen mode, offline indicator, duplicated sharing + option, and UI issues. + + https://github.com/owncloud/ios-app/pull/1220 + Changelog for ownCloud iOS Client [12.0.0] (2023-06-12) ======================================= The following sections list the changes in ownCloud iOS Client 12.0.0 relevant to diff --git a/changelog/12.0.1_2023-06-15/1220 b/changelog/12.0.1_2023-06-15/1220 new file mode 100644 index 000000000..7f70d45dd --- /dev/null +++ b/changelog/12.0.1_2023-06-15/1220 @@ -0,0 +1,5 @@ +Bugfix: Several Bug Fixes + +Fixed keyboard, media streaming, full screen mode, offline indicator, duplicated sharing option, and UI issues. + +https://github.com/owncloud/ios-app/pull/1220 diff --git a/fastlane/metadata-emm/en-US/release_notes.txt b/fastlane/metadata-emm/en-US/release_notes.txt index 741fea434..84555fb29 100644 --- a/fastlane/metadata-emm/en-US/release_notes.txt +++ b/fastlane/metadata-emm/en-US/release_notes.txt @@ -1,26 +1,3 @@ -• Version 12 Major Release -Rearchitectured for iOS 15 and later. +• Bug Fixes +Fixed keyboard, media streaming, full screen mode, offline indicator, duplicated sharing option, and UI issues. -• ownCloud Infinite Scale support -Support for Spaces, Authenticated WebFinger and other new oCIS features. - -• New Search Capabilities -Powerful new search UI, saved searches and search templates. - -• New Navigation -Navigate via the new sidebar, breadcrumbs and browser controls. - -• Role-based Sharing Interface -The new role-based sharing user interface makes creating and editing shares and links even easier. - -• Grid View Modes -Switch between list and several, different grid modes to display your folder's contents in new ways. - -• App Provider support -Create and edit new documents through app providers on servers that support them. - -• Improved Theming -The updated dark and light themes make use of a new, CSS-based theming system. - -• MDM Enhancements -Many new MDM parameters. diff --git a/fastlane/metadata-owncloud-online/en-US/release_notes.txt b/fastlane/metadata-owncloud-online/en-US/release_notes.txt index 741fea434..84555fb29 100644 --- a/fastlane/metadata-owncloud-online/en-US/release_notes.txt +++ b/fastlane/metadata-owncloud-online/en-US/release_notes.txt @@ -1,26 +1,3 @@ -• Version 12 Major Release -Rearchitectured for iOS 15 and later. +• Bug Fixes +Fixed keyboard, media streaming, full screen mode, offline indicator, duplicated sharing option, and UI issues. -• ownCloud Infinite Scale support -Support for Spaces, Authenticated WebFinger and other new oCIS features. - -• New Search Capabilities -Powerful new search UI, saved searches and search templates. - -• New Navigation -Navigate via the new sidebar, breadcrumbs and browser controls. - -• Role-based Sharing Interface -The new role-based sharing user interface makes creating and editing shares and links even easier. - -• Grid View Modes -Switch between list and several, different grid modes to display your folder's contents in new ways. - -• App Provider support -Create and edit new documents through app providers on servers that support them. - -• Improved Theming -The updated dark and light themes make use of a new, CSS-based theming system. - -• MDM Enhancements -Many new MDM parameters. diff --git a/fastlane/metadata/en-US/release_notes.txt b/fastlane/metadata/en-US/release_notes.txt index 741fea434..84555fb29 100644 --- a/fastlane/metadata/en-US/release_notes.txt +++ b/fastlane/metadata/en-US/release_notes.txt @@ -1,26 +1,3 @@ -• Version 12 Major Release -Rearchitectured for iOS 15 and later. +• Bug Fixes +Fixed keyboard, media streaming, full screen mode, offline indicator, duplicated sharing option, and UI issues. -• ownCloud Infinite Scale support -Support for Spaces, Authenticated WebFinger and other new oCIS features. - -• New Search Capabilities -Powerful new search UI, saved searches and search templates. - -• New Navigation -Navigate via the new sidebar, breadcrumbs and browser controls. - -• Role-based Sharing Interface -The new role-based sharing user interface makes creating and editing shares and links even easier. - -• Grid View Modes -Switch between list and several, different grid modes to display your folder's contents in new ways. - -• App Provider support -Create and edit new documents through app providers on servers that support them. - -• Improved Theming -The updated dark and light themes make use of a new, CSS-based theming system. - -• MDM Enhancements -Many new MDM parameters. diff --git a/ownCloud File Provider UI/Info.plist b/ownCloud File Provider UI/Info.plist index 718845e3d..8df1972b8 100644 --- a/ownCloud File Provider UI/Info.plist +++ b/ownCloud File Provider UI/Info.plist @@ -30,7 +30,7 @@ NSExtensionFileProviderActionIdentifier com.owncloud.FileProviderUI.Share NSExtensionFileProviderActionName - Share + Share by cloud NSExtensionMainStoryboard diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 987a6995e..a17ff7387 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -5137,8 +5137,8 @@ APP_BUILD_FLAGS = "$(inherited)"; APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; - APP_SHORT_VERSION = 12.0; - APP_VERSION = 267; + APP_SHORT_VERSION = 12.0.1; + APP_VERSION = 268; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -5207,8 +5207,8 @@ APP_BUILD_FLAGS = "$(inherited)"; APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; - APP_SHORT_VERSION = 12.0; - APP_VERSION = 267; + APP_SHORT_VERSION = 12.0.1; + APP_VERSION = 268; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; diff --git a/ownCloud/Client/Viewer/Media/MediaDisplayViewController.swift b/ownCloud/Client/Viewer/Media/MediaDisplayViewController.swift index 0d648e43d..60bae6581 100644 --- a/ownCloud/Client/Viewer/Media/MediaDisplayViewController.swift +++ b/ownCloud/Client/Viewer/Media/MediaDisplayViewController.swift @@ -71,25 +71,43 @@ class MediaDisplayViewController : DisplayViewController { NotificationCenter.default.removeObserver(self, name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: nil) } - override func viewDidLoad() { - super.viewDidLoad() - - playerViewController = AVPlayerViewController() - - guard let playerViewController = playerViewController else { return } - - addChild(playerViewController) - self.view.addSubview(playerViewController.view) - playerViewController.didMove(toParent: self) + var showLoadingIndicator: Bool = false { + didSet { + if oldValue != showLoadingIndicator { + if showLoadingIndicator { + // Show loading indicator + let indeterminateProgress: Progress = .indeterminate() + indeterminateProgress.isCancellable = false + + let messageView = ComposedMessageView.infoBox(additionalElements: [ + .spacing(25), + .progressCircle(with: indeterminateProgress), + .spacing(25), + .title("Loading…".localized, alignment: .centered) + ], withRoundedBackgroundView: true) + + loadingIndicator = messageView + } else { + // Remove loading indicator + loadingIndicator = nil + } + } + } + } - playerViewController.view.translatesAutoresizingMaskIntoConstraints = false + private var loadingIndicator: ComposedMessageView? { + willSet { + loadingIndicator?.removeFromSuperview() + } + didSet { + if let loadingIndicator { + view.embed(centered: loadingIndicator) + } + } + } - NSLayoutConstraint.activate([ - playerViewController.view.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor), - playerViewController.view.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor), - playerViewController.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), - playerViewController.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor) - ]) + override func viewDidLoad() { + super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(handleDidEnterBackgroundNotification), name: UIApplication.didEnterBackgroundNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(handleWillEnterForegroundNotification), name: UIApplication.willEnterForegroundNotification, object: nil) @@ -131,7 +149,28 @@ class MediaDisplayViewController : DisplayViewController { return (OCAppIdentity.shared.userDefaults?.downloadMediaFiles ?? false) } + private var timeControlStatusObservation: NSKeyValueObservation? + override func renderItem(completion: @escaping (Bool) -> Void) { + if playerViewController == nil { + playerViewController = AVPlayerViewController() + + if let playerViewController { + addChild(playerViewController) + self.view.addSubview(playerViewController.view) + playerViewController.didMove(toParent: self) + + playerViewController.view.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + playerViewController.view.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor), + playerViewController.view.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor), + playerViewController.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), + playerViewController.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor) + ]) + } + } + if let directURL = itemDirectURL { playerItemStatusObservation?.invalidate() playerItemStatusObservation = nil @@ -149,7 +188,7 @@ class MediaDisplayViewController : DisplayViewController { if player == nil { player = AVPlayer(playerItem: playerItem) player?.allowsExternalPlayback = true - if let playerViewController = self.playerViewController { + if let playerViewController { playerViewController.updatesNowPlayingInfoCenter = false if UIApplication.shared.applicationState == .active { @@ -157,42 +196,18 @@ class MediaDisplayViewController : DisplayViewController { } } - // Add artwork to the player overlay if corresponding meta data item is available in the asset - if !(player?.isVideoAvailable ?? false), let artworkMetadataItem = asset.commonMetadata.filter({$0.commonKey == AVMetadataKey.commonKeyArtwork}).first, - let imageData = artworkMetadataItem.dataValue, - let overlayView = playerViewController?.contentOverlayView { - - if let artworkImage = UIImage(data: imageData) { - - // Construct image view overlay for AVPlayerViewController - let imageView = UIImageView(image: artworkImage) - imageView.translatesAutoresizingMaskIntoConstraints = false - imageView.contentMode = .scaleAspectFit - playerViewController?.contentOverlayView?.addSubview(imageView) - - NSLayoutConstraint.activate([ - imageView.leadingAnchor.constraint(equalTo: overlayView.leadingAnchor), - imageView.trailingAnchor.constraint(equalTo: overlayView.trailingAnchor), - imageView.topAnchor.constraint(equalTo: overlayView.topAnchor), - imageView.bottomAnchor.constraint(equalTo: overlayView.bottomAnchor) - ]) - - // Create MPMediaItemArtwork to be shown in 'now playing' in the lock screen - mediaItemArtwork = MPMediaItemArtwork(boundsSize: artworkImage.size, requestHandler: { (_) -> UIImage in - return artworkImage - }) - } - } - - // Extract title meta-data item - mediaItemTitle = asset.commonMetadata.filter({$0.commonKey == AVMetadataKey.commonKeyTitle}).first?.value as? String + // Start with the loading indicator active + showLoadingIndicator = true - // Extract artist meta-data item - mediaItemArtist = asset.commonMetadata.filter({$0.commonKey == AVMetadataKey.commonKeyArtist}).first?.value as? String + // .. it will be updated as soon as the player starts playing .. + timeControlStatusObservation = player?.observe(\AVPlayer.timeControlStatus, changeHandler: { [weak self] player, change in + self?.updateLoadingIndicator() + }) // Setup player status observation handler playerStatusObservation = player!.observe(\AVPlayer.status, options: [.initial, .new], changeHandler: { [weak self] (player, _) in if player.status == .readyToPlay { + self?.updateMediaMetadata() self?.setupRemoteTransportControls() @@ -200,7 +215,11 @@ class MediaDisplayViewController : DisplayViewController { try? AVAudioSession.sharedInstance().setActive(true) if (self?.hasFocus)! { + // .. with playback starting here. self?.player?.play() + } else { + // .. or the loading indicator being updated when the file is ready to play, here. + self?.updateLoadingIndicator() } self?.updateNowPlayingInfoCenter() @@ -218,6 +237,55 @@ class MediaDisplayViewController : DisplayViewController { } } + private func updateLoadingIndicator() { + if let player { + let showLoadingIndicator = (player.timeControlStatus == .waitingToPlayAtSpecifiedRate) + + OnMainThread(inline: true) { + self.showLoadingIndicator = showLoadingIndicator + } + } + } + + private func updateMediaMetadata() { + guard let asset = playerItem?.asset else { return } + + // Add artwork to the player overlay if corresponding meta data item is available in the asset + if !(player?.isVideoAvailable ?? false), let artworkMetadataItem = asset.commonMetadata.filter({$0.commonKey == AVMetadataKey.commonKeyArtwork}).first, + let imageData = artworkMetadataItem.dataValue, + let overlayView = playerViewController?.contentOverlayView { + + if let artworkImage = UIImage(data: imageData) { + + // Construct image view overlay for AVPlayerViewController + OnMainThread(inline: true) { [weak self] in + let imageView = UIImageView(image: artworkImage) + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.contentMode = .scaleAspectFit + self?.playerViewController?.contentOverlayView?.addSubview(imageView) + + NSLayoutConstraint.activate([ + imageView.leadingAnchor.constraint(equalTo: overlayView.leadingAnchor), + imageView.trailingAnchor.constraint(equalTo: overlayView.trailingAnchor), + imageView.topAnchor.constraint(equalTo: overlayView.topAnchor), + imageView.bottomAnchor.constraint(equalTo: overlayView.bottomAnchor) + ]) + } + + // Create MPMediaItemArtwork to be shown in 'now playing' in the lock screen + mediaItemArtwork = MPMediaItemArtwork(boundsSize: artworkImage.size, requestHandler: { (_) -> UIImage in + return artworkImage + }) + } + } + + // Extract title meta-data item + mediaItemTitle = asset.commonMetadata.filter({$0.commonKey == AVMetadataKey.commonKeyTitle}).first?.value as? String + + // Extract artist meta-data item + mediaItemArtist = asset.commonMetadata.filter({$0.commonKey == AVMetadataKey.commonKeyArtist}).first?.value as? String + } + private func present(error:Error?) { guard let error = error else { return } @@ -230,12 +298,18 @@ class MediaDisplayViewController : DisplayViewController { } } + private var isInBackground: Bool = false { + didSet { + playerViewController?.player = isInBackground ? nil : player + } + } + @objc private func handleDidEnterBackgroundNotification() { - playerViewController?.player = nil + isInBackground = true } @objc private func handleWillEnterForegroundNotification() { - playerViewController?.player = player + isInBackground = false } @objc private func handleAVPlayerItem(notification:Notification) { diff --git a/ownCloud/Client/Viewer/PDF/PDFViewerViewController.swift b/ownCloud/Client/Viewer/PDF/PDFViewerViewController.swift index 7e3b0bf12..ea8f5db47 100644 --- a/ownCloud/Client/Viewer/PDF/PDFViewerViewController.swift +++ b/ownCloud/Client/Viewer/PDF/PDFViewerViewController.swift @@ -116,7 +116,7 @@ class PDFViewerViewController: DisplayViewController, DisplayExtension, UIPopove private var fullScreen: Bool = false { didSet { - self.navigationController?.setNavigationBarHidden(fullScreen, animated: true) + browserNavigationViewController?.setNavigationBarHidden(fullScreen, animated: true) isFullScreenModeEnabled = fullScreen pageCountButton.isHidden = fullScreen pageCountContainerView.isHidden = fullScreen diff --git a/ownCloud/Release Notes/ReleaseNotes.plist b/ownCloud/Release Notes/ReleaseNotes.plist index 8d0f8d53d..5e3ad11b0 100644 --- a/ownCloud/Release Notes/ReleaseNotes.plist +++ b/ownCloud/Release Notes/ReleaseNotes.plist @@ -1669,7 +1669,7 @@ Added an optional "Wait for completion" option to the "Save File& Title Grid View Modes Subtitle - Switch between list and several, different grid modes to display your folder's contents in new ways. + Switch between list and several, different grid modes to display your folder's contents in new ways. Type New ImageName @@ -1707,6 +1707,23 @@ Added an optional "Wait for completion" option to the "Save File& + + Version + 12.0.1 + ReleaseNotes + + + Title + Bug Fixes + Subtitle + Fixed keyboard, media streaming, full screen mode, offline indicator, duplicated sharing option, and UI issues. + Type + Fix + ImageName + wrench + + + diff --git a/ownCloudAppFramework/Resources/de.lproj/Localizable.strings b/ownCloudAppFramework/Resources/de.lproj/Localizable.strings index d3c3daaab..c5ab8c4bb 100644 --- a/ownCloudAppFramework/Resources/de.lproj/Localizable.strings +++ b/ownCloudAppFramework/Resources/de.lproj/Localizable.strings @@ -116,7 +116,7 @@ "Before last week" = "Vor letzter Woche"; ">%d weeks ago" = "Vor >%d Wochen"; "This week" = "Diese Woche"; -"Since last week" = "Seit letzter WOche"; +"Since last week" = "Seit letzter Woche"; "Last %d weeks" = "Letzte %d Wochen"; "Before this month" = "Vor diesem Monat"; diff --git a/ownCloudAppShared/Branding/Branding+App.swift b/ownCloudAppShared/Branding/Branding+App.swift index ae75df02a..4f3668a6d 100644 --- a/ownCloudAppShared/Branding/Branding+App.swift +++ b/ownCloudAppShared/Branding/Branding+App.swift @@ -372,8 +372,8 @@ extension Branding { return url(forClassSettingsKey: .profileHelpURL) ?? nil } - public var sidebarLinks: Array? { - if let values = computedValue(forClassSettingsKey: .sidebarLinks) as? Array> { + public var sidebarLinks: [SidebarLink]? { + if let values = computedValue(forClassSettingsKey: .sidebarLinks) as? [[String:String]] { return values.compactMap { link in if let title = link["title"], let urlString = link["url"], let url = URL(string: urlString) { return SidebarLink(title: title, symbol: link["symbol"], image: link["image"], url: url) diff --git a/ownCloudAppShared/Client/Account/Connection/Authentication Error Handling/AccountConnectionAuthErrorConsumer.swift b/ownCloudAppShared/Client/Account/Connection/Authentication Error Handling/AccountConnectionAuthErrorConsumer.swift index 702895d11..a2b00916b 100644 --- a/ownCloudAppShared/Client/Account/Connection/Authentication Error Handling/AccountConnectionAuthErrorConsumer.swift +++ b/ownCloudAppShared/Client/Account/Connection/Authentication Error Handling/AccountConnectionAuthErrorConsumer.swift @@ -234,6 +234,7 @@ class AccountConnectionAuthErrorConsumer: AccountConnectionConsumer, AccountConn OCSynchronized(self) { self.skipAuthorizationFailure = false // Auth failure fixed -> allow new failures to prompt for sign in again } + self.connection?.updateConnectionStatusSummary() // Trigger status summary update to clear connection._authFailureStatus } else if let nsError = error as NSError?, !nsError.isOCError(withCode: .authorizationCancelled) { // Error updating authentication -> inform the user and provide option to retry context.alertQueue?.async { [weak self] (queueCompletionHandler) in diff --git a/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift b/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift index 6f052bd35..b9d428724 100644 --- a/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift +++ b/ownCloudAppShared/Client/Collection Views/CollectionViewController.swift @@ -31,13 +31,16 @@ open class CollectionViewController: UIViewController, UICollectionViewDelegate, var didHighlightItemReference: Bool = false var hideNavigationBar: Bool? + var compressForKeyboard: Bool + var emptyCellRegistration: UICollectionView.CellRegistration? - public init(context inContext: ClientContext?, sections inSections: [CollectionViewSection]?, useStackViewRoot: Bool = false, hierarchic: Bool = false, useWrappedIdentifiers: Bool = false, highlightItemReference: OCDataItemReference? = nil) { + public init(context inContext: ClientContext?, sections inSections: [CollectionViewSection]?, useStackViewRoot: Bool = false, hierarchic: Bool = false, compressForKeyboard: Bool = false, useWrappedIdentifiers: Bool = false, highlightItemReference: OCDataItemReference? = nil) { supportsHierarchicContent = hierarchic usesStackViewRoot = useStackViewRoot self.useWrappedIdentifiers = hierarchic ? hierarchic : useWrappedIdentifiers self.highlightItemReference = highlightItemReference + self.compressForKeyboard = compressForKeyboard super.init(nibName: nil, bundle: nil) @@ -166,8 +169,8 @@ open class CollectionViewController: UIViewController, UICollectionViewDelegate, if usesStackViewRoot { createStackView() - if let stackView = stackView { - view.embed(toFillWith: stackView, enclosingAnchors: view.defaultAnchorSet) + if let stackView { + view.embed(toFillWith: stackView, enclosingAnchors: compressForKeyboard ? view.safeAreaWithKeyboardAnchorSet : view.safeAreaAnchorSet) } } } diff --git a/ownCloudAppShared/Client/Sharing/ShareViewController.swift b/ownCloudAppShared/Client/Sharing/ShareViewController.swift index 3af47fa10..332cc7ee6 100644 --- a/ownCloudAppShared/Client/Sharing/ShareViewController.swift +++ b/ownCloudAppShared/Client/Sharing/ShareViewController.swift @@ -198,7 +198,7 @@ open class ShareViewController: CollectionViewController, SearchViewControllerDe sections.append(optionsSection) } - super.init(context: shareControllerContext, sections: sections, useStackViewRoot: true) + super.init(context: shareControllerContext, sections: sections, useStackViewRoot: true, compressForKeyboard: true) self.cssSelector = .grouped diff --git a/ownCloudAppShared/Client/User Interface/BottomButtonBar.swift b/ownCloudAppShared/Client/User Interface/BottomButtonBar.swift index 96042c674..84e3b000a 100644 --- a/ownCloudAppShared/Client/User Interface/BottomButtonBar.swift +++ b/ownCloudAppShared/Client/User Interface/BottomButtonBar.swift @@ -129,7 +129,7 @@ open class BottomButtonBar: ThemeCSSView { constraints.append(contentsOf: [ promptLabel.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 20), promptLabel.trailingAnchor.constraint(lessThanOrEqualTo: leadingButtonAnchor, constant: -20), - promptLabel.centerYAnchor.constraint(equalTo: safeAreaLayoutGuide.centerYAnchor), + promptLabel.centerYAnchor.constraint(equalTo: selectButton.centerYAnchor), selectButton.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -20), selectButton.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 20), diff --git a/ownCloudAppShared/Client/User Interface/ComposedMessageView.swift b/ownCloudAppShared/Client/User Interface/ComposedMessageView.swift index c5398ba2b..398e32c44 100644 --- a/ownCloudAppShared/Client/User Interface/ComposedMessageView.swift +++ b/ownCloudAppShared/Client/User Interface/ComposedMessageView.swift @@ -557,7 +557,7 @@ public extension ComposedMessageView { static func infoBox(image: UIImage? = nil, title: String? = nil, subtitle: String? = nil, additionalElements: [ComposedMessageElement]? = nil, withRoundedBackgroundView: Bool = true) -> ComposedMessageView { var elements: [ComposedMessageElement] = [] - if let image = image { + if let image { let imageElement: ComposedMessageElement = .image(image, size: CGSize(width: 48, height: 48), alignment: .centered, cssSelectors: [.icon]) imageElement.insets = NSDirectionalEdgeInsets(top: 0, leading: 10, bottom: 10, trailing: 10) elements.append(imageElement) diff --git a/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift b/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift index e320c9743..a1d190b35 100644 --- a/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift +++ b/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift @@ -211,7 +211,7 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, emptySection = CollectionViewSection(identifier: "empty", dataSource: emptySectionDataSource, 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, highlightItemReference: highlightItemReference) + super.init(context: itemControllerContext, sections: sections, useStackViewRoot: true, compressForKeyboard: true, highlightItemReference: highlightItemReference) // Track query state and recompute content state when it changes stateObservation = itemsListDataSource?.observe(\OCDataSource.state, options: [], changeHandler: { [weak self] query, change in diff --git a/ownCloudAppShared/Client/View Controllers/Location Picker/ClientLocationPicker.swift b/ownCloudAppShared/Client/View Controllers/Location Picker/ClientLocationPicker.swift index 491cb6601..a4b413cbf 100644 --- a/ownCloudAppShared/Client/View Controllers/Location Picker/ClientLocationPicker.swift +++ b/ownCloudAppShared/Client/View Controllers/Location Picker/ClientLocationPicker.swift @@ -303,7 +303,7 @@ public class ClientLocationPicker : NSObject { context.add(permissionHandler: { [weak self] context, dataItemRecord, checkInteraction, viewController in return self?.checkPermission(context: context, dataItemRecord: dataItemRecord, interaction: checkInteraction, viewController: viewController) ?? false }) - context.viewControllerPusher = nil + context.viewControllerPusher = self context.browserController = nil context.navigationController = navigationController context.permissions = [ .selection ] @@ -382,6 +382,30 @@ public class ClientLocationPicker : NSObject { } } +// MARK: - ViewControllerPusher +// Implemented solely to disable .compressForKeyboard for the collection views in the picker, as the usage of the keyboardLayoutGuide +// inexplicably leads to a bogus BottomBar layout when pushing the first "Files" view controller +extension ClientLocationPicker : ViewControllerPusher { + public func pushViewController(context: ClientContext?, provider: (ClientContext) -> UIViewController?, push: Bool, animated: Bool) -> UIViewController? { + if let context { + let viewController = provider(context) + + if let collectionViewController = viewController as? CollectionViewController { + // Disable .compressForKeyboard for CollectionViewController + collectionViewController.compressForKeyboard = false + } + + if push, let viewController { + context.navigationController?.pushViewController(viewController, animated: animated) + } + + return viewController + } + + return nil + } +} + extension ThemeCSSSelector { static let accountList = ThemeCSSSelector(rawValue: "accountList") } diff --git a/ownCloudAppShared/User Interface/Browser Navigation Controller/BrowserNavigationViewController.swift b/ownCloudAppShared/User Interface/Browser Navigation Controller/BrowserNavigationViewController.swift index bc0631725..3f46c603b 100644 --- a/ownCloudAppShared/User Interface/Browser Navigation Controller/BrowserNavigationViewController.swift +++ b/ownCloudAppShared/User Interface/Browser Navigation Controller/BrowserNavigationViewController.swift @@ -85,6 +85,8 @@ open class BrowserNavigationViewController: EmbeddingViewController, Themeable, sideBarSeperatorView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(sideBarSeperatorView) + navigationBarTopConstraint = navigationBarTopConstraint(for: navigationBarHidden) + NSLayoutConstraint.activate([ contentContainerView.topAnchor.constraint(equalTo: view.topAnchor), contentContainerView.bottomAnchor.constraint(equalTo: view.bottomAnchor), @@ -101,7 +103,7 @@ open class BrowserNavigationViewController: EmbeddingViewController, Themeable, sideBarSeperatorView.leadingAnchor.constraint(equalTo: contentContainerView.leadingAnchor, constant: -1), sideBarSeperatorView.widthAnchor.constraint(equalToConstant: 1), - navigationView.topAnchor.constraint(equalTo: contentContainerView.safeAreaLayoutGuide.topAnchor), + navigationBarTopConstraint!, navigationView.leadingAnchor.constraint(equalTo: contentContainerView.safeAreaLayoutGuide.leadingAnchor), navigationView.trailingAnchor.constraint(equalTo: contentContainerView.safeAreaLayoutGuide.trailingAnchor) ]) @@ -124,6 +126,39 @@ open class BrowserNavigationViewController: EmbeddingViewController, Themeable, navigationController?.isNavigationBarHidden = true } + // MARK: - Navigation Bar + private var navigationBarTopConstraint: NSLayoutConstraint? + private func navigationBarTopConstraint(for hidden: Bool) -> NSLayoutConstraint { + if hidden { + return navigationView.bottomAnchor.constraint(equalTo: contentContainerView.topAnchor) + } + + return navigationView.topAnchor.constraint(equalTo: contentContainerView.safeAreaLayoutGuide.topAnchor) + } + + open var navigationBarHidden: Bool = false + open func setNavigationBarHidden(_ hidden: Bool, animated: Bool, completion: (() -> Void)? = nil) { + let updateLayout = { + self.navigationBarTopConstraint?.isActive = false + self.navigationBarTopConstraint = self.navigationBarTopConstraint(for: hidden) + self.navigationBarTopConstraint?.isActive = true + } + + OnMainThread(inline: true) { + if animated { + UIView.animate(withDuration: 0.3, animations: { + updateLayout() + self.view.layoutIfNeeded() + }, completion: { _ in + completion?() + }) + } else { + updateLayout() + completion?() + } + } + } + // MARK: - Push & Navigation open func push(viewController: UIViewController, completion: BrowserNavigationHistory.CompletionHandler? = nil) { push(item: BrowserNavigationItem(viewController: viewController), completion: completion) @@ -519,3 +554,19 @@ extension BrowserNavigationViewController: UINavigationBarDelegate { return .topAttached } } + +public extension UIViewController { + var browserNavigationViewController: BrowserNavigationViewController? { + var viewController: UIViewController? = self + + while viewController != nil { + if let browserNavigationViewController = viewController as? BrowserNavigationViewController { + return browserNavigationViewController + } + + viewController = viewController?.parent + } + + return nil + } +} diff --git a/ownCloudAppShared/User Interface/SegmentView/UIView+EmbedAndLayout.swift b/ownCloudAppShared/User Interface/SegmentView/UIView+EmbedAndLayout.swift index 36b64c171..4a74781d6 100644 --- a/ownCloudAppShared/User Interface/SegmentView/UIView+EmbedAndLayout.swift +++ b/ownCloudAppShared/User Interface/SegmentView/UIView+EmbedAndLayout.swift @@ -46,6 +46,10 @@ public extension UIView { return AnchorSet(leadingAnchor: safeAreaLayoutGuide.leadingAnchor, trailingAnchor: safeAreaLayoutGuide.trailingAnchor, topAnchor: safeAreaLayoutGuide.topAnchor, bottomAnchor: safeAreaLayoutGuide.bottomAnchor, centerXAnchor: safeAreaLayoutGuide.centerXAnchor, centerYAnchor: safeAreaLayoutGuide.centerYAnchor) } + var safeAreaWithKeyboardAnchorSet : AnchorSet { + return AnchorSet(leadingAnchor: safeAreaLayoutGuide.leadingAnchor, trailingAnchor: safeAreaLayoutGuide.trailingAnchor, topAnchor: safeAreaLayoutGuide.topAnchor, bottomAnchor: keyboardLayoutGuide.topAnchor, centerXAnchor: safeAreaLayoutGuide.centerXAnchor, centerYAnchor: safeAreaLayoutGuide.centerYAnchor) + } + @discardableResult func embedHorizontally(views: [UIView], insets: NSDirectionalEdgeInsets, enclosingAnchors: AnchorSet? = nil, limitHeight: Bool = false, spacingProvider: SpacingProvider? = nil, constraintsModifier: ConstraintsModifier? = nil) -> ConstraintSet { var viewIdx : Int = 0 var previousView: UIView?