From 67685374bb500fe82993feea354cfa47505c5af7 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Thu, 10 Oct 2019 09:22:34 +0200 Subject: [PATCH] [release/1.1.1] Release branch (#522) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * #516 truncate middle of file name instead of tail (#517) * new app version and build version * Added a fix for background media playback - Added audio session activation / deactivation code - Also fixed potential issue for background video playback (PiP) as suggested by Apple in https://developer.apple.com/documentation/avfoundation/media_assets_playback_and_editing/creating_a_basic_video_player_ios_and_tvos/playing_audio_from_a_video_asset_in_the_background * Fixed naming of uploaded edited photos * Fixed mission icons for ‘moreFolder’ location (#523) * [feature/darkmode] iOS 13 dark mode support (#489) * Naming improvements based on latest SDK: - uploads use new OCCoreOptionAutomaticConflictResolutionNameStyle option to automatically resolve naming conflicts during upload - Duplicate action uses the new OCCore name suggestion API to - determine naming conflicts and resolve them automatically - match the name style of the file to duplicate, f.ex. - duplicating "File copy.jpg" will create "File copy 2.jpg" - duplicating "File (1).jpg" will create "File (2).jpg" - duplicating "File Kopie 2.jpg" will create "File Kopie 3.jpg" - folder creation uses the new OCCore name suggestion API to - detect naming conflicts beforehand - pre-fill the new folder name name with an unused name directly * #393 added an activity indicator which will be shown, if offline copies will be deleted to give the user a UI feedback * - Change SDK branch to master * #76 user can import files using the iOS share sheet. all file types are accepted. * - using new SDK with fixes for requesting core - minor code fixes * - Log device, version and locale information at the beginning of every log file using new SDK protocol - Show SDK commit hash in Settings * fixed crash, changed bookmark name to shortname * changed UIAlertViewController for account selection to CardViewController style for testing * fixed icon badge creation for fastlane lane In-House Enterprise IPA generation * install librsvg for fastlane via sh * moved import file code to own class * added option to automatically resolve name conflicts, if file name already exists * Changed app version to 1.1.0 * enabled beta build and warning * Keep the gallery alive after doing some action over an item (#447) * Rename updates the currently visible file in the gallery * Keeping gallery alive fix - Don’t pop to the root view controller on destructive action by default - Only pop to the root if all items have been deleted - Select next item upon deletion of the current ones - Update UIPageViewController data source if number of items changes due e.g. to duplication or deletion * Small fixes * Fixed review findings * Media player implemented using AVKit (#429) * Media player implemented using AVKit * Added possibility to stream audio / video * Improved error handling * Fixed review finding * Added background audio, airplay and PiP mode * Propagating safe area to the main view of AVPlayerViewController * Another fix for safe area considering layout of AVPlayerViewController * Updated to latest develop version of SDK * Display error message in case file couldn’t be preview e.g. due to file corruption (#427) * Fixed UI test for creating folder, need to return a fixed name, because suggestion is not working in tests * added a description header and changed typo * - fixed showing the directory picker controller, after the card view was dismissed - use the current development SDK * Version Bump to 126 * - create local copy of import file, if needed - fixed create folder action - delete local copy, if needed - code review changes * - improved duplicate item deletion behaviour - add additional safeguard so duplicate files are only deleted if they were actually duplicated (previously non-duplicate files could be removed if duplication or folder creation failed) - remove temporary container folder, too - add 2 second delay before returning the core to give the core an opportunity to schedule the upload on a NSURLSession * Version Bump to 127 * changed back signing identity * Version Bump to 128 * added required CFBundleTypeName key * Version Bump to 129 * updated changelog * use formSheet presentation style for the iPad when showing the document picker * Fix for images not being displayed in the gallery * Version Bump to 130 * Version Bump to 131 * [fix/sharing-search] Fixed min length for searching sharing users (#455) * #454 used correct comparator for sharingSearchMinLength and set a new default value, if capability does not exists * added a constant for defaultSharingSearchMinLength value * - Update ios-sdk to address finding (1) in ios-app #446 * Preventing updating UI in DisplayViewController while item is being changed - Not calling present(item:) while meta data of OCItem is being updated - Elliminated an assumption in MediaDisplayViewController that render renderSpecificView() can be called only once. - Making sure that AVPlayer is not re-created upon changes in OCItem but that rather AVPlayerItem is replaced. * Fixed a small warning * Tried to improve gallery logic concerning items modification - Watch out if the modification does still includes the item with local ID of the currently visible item. - Take care of failed move but watching out for reappearing items * Added a setting allowing to decide user if media files shall be streamed * - Add debug output to Display*ViewController - Fix SwiftLint warnings * Fixed handling of deleted / moved item in the gallery * Another small fix for handling of failed item move * Version Bump to 132 * Added LSSupportsOpeningDocumentsInPlace key to Info.plist * Fixed Info.plist and added LSHandlerRank property * Fix for #455 issue: no search request triggered * show always the account selection sheet and added a note, that only one file can be imported at once * use securtiy scoped file operation for importing a file * Fix for the PR #447 (keep gallery alive) (#465) * Keeping track of individual OCItems in DisplayViewController instances But loading the list of items in the gallery only once and not reacting to any changes (moving, deleting) * Removed query stop call in DisplayHostViewController Since this query is not created there but just passed from the parent view. * Removing more button if the currently viewed file got moved or deleted * Fixed updating UI after renaming current item * Fixed a warning * Fixed issues in the gallery * - Minor fixes * [feature/bundle-import] Add support for certain bundle formats to the share sheet (#471) * - Add support for import of bundle-based document formats through transparent zipping - moved previous FileProvider functionality into ownCloudApp.framework - new method OCCore.importItemNamed() ensures correct handling of bundle-based document formats - fixes finding (4) in #445 - Changed LSSupportsOpeningDocumentsInPlace to NO address two issues: - the app is now listed as "Copy to ownCloud" (previously "Open in ownCloud", which was technically not correct) - previously, documents using "Open to ownCloud" would no longer open in f.ex. Pages, but in the ownCloud app when selected in the Files app * - Refactored ImportFilesController around using NSFileCoordinator, fixing finding (1) in #471 * changed path for moved Info.plist for target ownCloudAppFramework * Version Bump to 133 * [feature/sort direction] Allow sorting asc/desc for all sort methods (#474) * #470 added change sort direction asc/desc for all sort methods * - removed comments - changed translation keys * fixes sort comparator for sort method "shared" * use correct images for sort direction asc and desc * updated choosing sort method from a tableView/Popover instead of a UIAlertViewController * set maximum width for popover and set arrow direction * Added show sort direction in UISegmentControl * - make sure arrows of popovers appear in same color as the popover contents - fix issue where direction was toggled during setup of SortBar (setting of initial value from user defaults), so logging in, logging out and logging in would change the sort direction - reset sort direction to ascendant when switching sort methods - SortMethodTableViewController - remove extraneous instance lets - enforce row height used for table view height calculation - change table view height calculation to account for the last divider (which could look a bit off if present) * tap on segment control was fired twice, because there was a missing check, if selected segment changed * fixed changing width on selecting an other segment * fixed jumping width of sortSegmentControl items by setting the same width for all items by calculating the longest width before * [fix/index-bar] Make Index Bar available in general file list (#469) * #413 show index bar in general file list - Index bar is only visible, if sorting is "Sort by name" - Index bar is only visible, if more than one different letters are available - Index bar is only visible, if more items are available as visible * - adopt to new sort methods - reverse index list, if sort direction is descendant * Version Bump to 134 * Version Bump to 135 * Version Bump to 136 * Change behaviour of default expiration date (#476) * Enabling expiration date switch if default expiration date is configured Checking files_sharing.public.expire_date.expire_date.days server capability. If it is set then it is used as default date and the switch is enabled even if the expiration date is not enforced. * Fixed QA issue (1) * Fixed QA finding (2): limiting the date range if expiration date is enforced * Permission increasing UX in reshares (#467) * Not showing permissions which can’t be elevated Affecting items shared with the current users which are being reshared * Fixed an issue with wrong permissions displayed when creating public link - Checking if permissions can be elevated for the public link - Only viable options are displayed * Fixed a trailing whitespace warning * Fixed another compiler warning * Inhereting permissions from the share when creating public link * Fixed QA finding (4) * Fixed QA finding (1) * Fixed QA finding (2) * Version Bump to 137 * [feature/itempolicy] Item Policy / Available Offline support (#456) * Available Offline Support - new MakeAvailableOfflineAction and MakeUnavailableOfflineAction actions - new Available Offline Library section - easy access to all files available offline via Available Offline - new ItemPolicyTableViewController and ItemPolicyCell prepared for reuse for presenting other item policies - Design: added SVG/TVG/PNG icons and cloud symbols for make available offline / make unavailable offline ClientItemCell changes - new ClientItemResolvingCell can resolve item from LocalID or path, new superclass of ShareClientItemCell and ItemPolicyCell - string to use as content for title and detail are now provided via titleLabelString(for:) and detailLabelString(for:), updated via updateLabels(with:), making customization easier - support for new available offline badge ProgressSummarizer changes - changed ProgressSummary from a struct to a class, addressing issue #451 ("Waiting for server response..." message sometimes not disappearing) * - Adopt OCClaim to ensure files aren't deleted while they are being used in - DisplayViewController - DownloadItemsHUDViewController - FileListTableViewController - File Provider - Add new Storage Settings to allow control over how long unused local copies are kept around - DisplayViewController now uses OCClaims to ensure files aren't deleted while they are being viewed - ClientItemCell uses new SDK APIs to - show "available offline" badges for folders - to indicate if an item has been made available offline directly (solid icon) or indirectly (dimmed icon) - keep the cloud status icon updated following changes - Change "+" icon to "•••" icon in file list, rename plusButton to folderAction everywhere - Action.provideAlertAction() now applies padding to images if necessary so their text aligns at the same horizontal offset - Fix typo in BookmarkInfoViewController and change wording from "Delete Offline Copies" to "Delete Local Copies" for consistency - Update swiftlint.yml to silence ownCloudScreenshotsTests warnings * - Update ios-sdk * - Make changes requested in code review, adding missing localizable strings and removing extraneous code * - Update SDK * - Add downloadTriggerIdentifier and availableOfflinePolicyCoverage as properties that trigger the update of an item's visual representation (addressing finding 6 in #456) - Update make available offline action to use new convertExistingLocalDownloads option (addressing finding 2 in #456) - Update SDK (addressing finding 5 in #456) * Items in moved Available Offline folders should no longer be cleared locally and re-downloaded, following under the hood improvements in the ios-sdk * - Add initial support for cookies via SDK update * - Temporarily disable some UI tests broken due to lack of database running * - Fix issue (11) in #456 by updating the lastUsed date of already downloaded items when they are viewed * - Added slider type to StaticTableViewRow - changed storage settings to allow picking a timeframe via slider rather than a list of time frames * - Change text from "Remove" to "Make unavailable offline" in Quick Access > Available Offline list (addressing issue (9)) * - Update BookmarkInfoViewController to provide users with the choice to also remove local copies of items marked as Available Offline, issuing a warning and giving the users a way to cancel, addressing issue (12) in #456. * - Update ios-sdk - Take advantage of the new key-value store and OCCoreSkipAvailableOfflineKey to make sure available offline files are not immediately downloaded again after clearing the vault from local copies of available offline files. - Logging into an account removes any previously set OCCoreSkipAvailableOfflineKey from the key value store - Move Bookmark locking to an OCBookmarkManager category (next stop: use the new key-value store and bake support for locking bookmarks directly into the SDK) * - Add missing localization * Update SDK with latest fixes * - Fix SettingsTests bugs * - Apply additional fixes to UI tests and disable the remaining failing ones * - Wait for connection initialization to complete before requesting sharing info - Use latest SDK with cookie filtering * - Turn off Request/Response log tags only filter in project file * - Disable cookie support * - Address (13) via SDK update * link against newest development SDK * Fixes for UI testing: - use new SDK version that returns errors if the SQLite DB has not been opened - MockOCCore.state now returns .running to avoid queueing of PROPFINDs * - Fix unit tests * - UI testing: make MockOCCore.state no longer return .running due to side effects - fixed conflicting constraints in two places * - force-delete bookmarks in UI tests if timeout has passed, log error but don't assert in that case - handle case where a swipe on table cells in DeleteBookmarkTests triggers deletion outright * - Further hardening of SettingsTests against timing differences local vs. CI * - make SettingsTest.testCheckMoreItems() more robust * iOS 13 dark mode support: - detects dark / light mode changes and applies a different ThemeStyle as needed - added ThemeStyle.darkStyleIdentifier so ThemeStyles can indicate their dark counter part (or their lack thereof, in case of the dark theme) - add setting "Follow system light / dark appearance" to Settings > Theme * Version Bump to 138 * [feature/reorder buttons] Reorder Navigation Bar Actions (#478) * Available Offline Support - new MakeAvailableOfflineAction and MakeUnavailableOfflineAction actions - new Available Offline Library section - easy access to all files available offline via Available Offline - new ItemPolicyTableViewController and ItemPolicyCell prepared for reuse for presenting other item policies - Design: added SVG/TVG/PNG icons and cloud symbols for make available offline / make unavailable offline ClientItemCell changes - new ClientItemResolvingCell can resolve item from LocalID or path, new superclass of ShareClientItemCell and ItemPolicyCell - string to use as content for title and detail are now provided via titleLabelString(for:) and detailLabelString(for:), updated via updateLabels(with:), making customization easier - support for new available offline badge ProgressSummarizer changes - changed ProgressSummary from a struct to a class, addressing issue #451 ("Waiting for server response..." message sometimes not disappearing) * - Adopt OCClaim to ensure files aren't deleted while they are being used in - DisplayViewController - DownloadItemsHUDViewController - FileListTableViewController - File Provider - Add new Storage Settings to allow control over how long unused local copies are kept around - DisplayViewController now uses OCClaims to ensure files aren't deleted while they are being viewed - ClientItemCell uses new SDK APIs to - show "available offline" badges for folders - to indicate if an item has been made available offline directly (solid icon) or indirectly (dimmed icon) - keep the cloud status icon updated following changes - Change "+" icon to "•••" icon in file list, rename plusButton to folderAction everywhere - Action.provideAlertAction() now applies padding to images if necessary so their text aligns at the same horizontal offset - Fix typo in BookmarkInfoViewController and change wording from "Delete Offline Copies" to "Delete Local Copies" for consistency - Update swiftlint.yml to silence ownCloudScreenshotsTests warnings * - Update ios-sdk * - Make changes requested in code review, adding missing localizable strings and removing extraneous code * #477 reorganize navigation bar with create and more button and moved select button to sort menu * added safeAreaLayoutGuide.rightAnchor * - Update SDK * changed "Select" image * - Add downloadTriggerIdentifier and availableOfflinePolicyCoverage as properties that trigger the update of an item's visual representation (addressing finding 6 in #456) - Update make available offline action to use new convertExistingLocalDownloads option (addressing finding 2 in #456) - Update SDK (addressing finding 5 in #456) * Items in moved Available Offline folders should no longer be cleared locally and re-downloaded, following under the hood improvements in the ios-sdk * - Temporarily disable some UI tests broken due to lack of database running * - Fix issue (11) in #456 by updating the lastUsed date of already downloaded items when they are viewed * - Added slider type to StaticTableViewRow - changed storage settings to allow picking a timeframe via slider rather than a list of time frames * - Change text from "Remove" to "Make unavailable offline" in Quick Access > Available Offline list (addressing issue (9)) * - Update BookmarkInfoViewController to provide users with the choice to also remove local copies of items marked as Available Offline, issuing a warning and giving the users a way to cancel, addressing issue (12) in #456. * addresses: - missing multiselection UI in toolbar - missing select all / deselect all button in navigation bar * set if selectButton should be shown * disable temporary some tests due to bitrise failures * Version Bump to 139 * Feature/task scheduling (#484) * #386 Added relevant classes * First draft of the scheduled task manager * Just changed some comments * Implemented background fetch * Implemented background update fetch * Clean up of the code * Fixed merge problems from previous commit And eliminated some warnings * Small changes in background fetch operation * Some refactoring done * Added some user defaults for media upload * Available Offline Support - new MakeAvailableOfflineAction and MakeUnavailableOfflineAction actions - new Available Offline Library section - easy access to all files available offline via Available Offline - new ItemPolicyTableViewController and ItemPolicyCell prepared for reuse for presenting other item policies - Design: added SVG/TVG/PNG icons and cloud symbols for make available offline / make unavailable offline ClientItemCell changes - new ClientItemResolvingCell can resolve item from LocalID or path, new superclass of ShareClientItemCell and ItemPolicyCell - string to use as content for title and detail are now provided via titleLabelString(for:) and detailLabelString(for:), updated via updateLabels(with:), making customization easier - support for new available offline badge ProgressSummarizer changes - changed ProgressSummary from a struct to a class, addressing issue #451 ("Waiting for server response..." message sometimes not disappearing) * UI for bookmark / upload path selection * Added dummy implementation of media upload task * - Adopt OCClaim to ensure files aren't deleted while they are being used in - DisplayViewController - DownloadItemsHUDViewController - FileListTableViewController - File Provider - Add new Storage Settings to allow control over how long unused local copies are kept around - DisplayViewController now uses OCClaims to ensure files aren't deleted while they are being viewed - ClientItemCell uses new SDK APIs to - show "available offline" badges for folders - to indicate if an item has been made available offline directly (solid icon) or indirectly (dimmed icon) - keep the cloud status icon updated following changes - Change "+" icon to "•••" icon in file list, rename plusButton to folderAction everywhere - Action.provideAlertAction() now applies padding to images if necessary so their text aligns at the same horizontal offset - Fix typo in BookmarkInfoViewController and change wording from "Delete Offline Copies" to "Delete Local Copies" for consistency - Update swiftlint.yml to silence ownCloudScreenshotsTests warnings * - Update ios-sdk * - Make changes requested in code review, adding missing localizable strings and removing extraneous code * - Update SDK * Refactored current implementation of media upload * Added method to fetch photos from photo library * - Add downloadTriggerIdentifier and availableOfflinePolicyCoverage as properties that trigger the update of an item's visual representation (addressing finding 6 in #456) - Update make available offline action to use new convertExistingLocalDownloads option (addressing finding 2 in #456) - Update SDK (addressing finding 5 in #456) * Items in moved Available Offline folders should no longer be cleared locally and re-downloaded, following under the hood improvements in the ios-sdk * - Add initial support for cookies via SDK update * First draft of instant photo upload implementation * Calling task completion handler after upload was finished * - Temporarily disable some UI tests broken due to lack of database running * - Fix issue (11) in #456 by updating the lastUsed date of already downloaded items when they are viewed * - Added slider type to StaticTableViewRow - changed storage settings to allow picking a timeframe via slider rather than a list of time frames * - Change text from "Remove" to "Make unavailable offline" in Quick Access > Available Offline list (addressing issue (9)) * - Update BookmarkInfoViewController to provide users with the choice to also remove local copies of items marked as Available Offline, issuing a warning and giving the users a way to cancel, addressing issue (12) in #456. * Fixed QA finding (1) * Using PHAsset.creationDate to check which media files shall be instantly uploaded * Fixed naming of class / group * Moved Tasks folder * Fixed some review findings * - Fix issue where a busy bookmark alert is followed by duplicating the bookmark * Fixed some review findings * Improved background update fetching * Fixed some issues in media upload * Fixed a type in settings key * Improvements in the instant media upload task * Added missing returnCore in the selection of the instant upload path * Fixed QA finding (3) * Fixes for the correct core returning / requesting * Small cosmetic code changes * Fixed video and serialized photo / video uploads * Moved upload() method to UploadBaseAction class * Some refactoring in InstantMediaUploadTaskExtension * Improvements for bookmark / path selection for instant upload * Remove instant upload configuration if bookmark is not available * minor refactoring * Added possibility to cancel account selection * Small fix: reset the path if new account is selected * Using item tracking instead of OCQuery - Disabling instant photo upload if target directory is removed - Item tracking is potentially faster since it can use cached item information and is not always issuing PROPFIND * Made some ivars private * Detect in settings that instant upload path is gone In this case instant upload is disabled * - Update ios-sdk - Take advantage of the new key-value store and OCCoreSkipAvailableOfflineKey to make sure available offline files are not immediately downloaded again after clearing the vault from local copies of available offline files. - Logging into an account removes any previously set OCCoreSkipAvailableOfflineKey from the key value store - Move Bookmark locking to an OCBookmarkManager category (next stop: use the new key-value store and bake support for locking bookmarks directly into the SDK) * - Add missing localization * Update SDK with latest fixes * - Fix SettingsTests bugs * - Apply additional fixes to UI tests and disable the remaining failing ones * - Wait for connection initialization to complete before requesting sharing info - Use latest SDK with cookie filtering * - Turn off Request/Response log tags only filter in project file * - Disable cookie support * - Address (13) via SDK update * link against newest development SDK * Showing warning that instant upload was disabled * Fixes for UI testing: - use new SDK version that returns errors if the SQLite DB has not been opened - MockOCCore.state now returns .running to avoid queueing of PROPFINDs * - Fix unit tests * Added alert shown when in settings when instant upload is disabled * Added fetchUpdates() within which target folder tracking is started * - UI testing: make MockOCCore.state no longer return .running due to side effects - fixed conflicting constraints in two places * - force-delete bookmarks in UI tests if timeout has passed, log error but don't assert in that case - handle case where a swipe on table cells in DeleteBookmarkTests triggers deletion outright * - Further hardening of SettingsTests against timing differences local vs. CI * - make SettingsTest.testCheckMoreItems() more robust * Update to the latest changes in the SDK Those changes reduce memory consumption used for hash calculation * Change required to accomodate latest SDK changes * Less parallel photo uploads * - update SDK to address stuck sync action issues - no longer auto-open the last selected bookmark when launched by iOS in the background. Instead, the last selected bookmark is now auto-openend only when the app comes to the foreground. - update FileProviderInterfaceManager to better support the case where the app ships without File Provider. * Fixed compilation warning * - Add ATS exemption for File Provider * - Update SDK * - Update SDK + add colored debugging option to scheme * Addressed code review comments * Removed unneeded weak self * Removed another unwanted weak self * - Fix OCEvent-dropping issue via SDK update with fix - Make UploadMediaAction use OCBackgroundTask to protect while exporting * - Added missing OCBackgroundTask.start call in UploadMediaAction - Update SDK * Fixed missing copyright information and some warnings * Version Bump to 140 * Media upload pending flag added - Displaying a warning alert on the next launch, in case media upload couldn’t be completed due e.g. app being terminated in a backround or even due to an app crash. - Fixed an issue with DispatchGroup.notify() called prematurely in MediaUploadQueue.uploadAssets() * - Clarify comment * - sortbar was sometimes hidden - fixing layout issue * fixed location identifier for actions * Added more logs in PHAsset+Upload extension * - extend ThemeCollection and Theme support throughout the app, fixing the remaining issues with iOS 13 light/dark mode - ownCloudApp: add annotated properties using ObjC associated values * - Fix infinite loop / crash on trait changes - Improve status bar style changes on dark / light mode changes * - Make Issues view controller themeable / add dark/light mode support * Version Bump to 141 * Version Bump to 142 * Version Bump to 143 * - new ThemedAlertController automatically adapts to the current style, can be used as drop-in replacement for UIAlertController (which it subclasses) - make MoreViewHeader fully Themeable (fixing issues when switching between dark/light mode when visible), including TVG icons - remove hard-coded background color for header in SharingTableViewController - make NamingViewController use a blur effect and keyboard that match the current dark/light mode - make ClientItemCell fully Themeable, including TVG icons - add option to StaticTableViewRow to allow picking different icon tint colors that adapt with dark/light mode changes - make use of that option in LibraryTableViewController - fix magnification glass color in GroupSharingTableViewController and make it fully Themeable - fix SortBar popover content inset issue (iOS 13 only) - fix UISearchBar theme application / magnification glass color in NSObject+ThemeApplication - add support for UIVisualEffectView to NSObject+ThemeApplication - ThemeCollection improvements and fixes - add new properties keyboardAppearance and backgroundBlurEffectStyle - provide tableRowHighlightColors.backgroundColor to fix issue when overriding system-wide dark mode - adopt Themeable for CardPresentationController, fixing wrong background color when dragging up a card that was already open before a theme change * - Address review issues * solved merge conflict error * fixed colors in UISegmentedControl for iOS 13 * set tintColor to UIRefreshControl * Only show theme selection, if follow system appearance is not enabled * fixed iOS 13 content inset issue for popover * set the tint color in ThemedAlertController * set the UITableViewCell interface style for a better, multi-selection circle and disclosure indicator color * UIActivityIndicator is no longer working proper in iOS 13. Removed this UI element from UISearchBar and showing a message view instead * fixed default value of system UI appearance * - Fix finding (7) in #502 * - Fix finding (3) in #502 by translating OCErrorDomain errors to NSCocoaErrorDomain before return (on iOS 13 only) * Show available Themes for current system appearance mode, if enabled * code refactoring * solved some UI updating animations * changed theme selection again and show system appearance in theme selection list * - set the tint color for cells so that checkmark will match the same tint color - changed icon for available offline action * System was not shown as selected value * - Update SDK to new version to include OIDC improvements and OCCore fixes * disable beta build and beta warning * added changelog for version 1.1.1 * - added activity indicator instead of search message - fixed searchbar text color --- CHANGELOG.md | 10 ++ ios-sdk | 2 +- .../FileProviderExtension.m | 28 ++--- .../FileProviderExtensionThumbnailRequest.m | 2 +- ownCloud File Provider/Info.plist | 2 +- .../NSError+MessageResolution.h | 2 +- .../NSError+MessageResolution.m | 58 +++++++++- .../OCItem+FileProviderItem.m | 2 +- ownCloud File ProviderUI/Info.plist | 2 +- ownCloud.xcodeproj/project.pbxproj | 52 ++++++--- .../xcshareddata/xcschemes/ownCloud.xcscheme | 70 ++++++------ ownCloud/AppDelegate.swift | 7 +- .../BookmarkInfoViewController.swift | 4 +- .../Bookmarks/BookmarkViewController.swift | 2 +- .../ConnectionIssueViewController.swift | 16 ++- .../Issues/IssuesViewController.swift | 27 +++-- .../Actions+Extensions/CopyAction.swift | 2 +- .../Actions+Extensions/DeleteAction.swift | 4 +- .../Actions+Extensions/DuplicateAction.swift | 2 +- .../MakeUnavailableOfflineAction.swift | 2 +- .../Actions+Extensions/MoveAction.swift | 2 +- .../Actions+Extensions/OpenInAction.swift | 2 +- .../Actions+Extensions/RenameAction.swift | 2 +- .../Actions+Extensions/UnshareAction.swift | 4 +- ownCloud/Client/Actions/MoreViewHeader.swift | 37 ++++++- .../Client/Actions/NamingViewController.swift | 21 ++-- ownCloud/Client/ClientItemCell.swift | 16 ++- .../Client/ClientQueryViewController.swift | 20 +++- .../Client/ClientRootViewController.swift | 8 +- .../Library/LibraryTableViewController.swift | 24 +++- .../GroupSharingEditTableViewController.swift | 6 +- .../GroupSharingTableViewController.swift | 36 ++++-- .../PendingSharesTableViewController.swift | 6 +- .../PublicLinkEditTableViewController.swift | 18 +-- .../PublicLinkTableViewController.swift | 4 +- .../Sharing/SharingTableViewController.swift | 4 +- ownCloud/Client/SortBar.swift | 19 +++- .../Image/ImageDisplayViewController.swift | 2 +- .../Media/MediaDisplayViewController.swift | 23 +++- .../Viewer/PDF/PDFViewerViewController.swift | 4 +- .../FileListTableViewController.swift | 2 + .../QueryFileListTableViewController.swift | 1 + .../PhotoKit Extensions/PHAsset+Upload.swift | 17 ++- ownCloud/Resources/Info.plist | 2 +- .../Resources/en.lproj/Localizable.strings | 2 + .../ServerListTableViewController.swift | 15 ++- .../Settings/LogFilesViewController.swift | 2 +- .../Settings/MediaUploadSettingsSection.swift | 6 +- ownCloud/Settings/MoreSettingsSection.swift | 2 +- .../Passcode/PasscodeViewController.swift | 2 + .../UserInterfaceSettingsSection.swift | 44 ++++---- .../InstantMediaUploadTaskExtension.swift | 2 +- .../Theming/NSObject+ThemeApplication.swift | 82 +++++++++++++- ownCloud/Theming/Theme.swift | 2 +- ownCloud/Theming/ThemeCollection.swift | 74 ++++++++++++- .../Theming/ThemeStyle+DefaultStyles.swift | 4 +- ownCloud/Theming/ThemeStyle+Extensions.swift | 104 ++++++++++++++++++ ownCloud/Theming/ThemeStyle.swift | 5 +- ownCloud/Theming/UI/ThemeWindow.swift | 31 ++++++ .../Theming/UI/ThemedAlertController.swift | 39 +++++++ ownCloud/Tools/URL+Extensions.swift | 4 +- ownCloud/Tools/VendorServices.swift | 2 +- .../CardPresentationController.swift | 23 +++- .../StaticTableViewController.swift | 3 +- ownCloud/UI Elements/StaticTableViewRow.swift | 14 ++- ...lertViewController+SystemPermissions.swift | 2 +- .../UISearchBar+Extension.swift | 60 ---------- ...Animation.swift => UIView+Extension.swift} | 18 ++- .../NSObject+AnnotatedProperties.h | 30 +++++ .../NSObject+AnnotatedProperties.m | 56 ++++++++++ ownCloudAppFramework/Resources/Info.plist | 2 +- ownCloudAppFramework/ownCloudApp.h | 1 + ownCloudScreenshotsTests/Info.plist | 2 +- ownCloudTests/Info.plist | 2 +- 74 files changed, 924 insertions(+), 287 deletions(-) create mode 100644 ownCloud/Theming/UI/ThemeWindow.swift create mode 100644 ownCloud/Theming/UI/ThemedAlertController.swift delete mode 100644 ownCloud/UIKit Extensions/UISearchBar+Extension.swift rename ownCloud/UIKit Extensions/{UIView+Animation.swift => UIView+Extension.swift} (73%) create mode 100644 ownCloudAppFramework/Foundation Extensions/NSObject+AnnotatedProperties.h create mode 100644 ownCloudAppFramework/Foundation Extensions/NSObject+AnnotatedProperties.m diff --git a/CHANGELOG.md b/CHANGELOG.md index 14ef2a389..4024469fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # ChangeLog +## Release version 1.1.1 (October 2019) + +- Dark mode support (PR #489) +- iOS 13 support (#502) +- Background media playback fixed (PR #522) +- Displaying long file name improved (#516) +- Fixed naming of uploaded edited photos (#520) +- Fixed crash in File Provider (#502) +- UI fixes (#511, #502) + ## Release version 1.1.0 (September 2019) - Available Offline Support (#134, #135) diff --git a/ios-sdk b/ios-sdk index b85482a0b..c7e45dbd5 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit b85482a0b3e876374e3beaf9241286867d6df5ab +Subproject commit c7e45dbd517be0f3191e5567a929dd0302d29e00 diff --git a/ownCloud File Provider/FileProviderExtension.m b/ownCloud File Provider/FileProviderExtension.m index c8bdda035..a5747cadf 100644 --- a/ownCloud File Provider/FileProviderExtension.m +++ b/ownCloud File Provider/FileProviderExtension.m @@ -139,7 +139,7 @@ - (NSFileProviderItem)itemForIdentifier:(NSFileProviderItemIdentifier)identifier if (outError != NULL) { - *outError = [returnError resolvedError]; + *outError = [returnError translatedError]; } return item; @@ -238,7 +238,7 @@ - (void)startProvidingItemAtURL:(NSURL *)provideAtURL completionHandler:(void (^ FPLogCmd(@"Completed with error=%@", error); - completionHandler([error resolvedError]); + completionHandler([error translatedError]); }]; return; @@ -387,7 +387,7 @@ - (void)createDirectoryWithName:(NSString *)directoryName inParentItemIdentifier { FPLogCmd(@"Completed with collission with existingItem=%@ (locally detected)", existingItem); // completionHandler(nil, [NSError fileProviderErrorForCollisionWithItem:existingItem]); // This is what we should do according to docs - completionHandler(nil, [OCError(OCErrorItemAlreadyExists) resolvedError]); // This is what we need to do to avoid users running into issues using the broken Files "Duplicate" action + completionHandler(nil, [OCError(OCErrorItemAlreadyExists) translatedError]); // This is what we need to do to avoid users running into issues using the broken Files "Duplicate" action return; } @@ -395,7 +395,7 @@ - (void)createDirectoryWithName:(NSString *)directoryName inParentItemIdentifier // OCCoreOptionPlaceholderCompletionHandler : [^(NSError * _Nullable error, OCItem * _Nullable item) { // FPLogCmd(@"Completed placeholder creation with item=%@, error=%@", item, error); // -// completionHandler(item, [error resolvedError]); +// completionHandler(item, [error translatedError]); // } copy] } resultHandler:^(NSError *error, OCCore *core, OCItem *item, id parameter) { if (error != nil) @@ -409,7 +409,7 @@ - (void)createDirectoryWithName:(NSString *)directoryName inParentItemIdentifier { FPLogCmd(@"Completed with collission with existingItem=%@ (server response)", existingItem); // completionHandler(nil, [NSError fileProviderErrorForCollisionWithItem:existingItem]); // This is what we should do according to docs - completionHandler(nil, [OCError(OCErrorItemAlreadyExists) resolvedError]); // This is what we need to do to avoid users running into issues using the broken Files "Duplicate" action + completionHandler(nil, [OCError(OCErrorItemAlreadyExists) translatedError]); // This is what we need to do to avoid users running into issues using the broken Files "Duplicate" action return; } } @@ -417,7 +417,7 @@ - (void)createDirectoryWithName:(NSString *)directoryName inParentItemIdentifier FPLogCmd(@"Completed with item=%@, error=%@", item, error); - completionHandler(item, [error resolvedError]); + completionHandler(item, [error translatedError]); }]; } else @@ -443,7 +443,7 @@ - (void)reparentItemWithIdentifier:(NSFileProviderItemIdentifier)itemIdentifier [self.core moveItem:item to:parentItem withName:((newName != nil) ? newName : item.name) options:nil resultHandler:^(NSError *error, OCCore *core, OCItem *item, id parameter) { FPLogCmd(@"Completed with item=%@, error=%@", item, error); - completionHandler(item, [error resolvedError]); + completionHandler(item, [error translatedError]); }]; } else @@ -460,7 +460,7 @@ - (void)reparentItemWithIdentifier:(NSFileProviderItemIdentifier)itemIdentifier } FPLogCmd(@"Completed with item=%@ or parentItem=%@ not found, error=%@", item, parentItem, error); - completionHandler(nil, error); + completionHandler(nil, [error translatedError]); } } @@ -477,7 +477,7 @@ - (void)renameItemWithIdentifier:(NSFileProviderItemIdentifier)itemIdentifier to [self.core renameItem:item to:itemName options:nil resultHandler:^(NSError *error, OCCore *core, OCItem *item, id parameter) { FPLogCmd(@"Completed with item=%@, error=%@", item, error); - completionHandler(item, [error resolvedError]); + completionHandler(item, [error translatedError]); }]; } else @@ -500,7 +500,7 @@ - (void)deleteItemWithIdentifier:(NSFileProviderItemIdentifier)itemIdentifier co [self.core deleteItem:item requireMatch:YES resultHandler:^(NSError *error, OCCore *core, OCItem *item, id parameter) { FPLogCmd(@"Completed with error=%@", error); - completionHandler([error resolvedError]); + completionHandler([error translatedError]); }]; } else @@ -556,7 +556,7 @@ - (void)importDocumentAtURL:(NSURL *)fileURL toParentItemIdentifier:(NSFileProvi OCCoreOptionImportByCopying : @(importByCopying) } placeholderCompletionHandler:^(NSError *error, OCItem *item) { FPLogCmd(@"Completed with placeholderItem=%@, error=%@", item, error); - completionHandler(item, [error resolvedError]); + completionHandler(item, [error translatedError]); } resultHandler:^(NSError *error, OCCore *core, OCItem *item, id parameter) { if ([error.domain isEqual:OCHTTPStatusErrorDomain] && (error.code == OCHTTPStatusCodePRECONDITION_FAILED)) { @@ -606,7 +606,7 @@ - (void)setFavoriteRank:(NSNumber *)favoriteRank forItemIdentifier:(NSFileProvid [self.core updateItem:item properties:@[ OCItemPropertyNameLocalAttributes ] options:nil resultHandler:^(NSError *error, OCCore *core, OCItem *item, id parameter) { FPLogCmd(@"Completed with item=%@, error=%@", item, error); - completionHandler(item, [error resolvedError]); + completionHandler(item, [error translatedError]); }]; } else @@ -641,7 +641,7 @@ - (void)setTagData:(NSData *)tagData forItemIdentifier:(NSFileProviderItemIdenti [self.core updateItem:item properties:@[ OCItemPropertyNameLocalAttributes ] options:nil resultHandler:^(NSError *error, OCCore *core, OCItem *item, id parameter) { FPLogCmd(@"Completed with item=%@, error=%@", item, error); - completionHandler(item, [error resolvedError]); + completionHandler(item, [error translatedError]); }]; } else @@ -675,7 +675,7 @@ - (void)trashItemWithIdentifier:(NSFileProviderItemIdentifier)itemIdentifier com [self.core deleteItem:item requireMatch:YES resultHandler:^(NSError *error, OCCore *core, OCItem *item, id parameter) { FPLogCmd(@"Completed with error=%@", error); - completionHandler(nil, [error resolvedError]); + completionHandler(nil, [error translatedError]); }]; } else diff --git a/ownCloud File Provider/FileProviderExtensionThumbnailRequest.m b/ownCloud File Provider/FileProviderExtensionThumbnailRequest.m index b8f5fc353..edda46827 100644 --- a/ownCloud File Provider/FileProviderExtensionThumbnailRequest.m +++ b/ownCloud File Provider/FileProviderExtensionThumbnailRequest.m @@ -75,7 +75,7 @@ - (void)requestNextThumbnail if (!isOngoing) { dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ - self.perThumbnailCompletionHandler(itemIdentifier, thumbnail.data, (thumbnail==nil) ? nil : [error resolvedError]); + self.perThumbnailCompletionHandler(itemIdentifier, thumbnail.data, (thumbnail==nil) ? nil : [error translatedError]); OCLogDebug(@"Replied %ld: %@ -> thumbnailData=%d, error=%@", self.cursorPosition-1, itemFromDatabase.name, (thumbnail.data != nil), error); diff --git a/ownCloud File Provider/Info.plist b/ownCloud File Provider/Info.plist index 454311fae..a173bdd4d 100644 --- a/ownCloud File Provider/Info.plist +++ b/ownCloud File Provider/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString $(APP_SHORT_VERSION) CFBundleVersion - 142 + 143 NSExtension NSExtensionFileProviderDocumentGroup diff --git a/ownCloud File Provider/NSError+MessageResolution.h b/ownCloud File Provider/NSError+MessageResolution.h index 2906722c4..a1974c956 100644 --- a/ownCloud File Provider/NSError+MessageResolution.h +++ b/ownCloud File Provider/NSError+MessageResolution.h @@ -22,7 +22,7 @@ NS_ASSUME_NONNULL_BEGIN @interface NSError (MessageResolution) -- (NSError *)resolvedError; +- (NSError *)translatedError; @end diff --git a/ownCloud File Provider/NSError+MessageResolution.m b/ownCloud File Provider/NSError+MessageResolution.m index c8fb27d1a..be19ef331 100644 --- a/ownCloud File Provider/NSError+MessageResolution.m +++ b/ownCloud File Provider/NSError+MessageResolution.m @@ -21,10 +21,13 @@ @implementation NSError (MessageResolution) -- (NSError *)resolvedError +- (NSError *)resolvedErrorWithTranslation:(BOOL)withTranslation { if ([self.domain isEqual:OCErrorDomain]) { + NSErrorDomain errorDomain = self.domain; + NSInteger errorCode = self.code; + NSString *localizedDescription = self.localizedDescription; NSString *localizedFailureReason = self.localizedFailureReason; @@ -40,10 +43,61 @@ - (NSError *)resolvedError resolvedDict[NSLocalizedFailureReasonErrorKey] = localizedFailureReason; } - return ([NSError errorWithDomain:self.domain code:self.code userInfo:resolvedDict]); + if (withTranslation) + { + errorDomain = NSCocoaErrorDomain; + + resolvedDict[NSUnderlyingErrorKey] = self; + + switch ((OCError)self.code) + { + case OCErrorItemAlreadyExists: + errorCode = NSFileWriteFileExistsError; + break; + + case OCErrorItemNotFound: + case OCErrorItemDestinationNotFound: + case OCErrorFileNotFound: + errorCode = NSFileNoSuchFileError; + break; + + case OCErrorFeatureNotImplemented: + case OCErrorItemOperationForbidden: + errorCode = NSFeatureUnsupportedError; + break; + + case OCErrorItemInsufficientPermissions: + errorCode = NSFileWriteNoPermissionError; + break; + + case OCErrorCancelled: + errorCode = NSUserCancelledError; + break; + + case OCErrorInsufficientStorage: + errorCode = NSFileWriteOutOfSpaceError; + break; + + default: + errorCode = NSFileReadUnknownError; + break; + } + } + + return ([NSError errorWithDomain:errorDomain code:errorCode userInfo:resolvedDict]); } return (self); } +- (NSError *)translatedError +{ + if (@available(iOS 13, *)) + { + return ([self resolvedErrorWithTranslation:YES]); + } + + return ([self resolvedErrorWithTranslation:NO]); +} + @end diff --git a/ownCloud File Provider/OCItem+FileProviderItem.m b/ownCloud File Provider/OCItem+FileProviderItem.m index 388722b82..81396450f 100644 --- a/ownCloud File Provider/OCItem+FileProviderItem.m +++ b/ownCloud File Provider/OCItem+FileProviderItem.m @@ -237,7 +237,7 @@ - (void)setUploadingError:(NSError *)uploadingError sOCItemUploadingErrors = [NSMutableDictionary new]; } - sOCItemUploadingErrors[self.localID] = [uploadingError resolvedError]; + sOCItemUploadingErrors[self.localID] = [uploadingError translatedError]; } } } diff --git a/ownCloud File ProviderUI/Info.plist b/ownCloud File ProviderUI/Info.plist index 0341a14ad..f5bf5b0ae 100644 --- a/ownCloud File ProviderUI/Info.plist +++ b/ownCloud File ProviderUI/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString $(APP_SHORT_VERSION) CFBundleVersion - 142 + 143 NSExtension NSExtensionFileProviderActions diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 8eb28091d..412f89fff 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -37,7 +37,6 @@ 39104E10223991C8002FC02F /* UIButton+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39104E0A223991C8002FC02F /* UIButton+Extension.swift */; }; 3913213822946E5E00EF88F4 /* FileListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3913213722946E5E00EF88F4 /* FileListTableViewController.swift */; }; 3913214D22956D5700EF88F4 /* LibraryTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3913214A22956D5700EF88F4 /* LibraryTableViewController.swift */; }; - 392557FE2278703300E83F60 /* UISearchBar+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 392557FD2278703300E83F60 /* UISearchBar+Extension.swift */; }; 394804DA225CBDBA00AA8183 /* BreadCrumbTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 394804D9225CBDBA00AA8183 /* BreadCrumbTableViewController.swift */; }; 39607CBC2225D480007B386D /* UITableViewController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39607CBB2225D480007B386D /* UITableViewController+Extension.swift */; }; 396BE4C32288A84C00B254A9 /* PendingSharesTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396BE4C22288A84C00B254A9 /* PendingSharesTableViewController.swift */; }; @@ -148,7 +147,7 @@ 6EE97DCA2204703C0062CCBC /* BackgroundImage.png in Resources */ = {isa = PBXBuildFile; fileRef = 6EE97DC42204703C0062CCBC /* BackgroundImage.png */; }; 75AC0B4AD332C8CC785FE349 /* Pods_ownCloudTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A56EA84D8AD331FFA604138B /* Pods_ownCloudTests.framework */; }; A45A8D98137C902524B84E6D /* EarlGrey.framework in EarlGrey Copy Files */ = {isa = PBXBuildFile; fileRef = D0D9C062DD1E85A838608B0F /* EarlGrey.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - DC018F8320A0F56300135198 /* UIView+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC018F8220A0F56300135198 /* UIView+Animation.swift */; }; + DC018F8320A0F56300135198 /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC018F8220A0F56300135198 /* UIView+Extension.swift */; }; DC018F8C20A1060A00135198 /* ProgressHUDViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC018F8B20A1060A00135198 /* ProgressHUDViewController.swift */; }; DC0196AB20F7690C00C41B78 /* OCBookmark+FileProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DC27A1A420CBEF85008ACB6C /* OCBookmark+FileProvider.m */; }; DC01CDCC212EDDF600FC8E38 /* TextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC01CDCB212EDDF600FC8E38 /* TextViewController.swift */; }; @@ -164,6 +163,7 @@ 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 */; }; + DC243BFF2317B446004FBB5C /* ThemeWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC243BF92317B446004FBB5C /* ThemeWindow.swift */; }; DC248C67213E7DB00067FE94 /* NSLayoutConstraint+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC248C66213E7DB00067FE94 /* NSLayoutConstraint+Extension.swift */; }; DC2565EE225F5A1900828AA5 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC2565E8225F5A1900828AA5 /* UserNotifications.framework */; }; DC27A18E20CA9F66008ACB6C /* OCItem+FileProviderItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DC27A18D20CA9F66008ACB6C /* OCItem+FileProviderItem.m */; }; @@ -240,6 +240,7 @@ DC9BFBB920A1AF2C007064B5 /* icon-locked.tvg in Resources */ = {isa = PBXBuildFile; fileRef = DC9BFBB820A1AF2B007064B5 /* icon-locked.tvg */; }; DC9BFBBB20A1B3CA007064B5 /* icon-password-manager.tvg in Resources */ = {isa = PBXBuildFile; fileRef = DC9BFBBA20A1B3CA007064B5 /* icon-password-manager.tvg */; }; DC9BFBBD20A1C37B007064B5 /* PasswordManagerAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC9BFBBC20A1C37B007064B5 /* PasswordManagerAccess.swift */; }; + DCAB9CC923417243009091B6 /* ThemedAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAB9CC823417243009091B6 /* ThemedAlertController.swift */; }; DCAEB05F21F9FB370067E147 /* OCBookmarkManager+Tools.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAEB05E21F9FB370067E147 /* OCBookmarkManager+Tools.swift */; }; DCAEB06121F9FC510067E147 /* EarlGrey+Tools.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAEB06021F9FC510067E147 /* EarlGrey+Tools.swift */; }; DCB44D7D2186F0F600DAA4CC /* ThemeStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCB44D7C2186F0F600DAA4CC /* ThemeStyle.swift */; }; @@ -256,6 +257,8 @@ DCC0857F2293F48D008CC05C /* DisplaySettings.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC0857D2293F2D7008CC05C /* DisplaySettings.m */; }; DCC085802293F490008CC05C /* DisplaySettings.h in Headers */ = {isa = PBXBuildFile; fileRef = DCC0857C2293F2D7008CC05C /* DisplaySettings.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCC085812293F66D008CC05C /* ownCloudApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCC0855C2293F1FD008CC05C /* ownCloudApp.framework */; }; + 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 */; }; DCC6565B20C9B7E400110A97 /* DocumentActionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC6565A20C9B7E400110A97 /* DocumentActionViewController.m */; }; @@ -613,7 +616,6 @@ 39104E0A223991C8002FC02F /* UIButton+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIButton+Extension.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 = ""; }; - 392557FD2278703300E83F60 /* UISearchBar+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISearchBar+Extension.swift"; sourceTree = ""; }; 394804D9225CBDBA00AA8183 /* BreadCrumbTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadCrumbTableViewController.swift; sourceTree = ""; }; 39607CBB2225D480007B386D /* UITableViewController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableViewController+Extension.swift"; sourceTree = ""; }; 396BE4C22288A84C00B254A9 /* PendingSharesTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingSharesTableViewController.swift; sourceTree = ""; }; @@ -750,7 +752,7 @@ D0D9C062DD1E85A838608B0F /* EarlGrey.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = EarlGrey.framework; path = Pods/EarlGrey/EarlGrey/EarlGrey.framework; sourceTree = SOURCE_ROOT; }; D6033BC23D1129172E6D6383 /* Pods-ownCloudTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ownCloudTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ownCloudTests/Pods-ownCloudTests.release.xcconfig"; sourceTree = ""; }; D7F3B3E74D4B04F9CAF95C09 /* EarlGrey.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = EarlGrey.swift; sourceTree = ""; }; - DC018F8220A0F56300135198 /* UIView+Animation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Animation.swift"; sourceTree = ""; }; + DC018F8220A0F56300135198 /* UIView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extension.swift"; sourceTree = ""; }; DC018F8B20A1060A00135198 /* ProgressHUDViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressHUDViewController.swift; sourceTree = ""; }; DC01CDCB212EDDF600FC8E38 /* TextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewController.swift; sourceTree = ""; }; DC0B379320514E4700189B9A /* ServerListBookmarkCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListBookmarkCell.swift; sourceTree = ""; }; @@ -762,6 +764,7 @@ DC1B2705209CF0D3004715E1 /* CertificateViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CertificateViewController.swift; sourceTree = ""; }; DC1B2706209CF0D3004715E1 /* ConnectionIssueViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionIssueViewController.swift; sourceTree = ""; }; DC1B270B209CF34B004715E1 /* BookmarkViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkViewController.swift; sourceTree = ""; }; + DC243BF92317B446004FBB5C /* ThemeWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeWindow.swift; sourceTree = ""; }; DC248C66213E7DB00067FE94 /* NSLayoutConstraint+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSLayoutConstraint+Extension.swift"; sourceTree = ""; }; DC2565E8225F5A1900828AA5 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; DC27A18C20CA9F66008ACB6C /* OCItem+FileProviderItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCItem+FileProviderItem.h"; sourceTree = ""; }; @@ -839,6 +842,7 @@ 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; }; DC9BFBBC20A1C37B007064B5 /* PasswordManagerAccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordManagerAccess.swift; sourceTree = ""; }; + DCAB9CC823417243009091B6 /* ThemedAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemedAlertController.swift; sourceTree = ""; }; DCAEB05E21F9FB370067E147 /* OCBookmarkManager+Tools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCBookmarkManager+Tools.swift"; sourceTree = ""; }; DCAEB06021F9FC510067E147 /* EarlGrey+Tools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EarlGrey+Tools.swift"; sourceTree = ""; }; DCB44D7C2186F0F600DAA4CC /* ThemeStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeStyle.swift; sourceTree = ""; }; @@ -854,6 +858,8 @@ DCC0856D2293F1FD008CC05C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DCC0857C2293F2D7008CC05C /* DisplaySettings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DisplaySettings.h; sourceTree = ""; }; DCC0857D2293F2D7008CC05C /* DisplaySettings.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DisplaySettings.m; sourceTree = ""; }; + DCC5E444232654DE002E5B84 /* NSObject+AnnotatedProperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSObject+AnnotatedProperties.h"; sourceTree = ""; }; + DCC5E445232654DE002E5B84 /* NSObject+AnnotatedProperties.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSObject+AnnotatedProperties.m"; sourceTree = ""; }; DCC6564620C9B7E300110A97 /* ownCloud File Provider.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "ownCloud File Provider.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; DCC6564820C9B7E400110A97 /* FileProviderExtension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileProviderExtension.h; sourceTree = ""; }; DCC6564920C9B7E400110A97 /* FileProviderExtension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FileProviderExtension.m; sourceTree = ""; }; @@ -1127,7 +1133,7 @@ 239F1318205A693A0029F186 /* UIColor+Extension.swift */, 2347446920761BB700859C93 /* String+Extension.swift */, DCE974BB207EACA60069FC2B /* UIImage+Extension.swift */, - DC018F8220A0F56300135198 /* UIView+Animation.swift */, + DC018F8220A0F56300135198 /* UIView+Extension.swift */, 6E83C78320A33C180066EC23 /* LAContext+Extension.swift */, DC434D1220D7A8F100740056 /* UIAlertController+OCIssue.swift */, 4CB8ADDF22DF5EC500F1FEBC /* UIAlertViewController+SystemPermissions.swift */, @@ -1137,7 +1143,6 @@ 4CF8CAB021F9B70500B8CA67 /* UIBarButtonItem+Extension.swift */, 39607CBB2225D480007B386D /* UITableViewController+Extension.swift */, 3998F5D2224102FE00B66713 /* UITableView+Extension.swift */, - 392557FD2278703300E83F60 /* UISearchBar+Extension.swift */, 39CC8AE5228C12100020253B /* Array+Extension.swift */, ); path = "UIKit Extensions"; @@ -1524,6 +1529,8 @@ DC0B37962051681600189B9A /* ThemeButton.swift */, DC680579212EAB5E006C3B1F /* ThemeCertificateViewController.swift */, 39E2FDFF21FF814A00F0117F /* ThemeRoundedButton.swift */, + DC243BF92317B446004FBB5C /* ThemeWindow.swift */, + DCAB9CC823417243009091B6 /* ThemedAlertController.swift */, ); path = UI; sourceTree = ""; @@ -1644,6 +1651,7 @@ children = ( DCC0855E2293F1FD008CC05C /* ownCloudApp.h */, DC774E5A22F44E2A000B11A1 /* Display Settings */, + DCC5E443232654C1002E5B84 /* Foundation Extensions */, DC774E5422F44DF6000B11A1 /* SDK Extensions */, DC774E5B22F44E4A000B11A1 /* ZIP Archive */, DC774E6522F44EA7000B11A1 /* Resources */, @@ -1660,6 +1668,15 @@ path = ownCloudAppFrameworkTests; sourceTree = ""; }; + DCC5E443232654C1002E5B84 /* Foundation Extensions */ = { + isa = PBXGroup; + children = ( + DCC5E445232654DE002E5B84 /* NSObject+AnnotatedProperties.m */, + DCC5E444232654DE002E5B84 /* NSObject+AnnotatedProperties.h */, + ); + path = "Foundation Extensions"; + sourceTree = ""; + }; DCC6564720C9B7E400110A97 /* ownCloud File Provider */ = { isa = PBXGroup; children = ( @@ -1844,6 +1861,7 @@ DCC085802293F490008CC05C /* DisplaySettings.h in Headers */, DC774E6322F44E6D000B11A1 /* OCCore+BundleImport.h in Headers */, DCC0856E2293F1FD008CC05C /* ownCloudApp.h in Headers */, + DCC5E4472326564F002E5B84 /* NSObject+AnnotatedProperties.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2492,7 +2510,7 @@ 4C464BF62187AF1500D30602 /* PDFTocItem.swift in Sources */, 6E3A103E219D5BBA00F90C96 /* RenameAction.swift in Sources */, 39A513AC22674E56002CF1AA /* OCCore+Extension.swift in Sources */, - DC018F8320A0F56300135198 /* UIView+Animation.swift in Sources */, + DC018F8320A0F56300135198 /* UIView+Extension.swift in Sources */, 4CF8CAB121F9B70600B8CA67 /* UIBarButtonItem+Extension.swift in Sources */, 4CC4A21222FA20AD00AE7E2C /* URL+Extensions.swift in Sources */, DC42244A207CAFAA0006A2A6 /* Theme.swift in Sources */, @@ -2517,6 +2535,7 @@ 390B51E02292DBB100935E24 /* SharingTableViewController.swift in Sources */, 4C1561E8222321E0009C4EF3 /* PhotoSelectionViewController.swift in Sources */, 3998F5D3224102FE00B66713 /* UITableView+Extension.swift in Sources */, + DCAB9CC923417243009091B6 /* ThemedAlertController.swift in Sources */, 39CC8AE6228C12100020253B /* Array+Extension.swift in Sources */, 6E83C78420A33C180066EC23 /* LAContext+Extension.swift in Sources */, 593BAB46209AE1BC00023634 /* PasscodeViewController.swift in Sources */, @@ -2580,6 +2599,7 @@ DCB44D852186FEF700DAA4CC /* ThemeStyle+DefaultStyles.swift in Sources */, 597A404920AD59EF00B028B2 /* AppLockWindow.swift in Sources */, 6EA78B8F2179B55400A5216A /* ImageScrollView.swift in Sources */, + DCC5E446232654DE002E5B84 /* NSObject+AnnotatedProperties.m in Sources */, DC42244C207CAFBB0006A2A6 /* ThemeCollection.swift in Sources */, DC68057A212EAB5E006C3B1F /* ThemeCertificateViewController.swift in Sources */, DCFED9BA20809B8900A2D984 /* ThemeTVGResource.swift in Sources */, @@ -2593,13 +2613,13 @@ DC1B270A209CF0D3004715E1 /* ConnectionIssueViewController.swift in Sources */, DC0B37972051681600189B9A /* ThemeButton.swift in Sources */, DCF4F17B20519F9D00189B9A /* StaticTableViewSection.swift in Sources */, + DC243BFF2317B446004FBB5C /* ThemeWindow.swift in Sources */, 39D06BEC229BE8D8000D7FC9 /* SettingsSection.swift in Sources */, DCD2D40622F06ECA0071FB8F /* StorageSettingsSection.swift in Sources */, 39B289A8226F1EE000BE0E11 /* MessageView.swift in Sources */, 4C464BF22187AF1500D30602 /* PDFSearchViewController.swift in Sources */, DC3393A422E0A75C00DD3DA4 /* ClientItemResolvingCell.swift in Sources */, DC29F09522976B9300F77349 /* LibrarySharesTableViewController.swift in Sources */, - 392557FE2278703300E83F60 /* UISearchBar+Extension.swift in Sources */, DC7DBA2B207F71E400E7337D /* VectorImageView.swift in Sources */, 39DE75CD22F960CF0064C1E2 /* SortMethodTableViewController.swift in Sources */, DCE974BC207EACA60069FC2B /* UIImage+Extension.swift in Sources */, @@ -2903,7 +2923,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - APP_SHORT_VERSION = 1.1.0; + APP_SHORT_VERSION = 1.1.1; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -2965,7 +2985,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - APP_SHORT_VERSION = 1.1.0; + APP_SHORT_VERSION = 1.1.1; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -3024,7 +3044,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 142; + CURRENT_PROJECT_VERSION = 143; DEVELOPMENT_TEAM = 4AP2STM4H5; FRAMEWORK_SEARCH_PATHS = "${TARGET_BUILD_DIR}"; GCC_WARN_UNUSED_LABEL = YES; @@ -3052,7 +3072,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 142; + CURRENT_PROJECT_VERSION = 143; DEVELOPMENT_TEAM = 4AP2STM4H5; FRAMEWORK_SEARCH_PATHS = "${TARGET_BUILD_DIR}"; GCC_WARN_UNUSED_LABEL = YES; @@ -3198,11 +3218,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 142; + CURRENT_PROJECT_VERSION = 143; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 142; + DYLIB_CURRENT_VERSION = 143; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -3231,11 +3251,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 142; + CURRENT_PROJECT_VERSION = 143; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 142; + DYLIB_CURRENT_VERSION = 143; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = ownCloudAppFramework/Resources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme b/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme index c05ab9e9f..74d0af019 100644 --- a/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme +++ b/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme @@ -26,8 +26,41 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - codeCoverageEnabled = "YES" - shouldUseLaunchSchemeArgsEnv = "NO"> + shouldUseLaunchSchemeArgsEnv = "NO" + codeCoverageEnabled = "YES"> + + + + + + + + + + + + + + + + @@ -84,39 +117,6 @@ - - - - - - - - - - - - - - - - Bool { @@ -33,7 +33,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { Log.log("ownCloud \(VendorServices.shared.appVersion) (\(VendorServices.shared.appBuildNumber)) #\(LastGitCommit() ?? "unknown") finished launching with log settings: \(Log.logOptionStatus)") // Set up app - window = UIWindow(frame: UIScreen.main.bounds) + window = ThemeWindow(frame: UIScreen.main.bounds) ThemeStyle.registerDefaultStyles() @@ -83,6 +83,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Licenses OCExtensionManager.shared.addExtension(OCExtension.license(withIdentifier: "license.libzip", bundleOf: Theme.self, title: "libzip", resourceName: "libzip", fileExtension: "LICENSE")) + // Initially apply theme based on light / dark mode + ThemeStyle.considerAppearanceUpdate() + //Disable UI Animation for UITesting (screenshots) if let enableUIAnimations = VendorServices.classSetting(forOCClassSettingsKey: .enableUIAnimations) as? Bool { UIView.setAnimationsEnabled(enableUIAnimations) diff --git a/ownCloud/Bookmarks/BookmarkInfoViewController.swift b/ownCloud/Bookmarks/BookmarkInfoViewController.swift index bf39fe8e0..4de509d0e 100644 --- a/ownCloud/Bookmarks/BookmarkInfoViewController.swift +++ b/ownCloud/Bookmarks/BookmarkInfoViewController.swift @@ -47,7 +47,7 @@ class BookmarkInfoViewController: StaticTableViewController { // Compacting let includeAvailableOfflineCopiesRow = StaticTableViewRow(switchWithAction: { [weak self] (row, _) in if (row.value as? Bool) == true { - let alertController = UIAlertController(title: "Really include available offline files?".localized, + let alertController = ThemedAlertController(title: "Really include available offline files?".localized, message: "Files and folders marked as Available Offline will become unavailable. They will be re-downloaded next time you log into your account (connectivity required).".localized, preferredStyle: .alert) @@ -88,7 +88,7 @@ class BookmarkInfoViewController: StaticTableViewController { row.cell?.accessoryView = nil if error != nil { // Inform user if vault couldn't be comp acted - let alertController = UIAlertController(title: NSString(format: "Compacting of '%@' failed".localized as NSString, bookmark.shortName as NSString) as String, + let alertController = ThemedAlertController(title: NSString(format: "Compacting of '%@' failed".localized as NSString, bookmark.shortName as NSString) as String, message: error?.localizedDescription, preferredStyle: .alert) diff --git a/ownCloud/Bookmarks/BookmarkViewController.swift b/ownCloud/Bookmarks/BookmarkViewController.swift index bb2361cf8..528c6e006 100644 --- a/ownCloud/Bookmarks/BookmarkViewController.swift +++ b/ownCloud/Bookmarks/BookmarkViewController.swift @@ -338,7 +338,7 @@ class BookmarkViewController: StaticTableViewController { // Check for zero-length host name if (serverURL.host == nil) || ((serverURL.host != nil) && (serverURL.host?.count==0)) { // Missing hostname - let alertController = UIAlertController(title: "Missing hostname".localized, message: "The entered URL does not include a hostname.", preferredStyle: .alert) + let alertController = ThemedAlertController(title: "Missing hostname".localized, message: "The entered URL does not include a hostname.", preferredStyle: .alert) alertController.addAction(UIAlertAction(title: "OK".localized, style: .default, handler: nil)) diff --git a/ownCloud/Bookmarks/Issues/Issues Subclasses/ConnectionIssueViewController.swift b/ownCloud/Bookmarks/Issues/Issues Subclasses/ConnectionIssueViewController.swift index a4551ff4b..2b36ef23d 100644 --- a/ownCloud/Bookmarks/Issues/Issues Subclasses/ConnectionIssueViewController.swift +++ b/ownCloud/Bookmarks/Issues/Issues Subclasses/ConnectionIssueViewController.swift @@ -163,15 +163,13 @@ extension ConnectionIssueViewController: UITableViewDataSource { color = Theme.shared.activeCollection.errorColor } - cell.textLabel?.attributedText = NSAttributedString(string: issue.localizedTitle ?? "", attributes: [ - .foregroundColor : color, - .font : UIFont.systemFont(ofSize: 18, weight: .semibold) - ]) - - cell.detailTextLabel?.attributedText = NSAttributedString(string: issue.localizedDescription ?? "", attributes: [ - .foregroundColor : UIColor(hex: 0x4F4F4F), - .font : UIFont.systemFont(ofSize: 15, weight: .regular) - ]) + cell.textLabel?.font = UIFont.systemFont(ofSize: 18, weight: .semibold) + cell.textLabel?.textColor = color + cell.textLabel?.text = issue.localizedTitle ?? "" + + cell.detailTextLabel?.font = UIFont.systemFont(ofSize: 15, weight: .regular) + cell.detailTextLabel?.text = issue.localizedDescription ?? "" + cell.detailTextLabel?.numberOfLines = 0 cell.accessibilityIdentifier = "issue-row.\(indexPath.row)" return cell diff --git a/ownCloud/Bookmarks/Issues/IssuesViewController.swift b/ownCloud/Bookmarks/Issues/IssuesViewController.swift index 7b7b31513..6dd41faba 100644 --- a/ownCloud/Bookmarks/Issues/IssuesViewController.swift +++ b/ownCloud/Bookmarks/Issues/IssuesViewController.swift @@ -32,14 +32,25 @@ struct IssueButton { let accessibilityIdentifier: String } -class IssuesTableViewCell : UITableViewCell { +class IssuesTableViewCell : UITableViewCell, Themeable { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: .subtitle, reuseIdentifier: reuseIdentifier) + + Theme.shared.register(client: self, applyImmediately: true) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + deinit { + Theme.shared.unregister(client: self) + } + + func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { + self.backgroundColor = collection.tableBackgroundColor + self.detailTextLabel?.textColor = collection.tableRowColors.labelColor + } } let IssuesViewControllerCellIdentifier = "issue-cell" @@ -90,7 +101,7 @@ class IssuesViewController: UIViewController { tableView?.separatorInset = .zero tableView?.bounces = false tableView?.rowHeight = UITableView.automaticDimension - tableView?.register(IssuesTableViewCell.self, forCellReuseIdentifier: IssuesViewControllerCellIdentifier) + tableView?.register(IssuesTableViewCell.self, forCellReuseIdentifier: IssuesViewControllerCellIdentifier) } private func setupBottomContainer() { @@ -116,8 +127,8 @@ class IssuesViewController: UIViewController { case .custom(let backColor): backgroundColor = backColor default: - backgroundColor = .white - color = .blue + backgroundColor = Theme.shared.activeCollection.tableRowColors.filledColorPairCollection.normal.background + color = Theme.shared.activeCollection.tableRowColors.filledColorPairCollection.normal.foreground } button.backgroundColor = backgroundColor @@ -176,12 +187,14 @@ extension IssuesViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let cell = UITableViewCell(style: .default, reuseIdentifier: nil) - cell.textLabel?.attributedText = NSAttributedString(string: headerTitle ?? "", attributes: [.font : UIFont.systemFont(ofSize: 20, weight: .semibold)]) - cell.backgroundColor = .white + cell.textLabel?.text = headerTitle ?? "" + cell.textLabel?.font = UIFont.systemFont(ofSize: 20, weight: .semibold) + cell.textLabel?.textColor = Theme.shared.activeCollection.tableRowColors.labelColor + cell.backgroundColor = Theme.shared.activeCollection.tableBackgroundColor let separatorView: UIView = UIView() separatorView.translatesAutoresizingMaskIntoConstraints = false - separatorView.backgroundColor = UIColor(hex: 0x04040F) + separatorView.backgroundColor = Theme.shared.activeCollection.tableSeparatorColor cell.addSubview(separatorView) separatorView.heightAnchor.constraint(equalToConstant: 1).isActive = true separatorView.leftAnchor.constraint(equalTo: cell.leftAnchor, constant: 0).isActive = true diff --git a/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift b/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift index 12e55a41d..961670e98 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/CopyAction.swift @@ -68,7 +68,7 @@ class CopyAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem { + if location == .moreItem || location == .moreFolder { return UIImage(named: "copy-file") } diff --git a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift index 8d73d23de..a985a34b0 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DeleteAction.swift @@ -86,7 +86,7 @@ class DeleteAction : Action { self.completed() } - let alertController = UIAlertController( + let alertController = ThemedAlertController( with: name, message: message, destructiveLabel: "Delete".localized, @@ -100,7 +100,7 @@ class DeleteAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem { + if location == .moreItem || location == .moreFolder { return UIImage(named: "trash") } diff --git a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift index 72d260d53..ea384cbeb 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/DuplicateAction.swift @@ -71,7 +71,7 @@ class DuplicateAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem { + if location == .moreItem || location == .moreFolder { return UIImage(named: "duplicate-file") } diff --git a/ownCloud/Client/Actions/Actions+Extensions/MakeUnavailableOfflineAction.swift b/ownCloud/Client/Actions/Actions+Extensions/MakeUnavailableOfflineAction.swift index 7a9463c8b..43c0f0323 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/MakeUnavailableOfflineAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/MakeUnavailableOfflineAction.swift @@ -78,7 +78,7 @@ class MakeUnavailableOfflineAction: Action { override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { if location == .moreItem || location == .moreFolder { - return UIImage(named: "unavailable-offline") + return UIImage(named: "available-offline") } return nil diff --git a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift index 4604ba253..c14ce4893 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/MoveAction.swift @@ -71,7 +71,7 @@ class MoveAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem { + if location == .moreItem || location == .moreFolder { return UIImage(named: "folder") } diff --git a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift index aa88b871a..9aaa2f340 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/OpenInAction.swift @@ -47,7 +47,7 @@ class OpenInAction: Action { } let appName = OCAppIdentity.shared.appName ?? "ownCloud" - let alertController = UIAlertController(with: "Cannot connect to ".localized + appName, message: appName + " couldn't download file(s)".localized, okLabel: "OK".localized, action: nil) + let alertController = ThemedAlertController(with: "Cannot connect to ".localized + appName, message: appName + " couldn't download file(s)".localized, okLabel: "OK".localized, action: nil) hostViewController?.present(alertController, animated: true) } else { diff --git a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift index e51318a14..6144931e8 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/RenameAction.swift @@ -83,7 +83,7 @@ class RenameAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem { + if location == .moreItem || location == .moreFolder { return UIImage(named: "folder") } diff --git a/ownCloud/Client/Actions/Actions+Extensions/UnshareAction.swift b/ownCloud/Client/Actions/Actions+Extensions/UnshareAction.swift index ccd68c6ac..bbb37c596 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/UnshareAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/UnshareAction.swift @@ -115,7 +115,7 @@ class UnshareAction : Action { self.completed() } - let alertController = UIAlertController( + let alertController = ThemedAlertController( with: name, message: message, destructiveLabel: "Unshare".localized, @@ -129,7 +129,7 @@ class UnshareAction : Action { } override class func iconForLocation(_ location: OCExtensionLocationIdentifier) -> UIImage? { - if location == .moreItem { + if location == .moreItem || location == .moreFolder { return UIImage(named: "trash") } diff --git a/ownCloud/Client/Actions/MoreViewHeader.swift b/ownCloud/Client/Actions/MoreViewHeader.swift index 597c9b094..a90b0cb15 100644 --- a/ownCloud/Client/Actions/MoreViewHeader.swift +++ b/ownCloud/Client/Actions/MoreViewHeader.swift @@ -25,26 +25,33 @@ class MoreViewHeader: UIView { private var titleLabel: UILabel private var detailLabel: UILabel private var favoriteButton: UIButton + var activityIndicator : UIActivityIndicatorView + private var showsIcon : Bool = true var thumbnailSize = CGSize(width: 60, height: 60) let favoriteSize = CGSize(width: 24, height: 24) var showFavoriteButton: Bool + var showActivityIndicator: Bool + var adaptBackgroundColor : Bool var item: OCItem weak var core: OCCore? var url: URL? - init(for item: OCItem, with core: OCCore, favorite: Bool = true) { + init(for item: OCItem, with core: OCCore, favorite: Bool = true, adaptBackgroundColor: Bool = false, showActivityIndicator: Bool = false) { self.item = item self.core = core self.showFavoriteButton = favorite + self.showActivityIndicator = showActivityIndicator iconView = UIImageView() titleLabel = UILabel() detailLabel = UILabel() labelContainerView = UIView() favoriteButton = UIButton() + activityIndicator = UIActivityIndicatorView(style: .white) + self.adaptBackgroundColor = adaptBackgroundColor super.init(frame: .zero) @@ -57,6 +64,8 @@ class MoreViewHeader: UIView { init(url : URL) { self.showFavoriteButton = false + self.showActivityIndicator = false + self.adaptBackgroundColor = false self.item = OCItem() self.url = url @@ -65,6 +74,7 @@ class MoreViewHeader: UIView { detailLabel = UILabel() labelContainerView = UIView() favoriteButton = UIButton() + activityIndicator = UIActivityIndicatorView(style: .white) super.init(frame: .zero) @@ -85,13 +95,12 @@ class MoreViewHeader: UIView { iconView.translatesAutoresizingMaskIntoConstraints = false labelContainerView.translatesAutoresizingMaskIntoConstraints = false favoriteButton.translatesAutoresizingMaskIntoConstraints = false + activityIndicator.translatesAutoresizingMaskIntoConstraints = false iconView.contentMode = .scaleAspectFit titleLabel.font = UIFont.systemFont(ofSize: 17, weight: UIFont.Weight.semibold) detailLabel.font = UIFont.systemFont(ofSize: 14) - detailLabel.textColor = UIColor.gray - labelContainerView.addSubview(titleLabel) labelContainerView.addSubview(detailLabel) @@ -139,6 +148,14 @@ class MoreViewHeader: UIView { favoriteButton.centerYAnchor.constraint(equalTo: self.centerYAnchor), favoriteButton.leftAnchor.constraint(equalTo: labelContainerView.rightAnchor, constant: 10) ]) + } else if showActivityIndicator { + self.addSubview(activityIndicator) + + NSLayoutConstraint.activate([ + activityIndicator.centerYAnchor.constraint(equalTo: self.centerYAnchor), + activityIndicator.rightAnchor.constraint(equalTo: self.safeAreaLayoutGuide.rightAnchor, constant: -15), + activityIndicator.leftAnchor.constraint(equalTo: labelContainerView.rightAnchor, constant: 10) + ]) } else { NSLayoutConstraint.activate([ labelContainerView.rightAnchor.constraint(equalTo: self.safeAreaLayoutGuide.rightAnchor, constant: -20) @@ -188,6 +205,7 @@ class MoreViewHeader: UIView { image != nil, self.item.itemVersionIdentifier == thumbnail?.itemVersionIdentifier { OnMainThread { + self.showsIcon = false self.iconView.image = image } } @@ -234,7 +252,16 @@ class MoreViewHeader: UIView { extension MoreViewHeader: Themeable { func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { - self.titleLabel.applyThemeCollection(collection) - self.detailLabel.applyThemeCollection(collection) + titleLabel.applyThemeCollection(collection) + detailLabel.applyThemeCollection(collection, itemStyle: .message) + activityIndicator.style = collection.activityIndicatorViewStyle + + if adaptBackgroundColor { + backgroundColor = collection.tableBackgroundColor + } + + if showsIcon { + iconView.image = item.icon(fitInSize: CGSize(width: thumbnailSize.width, height: thumbnailSize.height)) + } } } diff --git a/ownCloud/Client/Actions/NamingViewController.swift b/ownCloud/Client/Actions/NamingViewController.swift index 8e5318b97..927124620 100644 --- a/ownCloud/Client/Actions/NamingViewController.swift +++ b/ownCloud/Client/Actions/NamingViewController.swift @@ -22,10 +22,7 @@ import ownCloudSDK typealias StringValidatorResult = (Bool, String?) typealias StringValidatorHandler = (String) -> StringValidatorResult -class NamingViewController: UIViewController { - - //TODO This view controller ideally should have Theme support. - +class NamingViewController: UIViewController, Themeable { weak var item: OCItem? weak var core: OCCore? var completion: (String?, NamingViewController) -> Void @@ -62,7 +59,7 @@ class NamingViewController: UIViewController { self.stringValidator = stringValidator self.defaultName = defaultName - blurView = UIVisualEffectView.init(effect: UIBlurEffect(style: .regular)) + blurView = UIVisualEffectView(effect: UIBlurEffect(style: Theme.shared.activeCollection.backgroundBlurEffectStyle)) stackView = UIStackView(frame: .zero) @@ -82,6 +79,8 @@ class NamingViewController: UIViewController { thumbnailHeightAnchorConstraint = thumbnailImageView.heightAnchor.constraint(equalToConstant: 150) super.init(nibName: nil, bundle: nil) + + Theme.shared.register(client: self, applyImmediately: true) } convenience init(with item: OCItem, core: OCCore? = nil, stringValidator: StringValidatorHandler? = nil, completion: @escaping (String?, NamingViewController) -> Void) { @@ -98,6 +97,13 @@ class NamingViewController: UIViewController { deinit { NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardDidShowNotification, object: nil) + Theme.shared.unregister(client: self) + } + + func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { + nameTextField.backgroundColor = collection.tableBackgroundColor + nameTextField.textColor = collection.tableRowColors.labelColor + nameTextField.keyboardAppearance = collection.keyboardAppearance } override func viewDidLoad() { @@ -170,7 +176,6 @@ class NamingViewController: UIViewController { nameTextField.rightAnchor.constraint(equalTo: nameContainer.rightAnchor, constant: -20) ]) - nameTextField.backgroundColor = .white nameTextField.delegate = self nameTextField.textAlignment = .center nameTextField.becomeFirstResponder() @@ -266,6 +271,8 @@ class NamingViewController: UIViewController { } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + render(newTraitCollection: traitCollection) } @@ -301,7 +308,7 @@ class NamingViewController: UIViewController { self.completion(self.nameTextField.text!, self) } } else { - let controller = UIAlertController(title: "Forbidden Characters".localized, message: validationErrorMessage, preferredStyle: .alert) + let controller = ThemedAlertController(title: "Forbidden Characters".localized, message: validationErrorMessage, preferredStyle: .alert) controller.view.accessibilityIdentifier = "forbidden-characters-alert" let okAction = UIAlertAction(title: "OK".localized, style: .default) controller.addAction(okAction) diff --git a/ownCloud/Client/ClientItemCell.swift b/ownCloud/Client/ClientItemCell.swift index b1680c8aa..efd752c92 100644 --- a/ownCloud/Client/ClientItemCell.swift +++ b/ownCloud/Client/ClientItemCell.swift @@ -34,12 +34,15 @@ class ClientItemCell: ThemeTableViewCell { private let iconViewWidth : CGFloat = 60 private let moreButtonWidth : CGFloat = 60 private let verticalLabelMarginFromCenter : CGFloat = 2 + private let iconSize : CGSize = CGSize(width: 40, height: 40) + private let thumbnailSize : CGSize = CGSize(width: 60, height: 60) weak var delegate: ClientItemCellDelegate? var titleLabel : UILabel = UILabel() var detailLabel : UILabel = UILabel() var iconView : UIImageView = UIImageView() + var showingIcon : Bool = false var cloudStatusIconView : UIImageView = UIImageView() var sharedStatusIconView : UIImageView = UIImageView() var publicLinkStatusIconView : UIImageView = UIImageView() @@ -117,6 +120,7 @@ class ClientItemCell: ThemeTableViewCell { titleLabel.font = UIFont.preferredFont(forTextStyle: .headline) titleLabel.adjustsFontForContentSizeCategory = true + titleLabel.lineBreakMode = .byTruncatingMiddle detailLabel.font = UIFont.preferredFont(forTextStyle: .footnote) detailLabel.adjustsFontForContentSizeCategory = true @@ -225,8 +229,6 @@ class ClientItemCell: ThemeTableViewCell { } func updateWith(_ item: OCItem) { - let iconSize : CGSize = CGSize(width: 40, height: 40) - let thumbnailSize : CGSize = CGSize(width: 60, height: 60) var iconImage : UIImage? // Cancel any already active request @@ -235,16 +237,18 @@ class ClientItemCell: ThemeTableViewCell { } iconImage = item.icon(fitInSize: iconSize) + showingIcon = true self.accessoryType = .none if item.thumbnailAvailability != .none { let displayThumbnail = { (thumbnail: OCItemThumbnail?) in - _ = thumbnail?.requestImage(for: thumbnailSize, scale: 0, withCompletionHandler: { (thumbnail, error, _, image) in + _ = thumbnail?.requestImage(for: self.thumbnailSize, scale: 0, withCompletionHandler: { (thumbnail, error, _, image) in if error == nil, image != nil, self.item?.itemVersionIdentifier == thumbnail?.itemVersionIdentifier { OnMainThread { + self.showingIcon = false self.iconView.image = image } } @@ -254,7 +258,7 @@ class ClientItemCell: ThemeTableViewCell { if let thumbnail = item.thumbnail { displayThumbnail(thumbnail) } else { - activeThumbnailRequestProgress = core?.retrieveThumbnail(for: item, maximumSize: thumbnailSize, scale: 0, retrieveHandler: { [weak self] (_, _, _, thumbnail, _, progress) in + activeThumbnailRequestProgress = core?.retrieveThumbnail(for: item, maximumSize: self.thumbnailSize, scale: 0, retrieveHandler: { [weak self] (_, _, _, thumbnail, _, progress) in displayThumbnail(thumbnail) if self?.activeThumbnailRequestProgress === progress { @@ -419,6 +423,10 @@ class ClientItemCell: ThemeTableViewCell { detailLabel.textColor = collection.tableRowColors.secondaryLabelColor moreButton.tintColor = collection.tableRowColors.labelColor + + if showingIcon, let item = item { + iconView.image = item.icon(fitInSize: iconSize) + } } // MARK: - Editing mode diff --git a/ownCloud/Client/ClientQueryViewController.swift b/ownCloud/Client/ClientQueryViewController.swift index d0648399f..f08ed0b01 100644 --- a/ownCloud/Client/ClientQueryViewController.swift +++ b/ownCloud/Client/ClientQueryViewController.swift @@ -420,6 +420,10 @@ class ClientQueryViewController: QueryFileListTableViewController, UIDropInterac selectedItemIds.removeAll() removeToolbar() sortBar?.showSelectButton = true + + if #available(iOS 13, *) { + self.tableView.overrideUserInterfaceStyle = .unspecified + } } func populateToolbar() { @@ -464,6 +468,10 @@ class ClientQueryViewController: QueryFileListTableViewController, UIDropInterac @objc func multipleSelectionButtonPressed() { if !self.tableView.isEditing { + if #available(iOS 13, *) { + self.tableView.overrideUserInterfaceStyle = Theme.shared.activeCollection.interfaceStyle.userInterfaceStyle + } + updateMultiSelectionUI() self.tableView.setEditing(true, animated: true) sortBar?.showSelectButton = false @@ -500,7 +508,7 @@ class ClientQueryViewController: QueryFileListTableViewController, UIDropInterac @objc func plusBarButtonPressed(_ sender: UIBarButtonItem) { - let controller = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + let controller = ThemedAlertController(title: nil, message: nil, preferredStyle: .actionSheet) // Actions for folderAction if let core = self.core, let rootItem = query.rootItem { @@ -551,6 +559,16 @@ class ClientQueryViewController: QueryFileListTableViewController, UIDropInterac tableViewController.bookmarkShortName = shortName } + 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() + } + let popoverPresentationController = tableViewController.popoverPresentationController popoverPresentationController?.sourceView = sender popoverPresentationController?.delegate = self diff --git a/ownCloud/Client/ClientRootViewController.swift b/ownCloud/Client/ClientRootViewController.swift index c040954e1..55961cf79 100644 --- a/ownCloud/Client/ClientRootViewController.swift +++ b/ownCloud/Client/ClientRootViewController.swift @@ -224,8 +224,8 @@ class ClientRootViewController: UITabBarController, UINavigationControllerDelega override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - if MediaUploadQueue.shallShowUploadUnfinishedWarning (for: self.bookmark) { - let unfinishedUploadAlert = UIAlertController(with: "Warning".localized, + if MediaUploadQueue.isMediaUploadPendingFlagSet(for: self.bookmark) { + let unfinishedUploadAlert = ThemedAlertController(with: "Warning".localized, message: "Media upload in the previous session was incomplete since the application was terminated".localized) self.present(unfinishedUploadAlert, animated: true, completion: nil) MediaUploadQueue.resetUploadPendingFlag(for: self.bookmark) @@ -391,7 +391,7 @@ extension ClientRootViewController : OCCoreDelegate { var queueCompletionHandlerScheduled : Bool = false if isAuthFailure { - let alertController = UIAlertController(title: authFailureTitle, + let alertController = ThemedAlertController(title: authFailureTitle, message: authFailureMessage, preferredStyle: .alert) @@ -423,7 +423,7 @@ extension ClientRootViewController : OCCoreDelegate { var presentViewController : UIViewController? if presentIssue?.type == .multipleChoice { - presentViewController = UIAlertController(with: presentIssue!, completion: queueCompletionHandler) + presentViewController = ThemedAlertController(with: presentIssue!, completion: queueCompletionHandler) } else { presentViewController = ConnectionIssueViewController(displayIssues: presentIssue?.prepareForDisplay(), completion: { (response) in switch response { diff --git a/ownCloud/Client/Library/LibraryTableViewController.swift b/ownCloud/Client/Library/LibraryTableViewController.swift index 96dce2202..20762eff2 100644 --- a/ownCloud/Client/Library/LibraryTableViewController.swift +++ b/ownCloud/Client/Library/LibraryTableViewController.swift @@ -59,6 +59,10 @@ class LibraryTableViewController: StaticTableViewController { weak var core : OCCore? deinit { + for applierToken in applierTokens { + Theme.shared.remove(applierForToken: applierToken) + } + self.stopQueries() } @@ -91,6 +95,7 @@ class LibraryTableViewController: StaticTableViewController { var shareQueryByUser : OCShareQuery? var shareQueryAcceptedCloudShares : OCShareQuery? var shareQueryPendingCloudShares : OCShareQuery? + private var applierTokens : [ThemeApplierToken] = [] private func start(query: OCCoreQuery) { core?.start(query) @@ -389,14 +394,14 @@ class LibraryTableViewController: StaticTableViewController { }) let imageQuery = OCQuery(condition: .where(.mimeType, contains: "image"), inputFilter:nil) - addCollectionRow(to: section, title: "Images".localized, image: Theme.shared.image(for: "image", size: CGSize(width: 25, height: 25))!, query: imageQuery, actionHandler: nil) + addCollectionRow(to: section, title: "Images".localized, themeImageName: "image", query: imageQuery, actionHandler: nil) let pdfQuery = OCQuery(condition: .where(.mimeType, contains: "pdf"), inputFilter:nil) - addCollectionRow(to: section, title: "PDF Documents".localized, image: Theme.shared.image(for: "application-pdf", size: CGSize(width: 25, height: 25))!, query: pdfQuery, actionHandler: nil) + addCollectionRow(to: section, title: "PDF Documents".localized, themeImageName: "application-pdf", query: pdfQuery, actionHandler: nil) } } - func addCollectionRow(to section: StaticTableViewSection, title: String, image: UIImage, query: OCQuery?, actionHandler: ((_ completion: @escaping () -> Void) -> Void)?) { + func addCollectionRow(to section: StaticTableViewSection, title: String, image: UIImage? = nil, themeImageName: String? = nil, query: OCQuery?, actionHandler: ((_ completion: @escaping () -> Void) -> Void)?) { let identifier = String(format:"%@-collection-row", title) if section.row(withIdentifier: identifier) == nil, let core = core { let row = StaticTableViewRow(rowWithAction: { [weak self] (_, _) in @@ -409,7 +414,18 @@ class LibraryTableViewController: StaticTableViewController { } actionHandler?({}) - }, title: title, image: image, accessoryType: .disclosureIndicator, identifier: identifier) + }, title: title, image: image, imageTintColorKey: "secondaryLabelColor", accessoryType: .disclosureIndicator, identifier: identifier) + + if themeImageName != nil { + let themeApplierToken = Theme.shared.add(applier: { [weak row] (theme, _, _) in + if let themeImageName = themeImageName { + row?.cell?.imageView?.image = theme.image(for: themeImageName, size: CGSize(width: 25, height: 25)) + } + }, applyImmediately: true) + + applierTokens.append(themeApplierToken) + } + section.add(row: row) } } diff --git a/ownCloud/Client/Sharing/GroupSharingEditTableViewController.swift b/ownCloud/Client/Sharing/GroupSharingEditTableViewController.swift index 0d200121b..f97e8fcb5 100644 --- a/ownCloud/Client/Sharing/GroupSharingEditTableViewController.swift +++ b/ownCloud/Client/Sharing/GroupSharingEditTableViewController.swift @@ -99,7 +99,7 @@ class GroupSharingEditTableViewController: StaticTableViewController { } else { if let shareError = error { OnMainThread { - let alertController = UIAlertController(with: "Adding User to Share failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) + let alertController = ThemedAlertController(with: "Adding User to Share failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) self.present(alertController, animated: true) } } @@ -264,7 +264,7 @@ class GroupSharingEditTableViewController: StaticTableViewController { } else { if let shareError = error { OnMainThread { - let alertController = UIAlertController(with: "Setting permission failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) + let alertController = ThemedAlertController(with: "Setting permission failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) self.present(alertController, animated: true) completionHandler(shareError) } @@ -320,7 +320,7 @@ class GroupSharingEditTableViewController: StaticTableViewController { self?.navigationController?.popViewController(animated: true) } else { if let shareError = error { - let alertController = UIAlertController(with: "Delete Recipient failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) + let alertController = ThemedAlertController(with: "Delete Recipient failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) self?.present(alertController, animated: true) } } diff --git a/ownCloud/Client/Sharing/GroupSharingTableViewController.swift b/ownCloud/Client/Sharing/GroupSharingTableViewController.swift index 6b5db5628..b643ac725 100644 --- a/ownCloud/Client/Sharing/GroupSharingTableViewController.swift +++ b/ownCloud/Client/Sharing/GroupSharingTableViewController.swift @@ -92,6 +92,7 @@ class GroupSharingTableViewController: SharingTableViewController, UISearchResul recipientSearchController = core?.recipientSearchController(for: item) recipientSearchController?.delegate = self recipientSearchController?.minimumSearchTermLength = core?.connection.capabilities?.sharingSearchMinLength?.uintValue ?? OCCapabilities.defaultSharingSearchMinLength.magnitude + showActivityIndicator = true } messageView = MessageView(add: self.view) @@ -182,7 +183,7 @@ class GroupSharingTableViewController: SharingTableViewController, UISearchResul self.dismissAnimated() } else { if let shareError = error { - let alertController = UIAlertController(with: "Unshare failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) + let alertController = ThemedAlertController(with: "Unshare failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) self.present(alertController, animated: true) } } @@ -362,11 +363,18 @@ class GroupSharingTableViewController: SharingTableViewController, UISearchResul func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { self.resetTable(showShares: true) - self.searchController?.searchBar.isLoading = false + self.messageView?.message(show: false) + + if let headerView = self.tableView.tableHeaderView as? MoreViewHeader { + headerView.activityIndicator.stopAnimating() + } } func searchControllerHasNewResults(_ searchController: OCRecipientSearchController, error: Error?) { OnMainThread { + if let headerView = self.tableView.tableHeaderView as? MoreViewHeader { + headerView.activityIndicator.stopAnimating() + } guard let recipients = searchController.recipients, let core = self.core else { self.messageView?.message(show: true, imageName: "icon-search", title: "No matches".localized, message: "There are no results for this search term".localized) return @@ -428,10 +436,13 @@ class GroupSharingTableViewController: SharingTableViewController, UISearchResul } func searchController(_ searchController: OCRecipientSearchController, isWaitingForResults isSearching: Bool) { - if isSearching { - self.searchController?.searchBar.isLoading = true - } else { - self.searchController?.searchBar.isLoading = false + OnMainThread { + if isSearching { + + if let headerView = self.tableView.tableHeaderView as? MoreViewHeader { + headerView.activityIndicator.startAnimating() + } + } } } @@ -450,7 +461,7 @@ class GroupSharingTableViewController: SharingTableViewController, UISearchResul presentationStyle = .alert } - let alertController = UIAlertController(title: "Remove Recipient".localized, message: nil, preferredStyle: presentationStyle) + let alertController = ThemedAlertController(title: "Remove Recipient".localized, message: nil, preferredStyle: presentationStyle) alertController.addAction(UIAlertAction(title: "Cancel".localized, style: .cancel, handler: nil)) alertController.addAction(UIAlertAction(title: "Delete".localized, style: .destructive, handler: { (_) in @@ -460,7 +471,7 @@ class GroupSharingTableViewController: SharingTableViewController, UISearchResul self.navigationController?.popViewController(animated: true) } else { if let shareError = error { - let alertController = UIAlertController(with: "Remove Recipient failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) + let alertController = ThemedAlertController(with: "Remove Recipient failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) self.present(alertController, animated: true) } } @@ -475,4 +486,13 @@ class GroupSharingTableViewController: SharingTableViewController, UISearchResul return [] } + + // MARK: Themeing + 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 + } + } } diff --git a/ownCloud/Client/Sharing/PendingSharesTableViewController.swift b/ownCloud/Client/Sharing/PendingSharesTableViewController.swift index 82199739e..d1fc8322c 100644 --- a/ownCloud/Client/Sharing/PendingSharesTableViewController.swift +++ b/ownCloud/Client/Sharing/PendingSharesTableViewController.swift @@ -120,7 +120,7 @@ class PendingSharesTableViewController: StaticTableViewController { presentationStyle = .alert } - let alertController = UIAlertController(title: String(format: "Accept Invite %@".localized, itemName ?? ""), + let alertController = ThemedAlertController(title: String(format: "Accept Invite %@".localized, itemName ?? ""), message: nil, preferredStyle: presentationStyle) alertController.addAction(UIAlertAction(title: "Cancel".localized, style: .cancel, handler: nil)) @@ -194,7 +194,7 @@ class PendingSharesTableViewController: StaticTableViewController { OnMainThread { if error != nil { if let shareError = error { - let alertController = UIAlertController(with: (accept ? "Accept Share failed".localized : "Decline Share failed".localized), message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) + let alertController = ThemedAlertController(with: (accept ? "Accept Share failed".localized : "Decline Share failed".localized), message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) strongSelf.present(alertController, animated: true) } } else if let libraryViewController = strongSelf.libraryViewController { @@ -215,7 +215,7 @@ class PendingSharesTableViewController: StaticTableViewController { itemName = (share.itemPath as NSString).lastPathComponent } - let alertController = UIAlertController(title: String(format: "Decline Invite %@".localized, itemName ?? ""), message: "Decline cannot be undone.", preferredStyle: .alert) + let alertController = ThemedAlertController(title: String(format: "Decline Invite %@".localized, itemName ?? ""), message: "Decline cannot be undone.", preferredStyle: .alert) alertController.addAction(UIAlertAction(title: "Cancel".localized, style: .cancel, handler: nil)) alertController.addAction(UIAlertAction(title: "Decline".localized, style: .destructive, handler: { [weak self] (_) in self?.makeDecision(on: share, accept: accept) diff --git a/ownCloud/Client/Sharing/PublicLinkEditTableViewController.swift b/ownCloud/Client/Sharing/PublicLinkEditTableViewController.swift index 40ea49fad..eb2f51e31 100644 --- a/ownCloud/Client/Sharing/PublicLinkEditTableViewController.swift +++ b/ownCloud/Client/Sharing/PublicLinkEditTableViewController.swift @@ -141,7 +141,7 @@ class PublicLinkEditTableViewController: StaticTableViewController { } else { if let shareError = error { OnMainThread { - let alertController = UIAlertController(with: "Setting name failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) + let alertController = ThemedAlertController(with: "Setting name failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) self.present(alertController, animated: true) } } @@ -218,7 +218,7 @@ class PublicLinkEditTableViewController: StaticTableViewController { } else { if let shareError = error { OnMainThread { - let alertController = UIAlertController(with: "Setting permission failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) + let alertController = ThemedAlertController(with: "Setting permission failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) self.present(alertController, animated: true) } } @@ -229,7 +229,7 @@ class PublicLinkEditTableViewController: StaticTableViewController { row.section?.setSelected(self.currentPermissionIndex, groupIdentifier: "permission-group") let permissionName = Array(values[selectedValueFromSection])[0].key - let alertController = UIAlertController(with: "Cannot change permission".localized, message: String(format: "Before you can set the permission\n%@,\n you must enter a password.".localized, permissionName), okLabel: "OK".localized, action: nil) + let alertController = ThemedAlertController(with: "Cannot change permission".localized, message: String(format: "Before you can set the permission\n%@,\n you must enter a password.".localized, permissionName), okLabel: "OK".localized, action: nil) self.present(alertController, animated: true) } } @@ -314,7 +314,7 @@ class PublicLinkEditTableViewController: StaticTableViewController { } else { if let shareError = error { OnMainThread { - let alertController = UIAlertController(with: "Deleting password failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) + let alertController = ThemedAlertController(with: "Deleting password failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) self.present(alertController, animated: true) } } @@ -351,7 +351,7 @@ class PublicLinkEditTableViewController: StaticTableViewController { } else { if let shareError = error { OnMainThread { - let alertController = UIAlertController(with: "Setting password failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) + let alertController = ThemedAlertController(with: "Setting password failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) self.present(alertController, animated: true) } } @@ -428,7 +428,7 @@ class PublicLinkEditTableViewController: StaticTableViewController { } else { if let shareError = error { OnMainThread { - let alertController = UIAlertController(with: "Setting expiration date failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) + let alertController = ThemedAlertController(with: "Setting expiration date failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) self.present(alertController, animated: true) } } @@ -496,7 +496,7 @@ class PublicLinkEditTableViewController: StaticTableViewController { } else { if let shareError = error { OnMainThread { - let alertController = UIAlertController(with: "Setting expiration date failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) + let alertController = ThemedAlertController(with: "Setting expiration date failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) self.present(alertController, animated: true) } } @@ -564,7 +564,7 @@ class PublicLinkEditTableViewController: StaticTableViewController { self.navigationController?.popViewController(animated: true) } else { if let shareError = error { - let alertController = UIAlertController(with: "Deleting Public Link failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) + let alertController = ThemedAlertController(with: "Deleting Public Link failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) self.present(alertController, animated: true) } } @@ -654,7 +654,7 @@ class PublicLinkEditTableViewController: StaticTableViewController { } else { if let shareError = error { OnMainThread { - let alertController = UIAlertController(with: "Creating public link failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) + let alertController = ThemedAlertController(with: "Creating public link failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) self.present(alertController, animated: true) } } diff --git a/ownCloud/Client/Sharing/PublicLinkTableViewController.swift b/ownCloud/Client/Sharing/PublicLinkTableViewController.swift index 2118d98f2..ea09236b9 100644 --- a/ownCloud/Client/Sharing/PublicLinkTableViewController.swift +++ b/ownCloud/Client/Sharing/PublicLinkTableViewController.swift @@ -219,7 +219,7 @@ class PublicLinkTableViewController: SharingTableViewController { presentationStyle = .alert } - let alertController = UIAlertController(title: "Delete Public Link".localized, + let alertController = ThemedAlertController(title: "Delete Public Link".localized, message: nil, preferredStyle: presentationStyle) alertController.addAction(UIAlertAction(title: "Cancel".localized, style: .cancel, handler: nil)) @@ -231,7 +231,7 @@ class PublicLinkTableViewController: SharingTableViewController { self.navigationController?.popViewController(animated: true) } else { if let shareError = error { - let alertController = UIAlertController(with: "Delete Public Link failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) + let alertController = ThemedAlertController(with: "Delete Public Link failed".localized, message: shareError.localizedDescription, okLabel: "OK".localized, action: nil) self.present(alertController, animated: true) } } diff --git a/ownCloud/Client/Sharing/SharingTableViewController.swift b/ownCloud/Client/Sharing/SharingTableViewController.swift index 0338e2a10..c1d9a2717 100644 --- a/ownCloud/Client/Sharing/SharingTableViewController.swift +++ b/ownCloud/Client/Sharing/SharingTableViewController.swift @@ -27,6 +27,7 @@ class SharingTableViewController : StaticTableViewController { var shareQuery : OCShareQuery? var shares : [OCShare] = [] + var showActivityIndicator : Bool = false var messageView : MessageView? @@ -59,10 +60,9 @@ class SharingTableViewController : StaticTableViewController { func addHeaderView() { guard let core = core else { return } - let headerView = MoreViewHeader(for: item, with: core, favorite: false) + let headerView = MoreViewHeader(for: item, with: core, favorite: false, adaptBackgroundColor: true, showActivityIndicator: showActivityIndicator) self.tableView.tableHeaderView = headerView self.tableView.layoutTableHeaderView() - self.tableView.tableHeaderView?.backgroundColor = Theme.shared.activeCollection.tableBackgroundColor } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { diff --git a/ownCloud/Client/SortBar.swift b/ownCloud/Client/SortBar.swift index 32c133128..dd3f17506 100644 --- a/ownCloud/Client/SortBar.swift +++ b/ownCloud/Client/SortBar.swift @@ -190,7 +190,7 @@ class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate { Theme.shared.register(client: self) selectButton?.isHidden = !showSelectButton - toggleSortControls() + updateForCurrentTraitCollection() } required init?(coder aDecoder: NSCoder) { @@ -213,10 +213,11 @@ class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate { // MARK: - Sort UI override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - toggleSortControls() + super.traitCollectionDidChange(previousTraitCollection) + self.updateForCurrentTraitCollection() } - func toggleSortControls() { + func updateForCurrentTraitCollection() { switch (traitCollection.horizontalSizeClass, traitCollection.verticalSizeClass) { case (.compact, .regular): sortSegmentedControl?.isHidden = true @@ -245,10 +246,20 @@ class SortBar: UIView, Themeable, UIPopoverPresentationControllerDelegate { // MARK: - Actions @objc private func presentSortButtonOptions(_ sender : UIButton) { let tableViewController = SortMethodTableViewController() - tableViewController.modalPresentationStyle = UIModalPresentationStyle.popover + tableViewController.modalPresentationStyle = .popover 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() + } + let popoverPresentationController = tableViewController.popoverPresentationController popoverPresentationController?.sourceView = sender popoverPresentationController?.delegate = self diff --git a/ownCloud/Client/Viewer/Image/ImageDisplayViewController.swift b/ownCloud/Client/Viewer/Image/ImageDisplayViewController.swift index 19a63fd40..3b0a0c33b 100644 --- a/ownCloud/Client/Viewer/Image/ImageDisplayViewController.swift +++ b/ownCloud/Client/Viewer/Image/ImageDisplayViewController.swift @@ -127,7 +127,7 @@ class ImageDisplayViewController : DisplayViewController { } } else { - let alert = UIAlertController(with: "Error".localized, message: "Could not get the picture".localized, okLabel: "OK") + let alert = ThemedAlertController(with: "Error".localized, message: "Could not get the picture".localized, okLabel: "OK") self.parent?.present(alert, animated: true) { self.parent?.dismiss(animated: true) } diff --git a/ownCloud/Client/Viewer/Media/MediaDisplayViewController.swift b/ownCloud/Client/Viewer/Media/MediaDisplayViewController.swift index dfad70212..11ce1d167 100644 --- a/ownCloud/Client/Viewer/Media/MediaDisplayViewController.swift +++ b/ownCloud/Client/Viewer/Media/MediaDisplayViewController.swift @@ -31,11 +31,16 @@ class MediaDisplayViewController : DisplayViewController { deinit { playerStatusObservation?.invalidate() playerItemStatusObservation?.invalidate() + NotificationCenter.default.removeObserver(self) } override func viewDidLoad() { super.viewDidLoad() self.requiresLocalItemCopy = !(OCAppIdentity.shared.userDefaults?.streamingEnabled ?? false) + + NotificationCenter.default.addObserver(self, selector: #selector(handleDidEnterBackgroundNotification), name: UIApplication.didEnterBackgroundNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(handleWillEnterForegroundNotification), name: UIApplication.willEnterForegroundNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(handleAVPlayerItem(notification:)), name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: nil) } override func viewDidAppear(_ animated: Bool) { @@ -87,6 +92,10 @@ class MediaDisplayViewController : DisplayViewController { playerStatusObservation = player!.observe(\AVPlayer.status, options: [.initial, .new], changeHandler: { [weak self] (player, _) in if player.status == .readyToPlay { + + try? AVAudioSession.sharedInstance().setCategory(.playback) + try? AVAudioSession.sharedInstance().setActive(true) + self?.player?.play() } else if player.status == .failed { self?.present(error: self?.player?.error) @@ -105,13 +114,25 @@ class MediaDisplayViewController : DisplayViewController { guard let error = error else { return } OnMainThread { [weak self] in - let alert = UIAlertController(with: "Error".localized, message: error.localizedDescription, okLabel: "OK".localized, action: { + let alert = ThemedAlertController(with: "Error".localized, message: error.localizedDescription, okLabel: "OK".localized, action: { self?.navigationController?.popViewController(animated: true) }) self?.parent?.present(alert, animated: true) } } + + @objc private func handleDidEnterBackgroundNotification() { + playerViewController?.player = nil + } + + @objc private func handleWillEnterForegroundNotification() { + playerViewController?.player = player + } + + @objc private func handleAVPlayerItem(notification:Notification) { + try? AVAudioSession.sharedInstance().setActive(false) + } } // MARK: - Display Extension. diff --git a/ownCloud/Client/Viewer/PDF/PDFViewerViewController.swift b/ownCloud/Client/Viewer/PDF/PDFViewerViewController.swift index eebd40ace..0166a4b76 100644 --- a/ownCloud/Client/Viewer/PDF/PDFViewerViewController.swift +++ b/ownCloud/Client/Viewer/PDF/PDFViewerViewController.swift @@ -210,7 +210,7 @@ class PDFViewerViewController: DisplayViewController, DisplayExtension { guard let pdfDocument = pdfView.document else { return } let alertMessage = NSString(format: "This document has %@ pages".localized as NSString, "\(pdfDocument.pageCount)") as String - let alertController = UIAlertController(title: "Go to page".localized, message: alertMessage, preferredStyle: .alert) + let alertController = ThemedAlertController(title: "Go to page".localized, message: alertMessage, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: "Cancel".localized, style: .cancel, handler: nil)) alertController.addTextField(configurationHandler: { textField in @@ -357,7 +357,7 @@ class PDFViewerViewController: DisplayViewController, DisplayExtension { self.pdfView.go(to: page) } } else { - let alertController = UIAlertController(title: "Invalid Page".localized, + let alertController = ThemedAlertController(title: "Invalid Page".localized, message: "The entered page number doesn't exist".localized, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: "OK".localized, style: .default, handler: nil)) diff --git a/ownCloud/FileLists/FileListTableViewController.swift b/ownCloud/FileLists/FileListTableViewController.swift index 9883a86f9..1a280510a 100644 --- a/ownCloud/FileLists/FileListTableViewController.swift +++ b/ownCloud/FileLists/FileListTableViewController.swift @@ -107,6 +107,7 @@ class FileListTableViewController: UITableViewController, ClientItemCellDelegate if allowPullToRefresh { pullToRefreshControl = UIRefreshControl() + pullToRefreshControl?.tintColor = Theme.shared.activeCollection.navigationBarColors.labelColor pullToRefreshControl?.addTarget(self, action: #selector(self.pullToRefreshTriggered), for: .valueChanged) self.tableView.insertSubview(pullToRefreshControl!, at: 0) tableView.contentOffset = CGPoint(x: 0, y: self.pullToRefreshVerticalOffset) @@ -246,6 +247,7 @@ class FileListTableViewController: UITableViewController, ClientItemCellDelegate // MARK: - Themable func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { self.tableView.applyThemeCollection(collection) + pullToRefreshControl?.tintColor = collection.navigationBarColors.labelColor if event == .update { self.reloadTableData() diff --git a/ownCloud/FileLists/QueryFileListTableViewController.swift b/ownCloud/FileLists/QueryFileListTableViewController.swift index 796549e7d..950f4ffd3 100644 --- a/ownCloud/FileLists/QueryFileListTableViewController.swift +++ b/ownCloud/FileLists/QueryFileListTableViewController.swift @@ -287,6 +287,7 @@ class QueryFileListTableViewController: FileListTableViewController, SortBarDele sortBar = SortBar(frame: CGRect(x: 0, y: 0, width: self.tableView.frame.width, height: 40), sortMethod: sortMethod) sortBar?.delegate = self sortBar?.sortMethod = self.sortMethod + sortBar?.updateForCurrentTraitCollection() tableView.tableHeaderView = sortBar } diff --git a/ownCloud/PhotoKit Extensions/PHAsset+Upload.swift b/ownCloud/PhotoKit Extensions/PHAsset+Upload.swift index 83a40af23..ad95ee712 100644 --- a/ownCloud/PhotoKit Extensions/PHAsset+Upload.swift +++ b/ownCloud/PhotoKit Extensions/PHAsset+Upload.swift @@ -44,8 +44,23 @@ extension PHAsset { var uploadProgress: Progress? + // Sometimes if the image was edited, the name is FullSizeRender.jpg but it is stored in the subfolder + // in the PhotoLibrary which is named after original image + var fileName = sourceURL.lastPathComponent + for component in sourceURL.pathComponents { + if component.starts(with: "IMG_") { + fileName = component + if component != sourceURL.pathComponents.last! { + fileName += "." + fileName += sourceURL.pathExtension + } + break + } + } + uploadProgress = sourceURL.upload(with: core, at: rootItem, + alternativeName: fileName, importByCopy: copySource, placeholderHandler: { (item, error) in if !copySource && error != nil { @@ -107,7 +122,7 @@ extension PHAsset { guard let url = assetURL else { return } - let fileName = url.lastPathComponent + var fileName = url.lastPathComponent // Check if the conversion was requested and current media format is not found in the list of requested formats if let formats = preferredFormats, formats.count > 0 { diff --git a/ownCloud/Resources/Info.plist b/ownCloud/Resources/Info.plist index 652055f21..5e526340a 100644 --- a/ownCloud/Resources/Info.plist +++ b/ownCloud/Resources/Info.plist @@ -38,7 +38,7 @@ CFBundleShortVersionString $(APP_SHORT_VERSION) CFBundleVersion - 142 + 143 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ownCloud/Resources/en.lproj/Localizable.strings b/ownCloud/Resources/en.lproj/Localizable.strings index 476ea99b9..9ab0f5779 100644 --- a/ownCloud/Resources/en.lproj/Localizable.strings +++ b/ownCloud/Resources/en.lproj/Localizable.strings @@ -185,6 +185,8 @@ "Dark" = "Dark"; "Light" = "Light"; "Classic" = "Classic"; +"System" = "System"; +"System Appeareance" = "System Appeareance"; /* Log settings */ "Log Files" = "Log Files"; diff --git a/ownCloud/Server List/ServerListTableViewController.swift b/ownCloud/Server List/ServerListTableViewController.swift index 0db8bffe0..76a5b0ac8 100644 --- a/ownCloud/Server List/ServerListTableViewController.swift +++ b/ownCloud/Server List/ServerListTableViewController.swift @@ -158,7 +158,7 @@ class ServerListTableViewController: UITableViewController, Themeable { let lastGitCommit = LastGitCommit(), (lastBetaWarningCommit == nil) || (lastBetaWarningCommit != lastGitCommit) { // Beta warning has never been shown before - or has last been shown for a different release - let betaAlert = UIAlertController(with: "Beta Warning", message: "\nThis is a BETA release that may - and likely will - still contain bugs.\n\nYOU SHOULD NOT USE THIS BETA VERSION WITH PRODUCTION SYSTEMS, PRODUCTION DATA OR DATA OF VALUE. YOU'RE USING THIS BETA AT YOUR OWN RISK.\n\nPlease let us know about any issues that come up via the \"Send Feedback\" option in the settings.", okLabel: "Agree") { + let betaAlert = ThemedAlertController(with: "Beta Warning", message: "\nThis is a BETA release that may - and likely will - still contain bugs.\n\nYOU SHOULD NOT USE THIS BETA VERSION WITH PRODUCTION SYSTEMS, PRODUCTION DATA OR DATA OF VALUE. YOU'RE USING THIS BETA AT YOUR OWN RISK.\n\nPlease let us know about any issues that come up via the \"Send Feedback\" option in the settings.", okLabel: "Agree") { OCAppIdentity.shared.userDefaults?.set(lastGitCommit, forKey: "LastBetaWarningCommit") OCAppIdentity.shared.userDefaults?.set(NSDate(), forKey: "LastBetaWarningAcceptDate") } @@ -256,6 +256,8 @@ class ServerListTableViewController: UITableViewController, Themeable { let bookmarkViewController : BookmarkViewController = BookmarkViewController(bookmark, removeAuthDataFromCopy: removeAuthDataFromCopy) let navigationController : ThemeNavigationController = ThemeNavigationController(rootViewController: bookmarkViewController) + navigationController.modalPresentationStyle = .fullScreen + // Prevent any in-progress connection from being shown resetPreviousBookmarkSelection() @@ -288,6 +290,8 @@ class ServerListTableViewController: UITableViewController, Themeable { let viewController = BookmarkInfoViewController(bookmark) let navigationController : ThemeNavigationController = ThemeNavigationController(rootViewController: viewController) + navigationController.modalPresentationStyle = .fullScreen + // Prevent any in-progress connection from being shown resetPreviousBookmarkSelection() @@ -358,6 +362,7 @@ class ServerListTableViewController: UITableViewController, Themeable { }) clientRootViewController.authDelegate = self + clientRootViewController.modalPresentationStyle = .fullScreen clientRootViewController.afterCoreStart { // Make sure only the UI for the last selected bookmark is actually presented (in case of other bookmarks facing a huge delay and users selecting another bookmark in the meantime) @@ -413,6 +418,8 @@ class ServerListTableViewController: UITableViewController, Themeable { } else { self.connect(to: bookmark) } + + self.tableView.deselectRow(at: indexPath, animated: true) } } @@ -462,7 +469,7 @@ class ServerListTableViewController: UITableViewController, Themeable { presentationStyle = .alert } - let alertController = UIAlertController(title: NSString(format: "Really delete '%@'?".localized as NSString, bookmark.shortName) as String, + let alertController = ThemedAlertController(title: NSString(format: "Really delete '%@'?".localized as NSString, bookmark.shortName) as String, message: "This will also delete all locally stored file copies.".localized, preferredStyle: presentationStyle) @@ -478,7 +485,7 @@ class ServerListTableViewController: UITableViewController, Themeable { OnMainThread { if error != nil { // Inform user if vault couldn't be erased - let alertController = UIAlertController(title: NSString(format: "Deletion of '%@' failed".localized as NSString, bookmark.shortName as NSString) as String, + let alertController = ThemedAlertController(title: NSString(format: "Deletion of '%@' failed".localized as NSString, bookmark.shortName as NSString) as String, message: error?.localizedDescription, preferredStyle: .alert) @@ -572,7 +579,7 @@ extension OCBookmarkManager { static func isLocked(bookmark: OCBookmark, presentAlertOn viewController: UIViewController? = nil, completion: ((_ isLocked: Bool) -> Void)? = nil) -> Bool { if self.lockedBookmarks.contains(bookmark) { if viewController != nil { - let alertController = UIAlertController(title: NSString(format: "'%@' is currently locked".localized as NSString, bookmark.shortName as NSString) as String, + let alertController = ThemedAlertController(title: NSString(format: "'%@' is currently locked".localized as NSString, bookmark.shortName as NSString) as String, message: NSString(format: "An operation is currently performed that prevents connecting to '%@'. Please try again later.".localized as NSString, bookmark.shortName as NSString) as String, preferredStyle: .alert) diff --git a/ownCloud/Settings/LogFilesViewController.swift b/ownCloud/Settings/LogFilesViewController.swift index 74bcab7e1..befc0f5d5 100644 --- a/ownCloud/Settings/LogFilesViewController.swift +++ b/ownCloud/Settings/LogFilesViewController.swift @@ -232,7 +232,7 @@ class LogFilesViewController : UITableViewController, Themeable { } @objc private func removeAllLogs() { - let alert = UIAlertController(with: "Delete all log files?".localized, + let alert = ThemedAlertController(with: "Delete all log files?".localized, message: "This action can't be undone.".localized, destructiveLabel: "Delete all".localized, preferredStyle: .alert, diff --git a/ownCloud/Settings/MediaUploadSettingsSection.swift b/ownCloud/Settings/MediaUploadSettingsSection.swift index c4b5e9a41..77b7306fb 100644 --- a/ownCloud/Settings/MediaUploadSettingsSection.swift +++ b/ownCloud/Settings/MediaUploadSettingsSection.swift @@ -252,7 +252,7 @@ class MediaUploadSettingsSection: SettingsSection { } else { self.userDefaults.resetInstantUploadConfiguration() OnMainThread { - let alertController = UIAlertController(with: "Instant upload disabled".localized, + let alertController = ThemedAlertController(with: "Instant upload disabled".localized, message: "Instant upload of media was disabled since configured account / folder was not found".localized) self.viewController?.present(alertController, animated: true, completion: nil) } @@ -321,11 +321,11 @@ class MediaUploadSettingsSection: SettingsSection { self?.userDefaults.instantUploadPath = nil // Proceed with upload path selection - self?.selectUploadPath(for: selectedBookmark, pushIn: navigationController, completion: { [weak navigationController] (success) in + self?.selectUploadPath(for: selectedBookmark, pushIn: navigationController, completion: { (success) in if !success && self?.userDefaults.instantUploadPath == nil { self?.userDefaults.resetInstantUploadConfiguration() } - navigationController?.dismiss(animated: true, completion: nil) + navigationController.dismiss(animated: true, completion: nil) self?.postSettingsChangedNotification() self?.updateDynamicUI() }) diff --git a/ownCloud/Settings/MoreSettingsSection.swift b/ownCloud/Settings/MoreSettingsSection.swift index 5e95d050a..c5c08f7f9 100644 --- a/ownCloud/Settings/MoreSettingsSection.swift +++ b/ownCloud/Settings/MoreSettingsSection.swift @@ -139,7 +139,7 @@ class MoreSettingsSection: SettingsSection { } private func openSFWebViewWithConfirmation(for url: URL) { - let alert = UIAlertController(title: "Do you want to open the following URL?".localized, + let alert = ThemedAlertController(title: "Do you want to open the following URL?".localized, message: url.absoluteString, preferredStyle: .alert) diff --git a/ownCloud/Settings/Passcode/PasscodeViewController.swift b/ownCloud/Settings/Passcode/PasscodeViewController.swift index 8b67e6658..cd6b9bd11 100644 --- a/ownCloud/Settings/Passcode/PasscodeViewController.swift +++ b/ownCloud/Settings/Passcode/PasscodeViewController.swift @@ -137,6 +137,8 @@ class PasscodeViewController: UIViewController, Themeable { self.screenBlurringEnabled = false super.init(nibName: "PasscodeViewController", bundle: nil) + + self.modalPresentationStyle = .fullScreen } required init?(coder aDecoder: NSCoder) { diff --git a/ownCloud/Settings/UserInterfaceSettingsSection.swift b/ownCloud/Settings/UserInterfaceSettingsSection.swift index 9f2bcf11b..d1bbd49aa 100644 --- a/ownCloud/Settings/UserInterfaceSettingsSection.swift +++ b/ownCloud/Settings/UserInterfaceSettingsSection.swift @@ -30,21 +30,15 @@ class UserInterfaceSettingsSection: SettingsSection { self.headerTitle = "User Interface".localized self.identifier = "ui-section" - var themeStylesByName : [[String:String]] = [] - - for themeStyle in ThemeCollectionStyle.allCases { - themeStylesByName.append([themeStyle.name : themeStyle.rawValue]) - } - themeRow = StaticTableViewRow(valueRowWithAction: { [weak self] (_, _) in self?.pushThemeStyleSelector() - }, title: "Theme".localized, value: ThemeStyle.preferredStyle.localizedName, accessoryType: .disclosureIndicator, identifier: "theme") + }, title: "Theme".localized, value: ThemeStyle.displayName, accessoryType: .disclosureIndicator, identifier: "theme") self.add(row: themeRow!) loggingRow = StaticTableViewRow(valueRowWithAction: { [weak self] (_, _) in self?.pushLogSettings() - }, title: "Logging".localized, value: OCLogger.logLevel.label, accessoryType: .disclosureIndicator, identifier: "logging") + }, title: "Logging".localized, value: OCLogger.logLevel.label, accessoryType: .disclosureIndicator, identifier: "logging") loggingNotificationObserverToken = NotificationCenter.default.addObserver(forName: LogSettingsViewController.logLevelChangedNotificationName, object: nil, queue: OperationQueue.main) { [weak loggingRow] (_) in loggingRow?.cell?.detailTextLabel?.text = OCLogger.logLevel.label @@ -61,29 +55,41 @@ class UserInterfaceSettingsSection: SettingsSection { func pushThemeStyleSelector() { let styleSelectorViewController = StaticTableViewController(style: .grouped) - let styleSelectorSection = StaticTableViewSection(headerTitle: "Theme".localized) - styleSelectorViewController.navigationItem.title = "Theme".localized + if let styleSelectorSection = styleSelectorViewController.sectionForIdentifier("theme-style-selection") { + styleSelectorViewController.removeSection(styleSelectorSection, animated: true) + } + let styleSelectorSection = StaticTableViewSection(headerTitle: "Theme".localized, footerTitle: nil, identifier: "theme-style-selection") + 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" + } + } for style in availableStyles { themeIdentifiersByName.append([style.localizedName : style.identifier ]) } styleSelectorSection.add(radioGroupWithArrayOfLabelValueDictionaries: themeIdentifiersByName, radioAction: { [weak themeRow] (row, _) in - if let styleIdentifier = row.value as? String, - let style = ThemeStyle.forIdentifier(styleIdentifier) { - ThemeStyle.preferredStyle = style - Theme.shared.switchThemeCollection(ThemeCollection(with: style)) - - themeRow?.cell?.detailTextLabel?.text = style.localizedName + if let styleIdentifier = row.value as? String, styleIdentifier == "com.owncloud.system" { + ThemeStyle.followSystemAppearance = true + themeRow?.cell?.detailTextLabel?.text = "System".localized + } else if let styleIdentifier = row.value as? String, + let style = ThemeStyle.forIdentifier(styleIdentifier), ThemeStyle.preferredStyle != style { + ThemeStyle.followSystemAppearance = false + ThemeStyle.preferredStyle = style + + themeRow?.cell?.detailTextLabel?.text = ThemeStyle.displayName } - }, groupIdentifier: "theme-id", selectedValue: ThemeStyle.preferredStyle.identifier) + }, groupIdentifier: "theme-id", selectedValue: selectedValue) } - - styleSelectorViewController.addSection(styleSelectorSection) + styleSelectorViewController.addSection(styleSelectorSection, animated: true) self.viewController?.navigationController?.pushViewController(styleSelectorViewController, animated: true) } diff --git a/ownCloud/Tasks/InstantMediaUploadTaskExtension.swift b/ownCloud/Tasks/InstantMediaUploadTaskExtension.swift index a403e603f..16f2fc4b8 100644 --- a/ownCloud/Tasks/InstantMediaUploadTaskExtension.swift +++ b/ownCloud/Tasks/InstantMediaUploadTaskExtension.swift @@ -200,7 +200,7 @@ class InstantMediaUploadTaskExtension : ScheduledTaskAction { private func showFeatureDisabledAlert() { OnMainThread { - let alertController = UIAlertController(with: "Instant upload disabled".localized, + let alertController = ThemedAlertController(with: "Instant upload disabled".localized, message: "Instant upload of media was disabled since configured account / folder was not found".localized) UIApplication.shared.delegate?.window??.rootViewController?.present(alertController, animated: true, completion: nil) } diff --git a/ownCloud/Theming/NSObject+ThemeApplication.swift b/ownCloud/Theming/NSObject+ThemeApplication.swift index b1fb7c8e9..bfa3dc143 100644 --- a/ownCloud/Theming/NSObject+ThemeApplication.swift +++ b/ownCloud/Theming/NSObject+ThemeApplication.swift @@ -52,6 +52,14 @@ enum ThemeItemState { } } +protocol ThemeableSectionHeader : class { + var sectionHeaderColor : UIColor? { get set } +} + +protocol ThemeableSectionFooter : class { + var sectionFooterColor : UIColor? { get set } +} + extension NSObject { func applyThemeCollection(_ collection: ThemeCollection, itemStyle: ThemeItemStyle = .defaultForItem, itemState: ThemeItemState = .normal) { if let themeButton = self as? ThemeButton { @@ -78,7 +86,7 @@ extension NSObject { if let navigationController = self as? UINavigationController { navigationController.navigationBar.applyThemeCollection(collection, itemStyle: itemStyle) - //navigationController.view.backgroundColor = collection.tableBackgroundColor + navigationController.view.backgroundColor = collection.tableBackgroundColor } if let navigationBar = self as? UINavigationBar { @@ -88,6 +96,13 @@ 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 + } } if let toolbar = self as? UIToolbar { @@ -101,8 +116,16 @@ extension NSObject { } if let tableView = self as? UITableView { - tableView.backgroundColor = collection.tableBackgroundColor + tableView.backgroundColor = tableView.style == .grouped ? collection.tableGroupBackgroundColor : collection.tableBackgroundColor tableView.separatorColor = collection.tableSeparatorColor + + if let themeableSectionHeaderTableView = tableView as? ThemeableSectionHeader { + themeableSectionHeaderTableView.sectionHeaderColor = collection.tableSectionHeaderColor + } + + if let themeableSectionFooterTableView = tableView as? ThemeableSectionFooter { + themeableSectionFooterTableView.sectionFooterColor = collection.tableSectionFooterColor + } } if let collectionView = self as? UICollectionView { @@ -114,6 +137,12 @@ extension NSObject { searchBar.tintColor = collection.tintColor searchBar.barStyle = collection.barStyle + + if #available(iOS 13, *) { + searchBar.searchTextField.textColor = collection.navigationBarColors.labelColor + // Ensure search bar icon color is correct + searchBar.overrideUserInterfaceStyle = collection.interfaceStyle.userInterfaceStyle + } } if let label = self as? UILabel { @@ -164,6 +193,7 @@ extension NSObject { if let cell = self as? UITableViewCell { cell.backgroundColor = collection.tableRowColors.backgroundColor + cell.tintColor = collection.tintColor if cell.selectionStyle != .none { if collection.tableRowHighlightColors.backgroundColor != nil { @@ -176,6 +206,10 @@ extension NSObject { cell.selectedBackgroundView = nil } } + + if #available(iOS 13, *) { + cell.overrideUserInterfaceStyle = collection.interfaceStyle.userInterfaceStyle + } } if let progressView = self as? UIProgressView { @@ -185,6 +219,50 @@ extension NSObject { if let segmentedControl = self as? UISegmentedControl { segmentedControl.tintColor = collection.navigationBarColors.tintColor + if #available(iOS 13, *) { + segmentedControl.setTitleTextAttributes([NSAttributedString.Key.foregroundColor : collection.navigationBarColors.labelColor], for: .normal) + segmentedControl.setTitleTextAttributes([NSAttributedString.Key.foregroundColor : collection.tintColor], for: .selected) + } + } + + if let visualEffectView = self as? UIVisualEffectView { + if #available(iOS 13, *) { + visualEffectView.overrideUserInterfaceStyle = collection.interfaceStyle.userInterfaceStyle + } + } + } +} + +extension UITableViewController : ThemeableSectionHeader, ThemeableSectionFooter { + var sectionHeaderColor: UIColor? { + get { + return self.value(forAnnotatedProperty: "sectionHeaderColor") as? UIColor + } + + set { + self.setValue(newValue, forAnnotatedProperty: "sectionHeaderColor") + } + } + + var sectionFooterColor: UIColor? { + get { + return self.value(forAnnotatedProperty: "sectionFooterColor") as? UIColor + } + + set { + self.setValue(newValue, forAnnotatedProperty: "sectionFooterColor") + } + } + + func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { + if let label = view as? UILabel, let sectionHeaderColor = sectionHeaderColor { + label.textColor = sectionHeaderColor + } + } + + func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) { + if let label = view as? UILabel, let sectionFooterColor = sectionFooterColor { + label.textColor = sectionFooterColor } } } diff --git a/ownCloud/Theming/Theme.swift b/ownCloud/Theming/Theme.swift index 4d2f1c761..3fc22fcde 100644 --- a/ownCloud/Theming/Theme.swift +++ b/ownCloud/Theming/Theme.swift @@ -65,7 +65,7 @@ class Theme: NSObject { OCExtensionManager.shared.addExtension(OCExtension.license(withIdentifier: "license.PocketSVG", bundleOf: Theme.self, title: "PocketSVG", resourceName: "PocketSVG", fileExtension: "LICENSE")) - return (sharedInstance) + return sharedInstance }() // MARK: - Client register / unregister diff --git a/ownCloud/Theming/ThemeCollection.swift b/ownCloud/Theming/ThemeCollection.swift index 062561874..0e6ffbeaf 100644 --- a/ownCloud/Theming/ThemeCollection.swift +++ b/ownCloud/Theming/ThemeCollection.swift @@ -73,9 +73,29 @@ enum ThemeCollectionStyle : String, CaseIterable { } } +enum ThemeCollectionInterfaceStyle : String, CaseIterable { + case dark + case light + case unspecified + + @available(iOS 12.0, *) + var userInterfaceStyle : UIUserInterfaceStyle { + switch self { + case .dark: return .dark + case .light: return .light + case .unspecified: return .unspecified + } + } +} + class ThemeCollection : NSObject { @objc var identifier : String = UUID().uuidString + // MARK: - Interface style + var interfaceStyle : ThemeCollectionInterfaceStyle + var keyboardAppearance : UIKeyboardAppearance + var backgroundBlurEffectStyle : UIBlurEffect.Style + // MARK: - Brand colors @objc var darkBrandColor: UIColor @objc var lightBrandColor: UIColor @@ -100,6 +120,8 @@ class ThemeCollection : NSObject { // MARK: - Table views @objc var tableBackgroundColor : UIColor @objc var tableGroupBackgroundColor : UIColor + @objc var tableSectionHeaderColor : UIColor? + @objc var tableSectionFooterColor : UIColor? @objc var tableSeparatorColor : UIColor? @objc var tableRowColors : ThemeColorCollection @objc var tableRowHighlightColors : ThemeColorCollection @@ -146,6 +168,10 @@ class ThemeCollection : NSObject { init(darkBrandColor darkColor: UIColor, lightBrandColor lightColor: UIColor, style: ThemeCollectionStyle = .dark) { var logoFillColor : UIColor? + self.interfaceStyle = .unspecified + self.keyboardAppearance = .default + self.backgroundBlurEffectStyle = .regular + self.darkBrandColor = darkColor self.lightBrandColor = lightColor @@ -180,8 +206,15 @@ class ThemeCollection : NSObject { // Table view self.tableBackgroundColor = UIColor.white - self.tableGroupBackgroundColor = UIColor.groupTableViewBackground - self.tableSeparatorColor = nil + if #available(iOS 13, *) { + self.tableGroupBackgroundColor = UIColor.groupTableViewBackground.resolvedColor(with: UITraitCollection(userInterfaceStyle: .light)) + self.tableSeparatorColor = UIColor.separator + } else { + self.tableGroupBackgroundColor = UIColor.groupTableViewBackground + self.tableSeparatorColor = UIColor.lightGray + } + self.tableSectionHeaderColor = UIColor.gray + self.tableSectionFooterColor = UIColor.gray self.tableRowBorderColor = UIColor.black.withAlphaComponent(0.1) self.tableRowColors = ThemeColorCollection( @@ -194,7 +227,7 @@ class ThemeCollection : NSObject { ) self.tableRowHighlightColors = ThemeColorCollection( - backgroundColor: nil, + backgroundColor: UIColor.white.darker(0.1), tintColor: nil, labelColor: UIColor.black, secondaryLabelColor: UIColor.gray, @@ -208,6 +241,11 @@ class ThemeCollection : NSObject { // Styles switch style { case .dark: + // Interface style + self.interfaceStyle = .dark + self.keyboardAppearance = .dark + self.backgroundBlurEffectStyle = .dark + // Bars self.navigationBarColors = self.darkBrandColors self.toolbarColors = self.darkBrandColors @@ -250,6 +288,11 @@ class ThemeCollection : NSObject { logoFillColor = UIColor.white case .light: + // Interface style + self.interfaceStyle = .light + self.keyboardAppearance = .light + self.backgroundBlurEffectStyle = .light + // Bars self.navigationBarColors = ThemeColorCollection( backgroundColor: UIColor.white.darker(0.05), @@ -263,7 +306,11 @@ class ThemeCollection : NSObject { self.toolbarColors = self.navigationBarColors // Bar styles - self.statusBarStyle = .default + if #available(iOS 13, *) { + self.statusBarStyle = .darkContent + } else { + self.statusBarStyle = .default + } self.barStyle = .default // Progress @@ -277,6 +324,11 @@ class ThemeCollection : NSObject { logoFillColor = UIColor.lightGray case .contrast: + // Interface style + self.interfaceStyle = .light + self.keyboardAppearance = .light + self.backgroundBlurEffectStyle = .light + // Bars self.navigationBarColors = self.darkBrandColors self.toolbarColors = self.darkBrandColors @@ -311,3 +363,17 @@ class ThemeCollection : NSObject { self.init(darkBrandColor: UIColor(hex: 0x1D293B), lightBrandColor: UIColor(hex: 0x468CC8)) } } + +@available(iOS 13.0, *) +extension ThemeCollection { + var navigationBarAppearance : UINavigationBarAppearance { + let appearance = UINavigationBarAppearance() + + appearance.configureWithOpaqueBackground() + appearance.backgroundColor = navigationBarColors.backgroundColor + appearance.titleTextAttributes = [ .foregroundColor : navigationBarColors.labelColor ] + appearance.largeTitleTextAttributes = [ .foregroundColor : navigationBarColors.labelColor ] + + return appearance + } +} diff --git a/ownCloud/Theming/ThemeStyle+DefaultStyles.swift b/ownCloud/Theming/ThemeStyle+DefaultStyles.swift index 556a02081..59119c378 100644 --- a/ownCloud/Theming/ThemeStyle+DefaultStyles.swift +++ b/ownCloud/Theming/ThemeStyle+DefaultStyles.swift @@ -27,7 +27,7 @@ extension UIColor { extension ThemeStyle { static public var ownCloudLight : ThemeStyle { - return (ThemeStyle(identifier: "com.owncloud.light", localizedName: "Light".localized, lightColor: .ownCloudLightColor, darkColor: .ownCloudDarkColor, themeStyle: .light)) + return (ThemeStyle(identifier: "com.owncloud.light", darkStyleIdentifier: "com.owncloud.dark", localizedName: "Light".localized, lightColor: .ownCloudLightColor, darkColor: .ownCloudDarkColor, themeStyle: .light)) } static public var ownCloudDark : ThemeStyle { @@ -35,6 +35,6 @@ extension ThemeStyle { } static public var ownCloudClassic : ThemeStyle { - return (ThemeStyle(identifier: "com.owncloud.classic", localizedName: "Classic".localized, lightColor: .ownCloudLightColor, darkColor: .ownCloudDarkColor, themeStyle: .contrast)) + return (ThemeStyle(identifier: "com.owncloud.classic", darkStyleIdentifier: "com.owncloud.dark", localizedName: "Classic".localized, lightColor: .ownCloudLightColor, darkColor: .ownCloudDarkColor, themeStyle: .contrast)) } } diff --git a/ownCloud/Theming/ThemeStyle+Extensions.swift b/ownCloud/Theming/ThemeStyle+Extensions.swift index ae57b137f..ce291e7f1 100644 --- a/ownCloud/Theming/ThemeStyle+Extensions.swift +++ b/ownCloud/Theming/ThemeStyle+Extensions.swift @@ -19,6 +19,17 @@ import Foundation import ownCloudSDK +@available(iOS 13.0, *) +extension UIUserInterfaceStyle { + func themeCollectionStyles() -> [ThemeCollectionStyle] { + if self == .dark { + return [.dark] + } + + return [.light, .contrast] + } +} + extension ThemeStyle { func themeStyleExtension(isDefault: Bool = false, isBranding: Bool = false) -> OCExtension { let features : [String:Any] = [ @@ -52,6 +63,8 @@ extension ThemeStyle { static var preferredStyle : ThemeStyle { set { UserDefaults.standard.setValue(newValue.identifier, forKey: "preferred-theme-style") + + considerAppearanceUpdate(animated: true) } get { @@ -69,6 +82,85 @@ extension ThemeStyle { } } + static var displayName : String { + if #available(iOS 13, *), ThemeStyle.followSystemAppearance { + return "System".localized + } + + return ThemeStyle.preferredStyle.localizedName + } + + @available(iOS 13.0, *) + static func userInterfaceStyle() -> UIUserInterfaceStyle? { + if let themeWindow = (UIApplication.shared.delegate as? AppDelegate)?.window { + return themeWindow.traitCollection.userInterfaceStyle + } + + return nil + } + + static func considerAppearanceUpdate(animated: Bool = false) { + let themeWindow : ThemeWindow? = (UIApplication.shared.delegate as? AppDelegate)?.window + 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 let applyStyle = applyStyle { + let themeCollection = ThemeCollection(with: applyStyle) + + if #available(iOS 13, *) { + if let themeWindowSubviews = themeWindow?.subviews { + for view in themeWindowSubviews { + view.overrideUserInterfaceStyle = themeCollection.interfaceStyle.userInterfaceStyle + } + } + } + + if animated { + Theme.shared.switchThemeCollection(themeCollection) + } else { + Theme.shared.activeCollection = themeCollection + } + } + } + + static var followSystemAppearance : Bool { + set { + UserDefaults.standard.setValue(newValue, forKey: "theme-style-follows-system-appearance") + + considerAppearanceUpdate() + } + + get { + var followSystemAppearance : Bool? + + if let themeStyleFollowsSystemAppearance = UserDefaults.standard.object(forKey: "theme-style-follows-system-appearance") as? Bool { + followSystemAppearance = themeStyleFollowsSystemAppearance + } + + if followSystemAppearance == nil { + followSystemAppearance = false + } + + return followSystemAppearance! + } + + } + static func forIdentifier(_ identifier: String) -> ThemeStyle? { let matchContext = OCExtensionContext(location: OCExtensionLocation(ofType: .themeStyle, identifier: OCExtensionLocationIdentifier(rawValue: identifier)), requirements: nil, preferences: nil) @@ -105,6 +197,18 @@ extension ThemeStyle { OCExtensionManager.shared.addExtension(ThemeStyle.ownCloudDark.themeStyleExtension(isDefault: true)) OCExtensionManager.shared.addExtension(ThemeStyle.ownCloudClassic.themeStyleExtension()) } + + static func availableStyles(for styles: [ThemeCollectionStyle]) -> [ThemeStyle]? { + let styles = ThemeStyle.availableStyles?.filter { (theme) -> Bool in + if styles.contains(theme.themeStyle) { + return true + } + + return false + } + + return styles + } } extension OCExtensionType { diff --git a/ownCloud/Theming/ThemeStyle.swift b/ownCloud/Theming/ThemeStyle.swift index b003730b6..da9c82630 100644 --- a/ownCloud/Theming/ThemeStyle.swift +++ b/ownCloud/Theming/ThemeStyle.swift @@ -26,10 +26,13 @@ class ThemeStyle : NSObject { var darkColor: UIColor var themeStyle: ThemeCollectionStyle + var darkStyleIdentifier: String? + var customizedColorsByPath : [String:String]? - init(identifier idtfr: String, localizedName name: String, lightColor lColor: UIColor, darkColor dColor: UIColor, themeStyle style: ThemeCollectionStyle = .light, customizedColorsByPath customizations: [String:String]? = nil) { + init(identifier idtfr: String, darkStyleIdentifier darkIdentifier: String? = nil, localizedName name: String, lightColor lColor: UIColor, darkColor dColor: UIColor, themeStyle style: ThemeCollectionStyle = .light, customizedColorsByPath customizations: [String:String]? = nil) { self.identifier = idtfr + self.darkStyleIdentifier = darkIdentifier self.localizedName = name self.lightColor = lColor self.darkColor = dColor diff --git a/ownCloud/Theming/UI/ThemeWindow.swift b/ownCloud/Theming/UI/ThemeWindow.swift new file mode 100644 index 000000000..8d8647dac --- /dev/null +++ b/ownCloud/Theming/UI/ThemeWindow.swift @@ -0,0 +1,31 @@ +// +// ThemeWindow.swift +// ownCloud +// +// Created by Felix Schwarz on 28.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 ThemeWindow : UIWindow { + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + if #available(iOS 13.0, *) { + if self.traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { + ThemeStyle.considerAppearanceUpdate() + } + } + } +} diff --git a/ownCloud/Theming/UI/ThemedAlertController.swift b/ownCloud/Theming/UI/ThemedAlertController.swift new file mode 100644 index 000000000..049f55fe5 --- /dev/null +++ b/ownCloud/Theming/UI/ThemedAlertController.swift @@ -0,0 +1,39 @@ +// +// ThemedAlertController.swift +// ownCloud +// +// Created by Felix Schwarz on 30.09.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 ThemedAlertController: UIAlertController, Themeable { + private var themeRegistered : Bool = false + + override func viewWillAppear(_ animated: Bool) { + Theme.shared.register(client: self, applyImmediately: true) + super.viewWillAppear(animated) + } + + func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { + if #available(iOS 13, *) { + self.overrideUserInterfaceStyle = collection.interfaceStyle.userInterfaceStyle + } + view.tintColor = collection.navigationBarColors.tintColor + } + + deinit { + Theme.shared.unregister(client: self) + } +} diff --git a/ownCloud/Tools/URL+Extensions.swift b/ownCloud/Tools/URL+Extensions.swift index 40b4cb5b8..972ce4653 100644 --- a/ownCloud/Tools/URL+Extensions.swift +++ b/ownCloud/Tools/URL+Extensions.swift @@ -12,8 +12,8 @@ import ownCloudSDK typealias UploadHandler = (OCItem?, Error?) -> Void extension URL { - func upload(with core:OCCore?, at rootItem:OCItem, importByCopy:Bool = false, placeholderHandler:UploadHandler? = nil, completionHandler:UploadHandler? = nil) -> Progress? { - let fileName = self.lastPathComponent + func upload(with core:OCCore?, at rootItem:OCItem, alternativeName:String? = nil, importByCopy:Bool = false, placeholderHandler:UploadHandler? = nil, completionHandler:UploadHandler? = nil) -> Progress? { + let fileName = alternativeName != nil ? alternativeName! : self.lastPathComponent let importOptions : [OCCoreOption : Any] = [OCCoreOption.importByCopying : importByCopy, OCCoreOption.automaticConflictResolutionNameStyle : OCCoreDuplicateNameStyle.bracketed.rawValue] var progress:Progress? diff --git a/ownCloud/Tools/VendorServices.swift b/ownCloud/Tools/VendorServices.swift index 99937db18..b34e451b4 100644 --- a/ownCloud/Tools/VendorServices.swift +++ b/ownCloud/Tools/VendorServices.swift @@ -113,7 +113,7 @@ class VendorServices : NSObject { viewController.present(mail, animated: true) } else { - let alert = UIAlertController(title: "Please configure an email account".localized, + let alert = ThemedAlertController(title: "Please configure an email account".localized, message: "You need to configure an email account first to be able to send emails.".localized, preferredStyle: .alert) diff --git a/ownCloud/UI Elements/Card Presentation Controller/CardPresentationController.swift b/ownCloud/UI Elements/Card Presentation Controller/CardPresentationController.swift index 9e199df3d..ac629fb30 100644 --- a/ownCloud/UI Elements/Card Presentation Controller/CardPresentationController.swift +++ b/ownCloud/UI Elements/Card Presentation Controller/CardPresentationController.swift @@ -37,7 +37,7 @@ protocol CardPresentationSizing : UIViewController { var fitsOnScreen : Bool { get set } } -final class CardPresentationController: UIPresentationController { +final class CardPresentationController: UIPresentationController, Themeable { // MARK: - Instance Variables. private var cardPosition: CardPosition = .open @@ -70,8 +70,11 @@ final class CardPresentationController: UIPresentationController { } private var windowFrame: CGRect { - let window = UIApplication.shared.delegate!.window!! - return window.bounds + if let window = UIApplication.shared.delegate?.window as? UIWindow { + return window.bounds + } else { + return UIScreen.main.bounds + } } override var frameOfPresentedViewInContainerView: CGRect { @@ -101,6 +104,17 @@ final class CardPresentationController: UIPresentationController { self.withHandle = withHandle self.dismissable = dismissable super.init(presentedViewController: presentedViewController, presenting: presentingViewController) + + Theme.shared.register(client: self, applyImmediately: true) + } + + deinit { + Theme.shared.unregister(client: self) + } + + func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { + overStretchView.backgroundColor = collection.tableGroupBackgroundColor + dragHandleView.backgroundColor = collection.tableSeparatorColor } private func offset(for position: CardPosition, translatedBy: CGFloat = 0, allowOverStretch: Bool = false) -> CGFloat { @@ -154,8 +168,6 @@ final class CardPresentationController: UIPresentationController { dragHandleView.widthAnchor.constraint(equalToConstant: 50), dragHandleView.heightAnchor.constraint(equalToConstant: 5) ]) - - dragHandleView.backgroundColor = .lightGray } } } @@ -178,7 +190,6 @@ final class CardPresentationController: UIPresentationController { if let presentedView = presentedView, let containerView = containerView { overStretchView.translatesAutoresizingMaskIntoConstraints = false - overStretchView.backgroundColor = Theme.shared.activeCollection.tableGroupBackgroundColor overStretchView.isUserInteractionEnabled = false containerView.insertSubview(overStretchView, aboveSubview: dimmingView) diff --git a/ownCloud/UI Elements/StaticTableViewController.swift b/ownCloud/UI Elements/StaticTableViewController.swift index c1f41d138..eb2c680c4 100644 --- a/ownCloud/UI Elements/StaticTableViewController.swift +++ b/ownCloud/UI Elements/StaticTableViewController.swift @@ -223,7 +223,6 @@ class StaticTableViewController: UITableViewController, Themeable { // MARK: - Theme support func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { - self.tableView.backgroundColor = collection.tableGroupBackgroundColor - self.tableView.separatorColor = collection.tableSeparatorColor + self.tableView.applyThemeCollection(collection) } } diff --git a/ownCloud/UI Elements/StaticTableViewRow.swift b/ownCloud/UI Elements/StaticTableViewRow.swift index f93085275..25c200996 100644 --- a/ownCloud/UI Elements/StaticTableViewRow.swift +++ b/ownCloud/UI Elements/StaticTableViewRow.swift @@ -116,7 +116,7 @@ class StaticTableViewRow : NSObject, UITextFieldDelegate { super.init() } - convenience init(rowWithAction: StaticTableViewRowAction?, title: String, subtitle: String? = nil, image: UIImage? = nil, imageWidth: CGFloat? = nil, alignment: NSTextAlignment = .left, accessoryType: UITableViewCell.AccessoryType = .none, identifier : String? = nil, accessoryView: UIView? = nil) { + convenience init(rowWithAction: StaticTableViewRowAction?, title: String, subtitle: String? = nil, image: UIImage? = nil, imageWidth: CGFloat? = nil, imageTintColorKey : String = "labelColor", alignment: NSTextAlignment = .left, accessoryType: UITableViewCell.AccessoryType = .none, identifier : String? = nil, accessoryView: UIView? = nil) { self.init() type = .row @@ -145,7 +145,7 @@ class StaticTableViewRow : NSObject, UITextFieldDelegate { } themeApplierToken = Theme.shared.add(applier: { [weak self] (_, themeCollection, _) in - self?.cell?.imageView?.tintColor = themeCollection.tableRowColors.labelColor + self?.cell?.imageView?.tintColor = themeCollection.tableRowColors.value(forKeyPath: imageTintColorKey) as? UIColor self?.cell?.accessoryView?.tintColor = themeCollection.tableRowColors.labelColor }) @@ -203,7 +203,7 @@ class StaticTableViewRow : NSObject, UITextFieldDelegate { } } - convenience init(rowWithAction: StaticTableViewRowAction?, title: String, alignment: NSTextAlignment = .left, image: UIImage? = nil, accessoryType: UITableViewCell.AccessoryType = UITableViewCell.AccessoryType.none, accessoryView: UIView?, identifier: String? = nil) { + convenience init(rowWithAction: StaticTableViewRowAction?, title: String, alignment: NSTextAlignment = .left, image: UIImage? = nil, imageTintColorKey : String = "labelColor", accessoryType: UITableViewCell.AccessoryType = UITableViewCell.AccessoryType.none, accessoryView: UIView?, identifier: String? = nil) { self.init() type = .row @@ -240,7 +240,7 @@ class StaticTableViewRow : NSObject, UITextFieldDelegate { } themeApplierToken = Theme.shared.add(applier: { [weak self] (_, themeCollection, _) in - self?.cell?.imageView?.tintColor = themeCollection.tableRowColors.labelColor + self?.cell?.imageView?.tintColor = themeCollection.tableRowColors.value(forKeyPath: imageTintColorKey) as? UIColor }) } @@ -405,6 +405,7 @@ class StaticTableViewRow : NSObject, UITextFieldDelegate { themeApplierToken = Theme.shared.add(applier: { [weak self] (_, themeCollection, _) in cellTextField.textColor = (self?.enabled == true) ? themeCollection.tableRowColors.labelColor : themeCollection.tableRowColors.secondaryLabelColor cellTextField.attributedPlaceholder = NSAttributedString(string: placeholderString, attributes: [.foregroundColor : themeCollection.tableRowColors.secondaryLabelColor]) + cellTextField.keyboardAppearance = themeCollection.keyboardAppearance }) cellTextField.accessibilityLabel = accessibilityLabel @@ -511,7 +512,7 @@ class StaticTableViewRow : NSObject, UITextFieldDelegate { // MARK: - Buttons - convenience init(buttonWithAction action: StaticTableViewRowAction?, title: String, style: StaticTableViewRowButtonStyle = .proceed, image: UIImage? = nil, imageWidth : CGFloat? = nil, alignment: NSTextAlignment = .center, identifier : String? = nil, accessoryView: UIView? = nil) { + convenience init(buttonWithAction action: StaticTableViewRowAction?, title: String, style: StaticTableViewRowButtonStyle = .proceed, image: UIImage? = nil, imageWidth : CGFloat? = nil, imageTintColorKey : String? = nil, alignment: NSTextAlignment = .center, identifier : String? = nil, accessoryView: UIView? = nil) { self.init() type = .button @@ -561,8 +562,9 @@ class StaticTableViewRow : NSObject, UITextFieldDelegate { } self?.cell?.textLabel?.textColor = textColor - self?.cell?.imageView?.tintColor = textColor + self?.cell?.imageView?.tintColor = (imageTintColorKey != nil) ? themeCollection.tableRowColors.value(forKeyPath: imageTintColorKey!) as? UIColor : textColor self?.cell?.accessoryView?.tintColor = textColor + self?.cell?.tintColor = themeCollection.tintColor if selectedTextColor != nil { diff --git a/ownCloud/UIKit Extensions/UIAlertViewController+SystemPermissions.swift b/ownCloud/UIKit Extensions/UIAlertViewController+SystemPermissions.swift index 03c7c2f4a..302adef60 100644 --- a/ownCloud/UIKit Extensions/UIAlertViewController+SystemPermissions.swift +++ b/ownCloud/UIKit Extensions/UIAlertViewController+SystemPermissions.swift @@ -21,7 +21,7 @@ import UIKit extension UIAlertController { class func alertControllerForPhotoLibraryAuthorizationInSettings() -> UIAlertController { - let alert = UIAlertController(title: "Missing permissions".localized, message: "This permission is needed to upload photos and videos from your photo library.".localized, preferredStyle: .alert) + let alert = ThemedAlertController(title: "Missing permissions".localized, message: "This permission is needed to upload photos and videos from your photo library.".localized, preferredStyle: .alert) let settingAction = UIAlertAction(title: "Settings".localized, style: .default, handler: { _ in UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil) diff --git a/ownCloud/UIKit Extensions/UISearchBar+Extension.swift b/ownCloud/UIKit Extensions/UISearchBar+Extension.swift deleted file mode 100644 index fce8fb3e1..000000000 --- a/ownCloud/UIKit Extensions/UISearchBar+Extension.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// UISearchBar+Extension.swift -// ownCloud -// -// Created by Matthias Hühne on 30.04.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 Foundation -import UIKit - -extension UISearchBar { - private var textField: UITextField? { - let subViews = self.subviews.flatMap { $0.subviews } - return (subViews.filter { $0 is UITextField }).first as? UITextField - } - - private var searchIcon: UIImage? { - let subViews = subviews.flatMap { $0.subviews } - return ((subViews.filter { $0 is UIImageView }).first as? UIImageView)?.image - } - - private var activityIndicator: UIActivityIndicatorView? { - return textField?.leftView?.subviews.compactMap { $0 as? UIActivityIndicatorView }.first - } - - var isLoading: Bool { - get { - return activityIndicator != nil - } set { - OnMainThread { - let _searchIcon = self.searchIcon - if newValue { - if self.activityIndicator == nil { - let _activityIndicator = UIActivityIndicatorView(style: Theme.shared.activeCollection.searchBarActivityIndicatorViewStyle) - _activityIndicator.startAnimating() - _activityIndicator.backgroundColor = UIColor.clear - self.setImage(UIImage(), for: .search, state: .normal) - self.textField?.leftView?.addSubview(_activityIndicator) - let leftViewSize = self.textField?.leftView?.frame.size ?? CGSize.zero - _activityIndicator.center = CGPoint(x: leftViewSize.width/2, y: leftViewSize.height/2) - } - } else { - self.setImage(_searchIcon, for: .search, state: .normal) - self.activityIndicator?.removeFromSuperview() - } - } - } - } -} diff --git a/ownCloud/UIKit Extensions/UIView+Animation.swift b/ownCloud/UIKit Extensions/UIView+Extension.swift similarity index 73% rename from ownCloud/UIKit Extensions/UIView+Animation.swift rename to ownCloud/UIKit Extensions/UIView+Extension.swift index 5ed0f9f62..791e0be89 100644 --- a/ownCloud/UIKit Extensions/UIView+Animation.swift +++ b/ownCloud/UIKit Extensions/UIView+Extension.swift @@ -1,5 +1,5 @@ // -// UIView+Animation.swift +// UIView+Extension.swift // ownCloud // // Created by Felix Schwarz on 07.05.18. @@ -19,6 +19,7 @@ import UIKit extension UIView { + // MARK: - Animation func shakeHorizontally(amplitude : CGFloat = 20, duration : CFTimeInterval = 0.5) { let animation : CAKeyframeAnimation = CAKeyframeAnimation(keyPath: "transform.translation.x") @@ -28,4 +29,19 @@ extension UIView { self.layer.add(animation, forKey: "shakeHorizontally") } + + // MARK: - View hierarchy + func findSubviewInTree(where filter: (UIView) -> Bool) -> UIView? { + for subview in subviews { + if filter(subview) { + return subview + } else { + if let foundSubview = subview.findSubviewInTree(where: filter) { + return foundSubview + } + } + } + + return nil + } } diff --git a/ownCloudAppFramework/Foundation Extensions/NSObject+AnnotatedProperties.h b/ownCloudAppFramework/Foundation Extensions/NSObject+AnnotatedProperties.h new file mode 100644 index 000000000..6ceecfa63 --- /dev/null +++ b/ownCloudAppFramework/Foundation Extensions/NSObject+AnnotatedProperties.h @@ -0,0 +1,30 @@ +// +// NSObject+AnnotatedProperties.h +// ownCloud +// +// Created by Felix Schwarz on 09.09.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 + +NS_ASSUME_NONNULL_BEGIN + +@interface NSObject (AnnotatedProperties) + +- (nullable id)valueForAnnotatedProperty:(NSString *)annotatedPropertyName; +- (void)setValue:(nullable id)value forAnnotatedProperty:(NSString *)annotatedPropertyName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudAppFramework/Foundation Extensions/NSObject+AnnotatedProperties.m b/ownCloudAppFramework/Foundation Extensions/NSObject+AnnotatedProperties.m new file mode 100644 index 000000000..5210721bf --- /dev/null +++ b/ownCloudAppFramework/Foundation Extensions/NSObject+AnnotatedProperties.m @@ -0,0 +1,56 @@ +// +// NSObject+AnnotatedProperties.m +// ownCloud +// +// Created by Felix Schwarz on 09.09.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 "NSObject+AnnotatedProperties.h" +#import + +static NSString *sOCAnnotatedPropertiesKey = @"AnnotatedProperties"; + +@implementation NSObject (AnnotatedProperties) + +- (NSMutableDictionary *)_annotatedProperties +{ + NSMutableDictionary *annotatedProperties = nil; + + if ((annotatedProperties = objc_getAssociatedObject(self, (__bridge void *)sOCAnnotatedPropertiesKey)) == nil) + { + annotatedProperties = [NSMutableDictionary new]; + + objc_setAssociatedObject(self, (__bridge void *)sOCAnnotatedPropertiesKey, annotatedProperties, OBJC_ASSOCIATION_RETAIN); + } + + return (annotatedProperties); +} + +- (nullable id)valueForAnnotatedProperty:(NSString *)customPropertyName +{ + @synchronized(self) + { + return ([[self _annotatedProperties] objectForKey:customPropertyName]); + } +} + +- (void)setValue:(nullable id)value forAnnotatedProperty:(NSString *)annotatedPropertyName +{ + @synchronized(self) + { + [self _annotatedProperties][annotatedPropertyName] = value; + } +} + +@end diff --git a/ownCloudAppFramework/Resources/Info.plist b/ownCloudAppFramework/Resources/Info.plist index a66133596..ee16060fb 100644 --- a/ownCloudAppFramework/Resources/Info.plist +++ b/ownCloudAppFramework/Resources/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.0 CFBundleVersion - 142 + 143 diff --git a/ownCloudAppFramework/ownCloudApp.h b/ownCloudAppFramework/ownCloudApp.h index e2a2afe5c..3dd4cd22a 100644 --- a/ownCloudAppFramework/ownCloudApp.h +++ b/ownCloudAppFramework/ownCloudApp.h @@ -28,4 +28,5 @@ FOUNDATION_EXPORT const unsigned char ownCloudAppVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import #import #import +#import #import diff --git a/ownCloudScreenshotsTests/Info.plist b/ownCloudScreenshotsTests/Info.plist index e5d49ce87..2ba035877 100644 --- a/ownCloudScreenshotsTests/Info.plist +++ b/ownCloudScreenshotsTests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.0 CFBundleVersion - 142 + 143 diff --git a/ownCloudTests/Info.plist b/ownCloudTests/Info.plist index e5d49ce87..2ba035877 100644 --- a/ownCloudTests/Info.plist +++ b/ownCloudTests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.0 CFBundleVersion - 142 + 143