diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index d50712529..c105f7ebf 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -64,9 +64,9 @@ body: - type: dropdown id: rageshake attributes: - label: Have you submitted a rageshake? + label: Will you send logs? description: | - Did you know that you can shake your phone to submit logs for this issue? Trigger the defect, then shake your phone and you will see a popup asking if you would like to open the bug report screen. Click YES, and describe the issue, mentioning that you have also filed a bug. Submit the report to send anonymous logs to the developers. + Did you know that you can shake your phone to submit logs for this issue? Trigger the defect, then shake your phone and you will see a popup asking if you would like to open the bug report screen. Click YES, and describe the issue, mentioning that you have also filed a bug (it's helpful if you can include a link to the bug). Send the report to submit anonymous logs to the developers. options: - 'Yes' - 'No' diff --git a/CHANGES.md b/CHANGES.md index 966ee4efa..53518bf12 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,35 @@ +## Changes in 1.6.8 (2021-11-17) + +🙌 Improvements + +- Upgrade MatrixKit version ([v0.16.10](https://github.com/matrix-org/matrix-ios-kit/releases/tag/v0.16.10)). +- Using mutable room list fetch sort options after chaning them to be a structure. ([#4384](https://github.com/vector-im/element-ios/issues/4384)) +- Share Extension: Remove the image compression prompt when the showMediaSizeSelection setting is disabled. ([#4815](https://github.com/vector-im/element-ios/issues/4815)) +- Replaced GrowingTextView with simpler, custom implementation. Cleaned up the RoomInputToolbar header. ([#4976](https://github.com/vector-im/element-ios/issues/4976)) +- Settings: Update about section footer text. ([#5090](https://github.com/vector-im/element-ios/issues/5090)) +- MXSession: Add logs to track if E2EE is enabled by default on the current HS. ([#5129](https://github.com/vector-im/element-ios/issues/5129)) + +🐛 Bugfixes + +- Fixed share extension and message forwarding room list accessory view icon. ([#5041](https://github.com/vector-im/element-ios/issues/5041)) +- Fixed message composer not following keyboard when swiping to dismiss. ([#5042](https://github.com/vector-im/element-ios/issues/5042)) +- RoomVC: Fix retain cycles that prevents `RoomViewController` to be deallocated. ([#5055](https://github.com/vector-im/element-ios/issues/5055)) +- Share Extension: Fix missing avatars and don't list spaces as rooms. ([#5057](https://github.com/vector-im/element-ios/issues/5057)) +- Fix retain cycles that prevents deallocation in several classes. ([#5058](https://github.com/vector-im/element-ios/issues/5058)) +- Fixed retain cycles between the user suggestion coordinator and the suggestion service, and in the suggestion service currentTextTrigger subject sink. ([#5063](https://github.com/vector-im/element-ios/issues/5063)) +- Ensure alerts with weak references are retained until they've been presented. ([#5071](https://github.com/vector-im/element-ios/issues/5071)) +- Message Composer: Ensure there is no text view when the user isn't allowed to send messages. ([#5079](https://github.com/vector-im/element-ios/issues/5079)) +- Home: Fix bug where favourited DM would be shown in both Favourites and People section. ([#5081](https://github.com/vector-im/element-ios/issues/5081)) +- Fix a crash when selected space is not home and a clear cache or logout is performed. ([#5082](https://github.com/vector-im/element-ios/issues/5082)) +- Room Previews: Fix room previews not loading. ([#5083](https://github.com/vector-im/element-ios/issues/5083)) +- Do not make the placeholder appearing when leaving a room on iPhone. ([#5084](https://github.com/vector-im/element-ios/issues/5084)) +- Fix room ordering when switching between Home and People/Rooms/Favourites. ([#5105](https://github.com/vector-im/element-ios/issues/5105)) + +Others + +- Improve wording around rageshakes in the defect issue template. ([#4987](https://github.com/vector-im/element-ios/issues/4987)) + + ## Changes in 1.6.6 (2021-10-21) ✨ Features diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index 6dab241e0..4b18e27d3 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -15,5 +15,5 @@ // // Version -MARKETING_VERSION = 1.6.6 -CURRENT_PROJECT_VERSION = 1.6.6 +MARKETING_VERSION = 1.6.8 +CURRENT_PROJECT_VERSION = 1.6.8 diff --git a/Podfile b/Podfile index dec1aaefb..9c04e8912 100644 --- a/Podfile +++ b/Podfile @@ -13,7 +13,7 @@ use_frameworks! # - `{ {kit spec hash} => {sdk spec hash}` to depend on specific pod options (:git => …, :podspec => …) for each repo. Used by Fastfile during CI # # Warning: our internal tooling depends on the name of this variable name, so be sure not to change it -$matrixKitVersion = '= 0.16.9' +$matrixKitVersion = '= 0.16.10' # $matrixKitVersion = :local # $matrixKitVersion = {'develop' => 'develop'} @@ -73,7 +73,6 @@ abstract_target 'RiotPods' do pod 'SideMenu', '~> 6.5' pod 'DSWaveformImage', '~> 6.1.1' pod 'ffmpeg-kit-ios-audio', '~> 4.5' - pod 'GrowingTextView', '~> 0.7.2' pod 'FLEX', '~> 4.5.0', :configurations => ['Debug'] diff --git a/Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/Contents.json b/Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/Contents.json index 35812152a..74d91f39d 100644 --- a/Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/Contents.json +++ b/Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" } } diff --git a/Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/Contents.json b/Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/Contents.json index a69d70fe6..a7cfab75b 100644 --- a/Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/Contents.json +++ b/Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" } } diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 844626362..0ff5e437d 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -1016,6 +1016,9 @@ Tap the + to start adding people."; // Share extension "share_extension_auth_prompt" = "Login in the main app to share content"; "share_extension_failed_to_encrypt" = "Failed to send. Check in the main app the encryption settings for this room"; +"share_extension_low_quality_video_title" = "Video will be sent in low quality"; +"share_extension_low_quality_video_message" = "Send in %@ for better quality, or send in low quality below."; +"share_extension_send_now" = "Send now"; // Room key request dialog "e2e_room_key_request_title" = "Encryption key request"; diff --git a/Riot/Categories/MXSession+Riot.m b/Riot/Categories/MXSession+Riot.m index 901c58406..069e13ca6 100644 --- a/Riot/Categories/MXSession+Riot.m +++ b/Riot/Categories/MXSession+Riot.m @@ -65,6 +65,7 @@ } else { + MXLogWarning(@"[MXSession] E2EE is disabled by default on this homeserver.\nWellknown content: %@", self.homeserverWellknown.JSONDictionary); success(NO); return [MXHTTPOperation new]; } diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 54ca11b35..f3622c608 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -4775,6 +4775,18 @@ public class VectorL10n: NSObject { public static var shareExtensionFailedToEncrypt: String { return VectorL10n.tr("Vector", "share_extension_failed_to_encrypt") } + /// Send in %@ for better quality, or send in low quality below. + public static func shareExtensionLowQualityVideoMessage(_ p1: String) -> String { + return VectorL10n.tr("Vector", "share_extension_low_quality_video_message", p1) + } + /// Video will be sent in low quality + public static var shareExtensionLowQualityVideoTitle: String { + return VectorL10n.tr("Vector", "share_extension_low_quality_video_title") + } + /// Send now + public static var shareExtensionSendNow: String { + return VectorL10n.tr("Vector", "share_extension_send_now") + } /// Feedback public static var sideMenuActionFeedback: String { return VectorL10n.tr("Vector", "side_menu_action_feedback") diff --git a/Riot/Managers/Analytics/DecryptionFailureTracker.h b/Riot/Managers/Analytics/DecryptionFailureTracker.h index ffefe0d99..b2dbbfc77 100644 --- a/Riot/Managers/Analytics/DecryptionFailureTracker.h +++ b/Riot/Managers/Analytics/DecryptionFailureTracker.h @@ -32,7 +32,7 @@ /** The delegate object to receive analytics events. */ -@property (nonatomic) id delegate; +@property (nonatomic, weak) id delegate; /** Report an event unable to decrypt. diff --git a/Riot/Managers/Settings/RiotSettings.swift b/Riot/Managers/Settings/RiotSettings.swift index 9e1ccd05b..193e05ce6 100644 --- a/Riot/Managers/Settings/RiotSettings.swift +++ b/Riot/Managers/Settings/RiotSettings.swift @@ -143,7 +143,7 @@ final class RiotSettings: NSObject { @UserDefault(key: "roomsAllowToJoinPublicRooms", defaultValue: BuildSettings.roomsAllowToJoinPublicRooms, storage: defaults) var roomsAllowToJoinPublicRooms - @UserDefault(key: UserDefaultsKeys.showAllRoomsInHomeSpace, defaultValue: false, storage: defaults) + @UserDefault(key: UserDefaultsKeys.showAllRoomsInHomeSpace, defaultValue: true, storage: defaults) var showAllRoomsInHomeSpace // MARK: - Room Screen diff --git a/Riot/Modules/Application/AppCoordinator.swift b/Riot/Modules/Application/AppCoordinator.swift index 32121a0f6..d60faf924 100755 --- a/Riot/Modules/Application/AppCoordinator.swift +++ b/Riot/Modules/Application/AppCoordinator.swift @@ -228,7 +228,7 @@ extension AppCoordinator: LegacyAppDelegateDelegate { } func legacyAppDelegateRestoreEmptyDetailsViewController(_ legacyAppDelegate: LegacyAppDelegate!) { - self.splitViewCoordinator?.restorePlaceholderDetails() + self.splitViewCoordinator?.resetDetails(animated: false) } func legacyAppDelegate(_ legacyAppDelegate: LegacyAppDelegate!, didAddMatrixSession session: MXSession!) { diff --git a/Riot/Modules/Common/Recents/RecentsViewController.h b/Riot/Modules/Common/Recents/RecentsViewController.h index b0c993f1e..6a475073a 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.h +++ b/Riot/Modules/Common/Recents/RecentsViewController.h @@ -40,7 +40,7 @@ FOUNDATION_EXPORT NSString *const RecentsViewControllerDataReadyNotification; /** Current alert (if any). */ - UIAlertController *currentAlert; + __weak UIAlertController *currentAlert; /** The list of the section headers currently displayed in the recents table. diff --git a/Riot/Modules/Common/Recents/RecentsViewController.m b/Riot/Modules/Common/Recents/RecentsViewController.m index f47f08a9d..c72a7a58b 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.m +++ b/Riot/Modules/Common/Recents/RecentsViewController.m @@ -50,13 +50,13 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro NSIndexPath* lastPotentialCellPath; // Observe UIApplicationDidEnterBackgroundNotification to cancel editing mode when app leaves the foreground state. - id UIApplicationDidEnterBackgroundNotificationObserver; + __weak id UIApplicationDidEnterBackgroundNotificationObserver; // Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar. - id kAppDelegateDidTapStatusBarNotificationObserver; + __weak id kAppDelegateDidTapStatusBarNotificationObserver; // Observe kMXNotificationCenterDidUpdateRules to update missed messages counts. - id kMXNotificationCenterDidUpdateRulesObserver; + __weak id kMXNotificationCenterDidUpdateRulesObserver; MXHTTPOperation *currentRequest; @@ -65,7 +65,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro UISearchBar *tableSearchBar; // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. - id kThemeServiceDidChangeThemeNotificationObserver; + __weak id kThemeServiceDidChangeThemeNotificationObserver; } @property (nonatomic, strong) CreateRoomCoordinatorBridgePresenter *createRoomCoordinatorBridgePresenter; @@ -156,11 +156,15 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro // Apply dragging settings self.enableDragging = _enableDragging; + MXWeakify(self); + // Observe UIApplicationDidEnterBackgroundNotification to refresh bubbles when app leaves the foreground state. UIApplicationDidEnterBackgroundNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + // Leave potential editing mode - [self cancelEditionMode:isRefreshPending]; + [self cancelEditionMode:self->isRefreshPending]; }]; @@ -170,6 +174,8 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro // Observe user interface theme change. kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + [self userInterfaceThemeDidChange]; }]; @@ -268,9 +274,13 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro [self.recentsTableView deselectRowAtIndexPath:indexPath animated:NO]; } + MXWeakify(self); + // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + [self scrollToTop:YES]; }]; @@ -278,6 +288,8 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro // Observe kMXNotificationCenterDidUpdateRules to refresh missed messages counts kMXNotificationCenterDidUpdateRulesObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXNotificationCenterDidUpdateRules object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + MXStrongifyAndReturnIfNil(self); + [self refreshRecentsTable]; }]; @@ -527,11 +539,13 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro [self->currentAlert dismissViewControllerAnimated:NO completion:nil]; - self->currentAlert = [UIAlertController alertControllerWithTitle:[MatrixKitL10n roomErrorJoinFailedTitle] message:msg preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *errorAlert = [UIAlertController alertControllerWithTitle:[MatrixKitL10n roomErrorJoinFailedTitle] + message:msg + preferredStyle:UIAlertControllerStyleAlert]; - [self->currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [errorAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); self->currentAlert = nil; @@ -541,7 +555,8 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro } }]]; - [self presentViewController:self->currentAlert animated:YES completion:nil]; + [self presentViewController:errorAlert animated:YES completion:nil]; + currentAlert = errorAlert; } #pragma mark - Sticky Headers @@ -1190,13 +1205,13 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro } // confirm leave - currentAlert = [UIAlertController alertControllerWithTitle:title - message:message - preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *leavePrompt = [UIAlertController alertControllerWithTitle:title + message:message + preferredStyle:UIAlertControllerStyleAlert]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] - style:UIAlertActionStyleCancel - handler:^(UIAlertAction * action) { + [leavePrompt addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -1206,8 +1221,8 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro }]]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n leave] - style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + [leavePrompt addAction:[UIAlertAction actionWithTitle:[VectorL10n leave] + style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { if (weakSelf) { @@ -1268,8 +1283,9 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro }]]; - [currentAlert mxk_setAccessibilityIdentifier:@"LeaveEditedRoomAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; + [leavePrompt mxk_setAccessibilityIdentifier:@"LeaveEditedRoomAlert"]; + [self presentViewController:leavePrompt animated:YES completion:nil]; + currentAlert = leavePrompt; } } @@ -1824,11 +1840,11 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro [currentAlert dismissViewControllerAnimated:NO completion:nil]; - currentAlert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; + UIAlertController *actionSheet = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomRecentsStartChatWith] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [actionSheet addAction:[UIAlertAction actionWithTitle:[VectorL10n roomRecentsStartChatWith] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -1840,9 +1856,9 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro }]]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomRecentsCreateEmptyRoom] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [actionSheet addAction:[UIAlertAction actionWithTitle:[VectorL10n roomRecentsCreateEmptyRoom] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -1854,9 +1870,9 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro }]]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomRecentsJoinRoom] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [actionSheet addAction:[UIAlertAction actionWithTitle:[VectorL10n roomRecentsJoinRoom] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -1870,10 +1886,9 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro if (self.mainSession.callManager.supportsPSTN) { - [currentAlert addAction:[UIAlertAction - actionWithTitle:[VectorL10n roomOpenDialpad] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [actionSheet addAction:[UIAlertAction actionWithTitle:[VectorL10n roomOpenDialpad] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -1886,9 +1901,9 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro }]]; } - [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] - style:UIAlertActionStyleCancel - handler:^(UIAlertAction * action) { + [actionSheet addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -1898,11 +1913,12 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro }]]; - [currentAlert popoverPresentationController].sourceView = plusButtonImageView; - [currentAlert popoverPresentationController].sourceRect = plusButtonImageView.bounds; + [actionSheet popoverPresentationController].sourceView = plusButtonImageView; + [actionSheet popoverPresentationController].sourceRect = plusButtonImageView.bounds; - [currentAlert mxk_setAccessibilityIdentifier:@"RecentsVCCreateRoomAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; + [actionSheet mxk_setAccessibilityIdentifier:@"RecentsVCCreateRoomAlert"]; + [self presentViewController:actionSheet animated:YES completion:nil]; + currentAlert = actionSheet; } - (void)openDialpad @@ -1996,7 +2012,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro } // Check whether the user has already joined the selected public room - if ([self.recentsDataSource.publicRoomsDirectoryDataSource.mxSession roomWithRoomId:publicRoom.roomId]) + if ([self.recentsDataSource.publicRoomsDirectoryDataSource.mxSession isJoinedOnRoom:publicRoom.roomId]) { // Open the public room [self showRoomWithRoomId:publicRoom.roomId diff --git a/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift b/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift index cda8f716d..6651fddba 100644 --- a/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift +++ b/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift @@ -20,7 +20,12 @@ import Foundation public class RecentsListService: NSObject, RecentsListServiceProtocol { private weak var session: MXSession? - public private(set) var mode: RecentsDataSourceMode + public private(set) var mode: RecentsDataSourceMode { + didSet { + refresh() + } + } + public private(set) var query: String? public private(set) var space: MXSpace? @@ -427,7 +432,7 @@ public class RecentsListService: NSObject, RecentsListServiceProtocol { private func updateDirectFetcher(_ fetcher: MXRoomListDataFetcher, for mode: RecentsDataSourceMode) { switch mode { case .home: - fetcher.fetchOptions.filterOptions.notDataTypes = [.invited, .lowPriority] + fetcher.fetchOptions.filterOptions.notDataTypes = [.invited, .favorited, .lowPriority] case .people: fetcher.fetchOptions.filterOptions.notDataTypes = [.lowPriority] default: @@ -440,7 +445,7 @@ public class RecentsListService: NSObject, RecentsListServiceProtocol { case .home: fetcher.fetchOptions.sortOptions = sortOptions case .favourites: - let newSortOptions = sortOptions + var newSortOptions = sortOptions newSortOptions.favoriteTag = true fetcher.fetchOptions.sortOptions = newSortOptions default: diff --git a/Riot/Modules/Common/Recents/Service/Mock/MockRoomSummary.swift b/Riot/Modules/Common/Recents/Service/Mock/MockRoomSummary.swift index 713932423..c211db025 100644 --- a/Riot/Modules/Common/Recents/Service/Mock/MockRoomSummary.swift +++ b/Riot/Modules/Common/Recents/Service/Mock/MockRoomSummary.swift @@ -60,6 +60,18 @@ public class MockRoomSummary: NSObject, MXRoomSummaryProtocol { public var highlightCount: UInt = 0 + public var hasAnyUnread: Bool { + return localUnreadEventCount > 0 + } + + public var hasAnyNotification: Bool { + return notificationCount > 0 + } + + public var hasAnyHighlight: Bool { + return highlightCount > 0 + } + public var isDirect: Bool { return isTyped(.direct) } diff --git a/Riot/Modules/Common/SegmentedViewController/SegmentedViewController.m b/Riot/Modules/Common/SegmentedViewController/SegmentedViewController.m index 8480205af..56b3caf91 100644 --- a/Riot/Modules/Common/SegmentedViewController/SegmentedViewController.m +++ b/Riot/Modules/Common/SegmentedViewController/SegmentedViewController.m @@ -50,7 +50,7 @@ NSLayoutConstraint *leftMarkerViewConstraint; // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. - id kThemeServiceDidChangeThemeNotificationObserver; + __weak id kThemeServiceDidChangeThemeNotificationObserver; } @end @@ -183,9 +183,13 @@ [self createSegmentedViews]; + MXWeakify(self); + // Observe user interface theme change. kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + [self userInterfaceThemeDidChange]; }]; diff --git a/Riot/Modules/Communities/GroupsViewController.m b/Riot/Modules/Communities/GroupsViewController.m index 916c344ee..d58d78c2f 100644 --- a/Riot/Modules/Communities/GroupsViewController.m +++ b/Riot/Modules/Communities/GroupsViewController.m @@ -27,10 +27,10 @@ BOOL isRefreshPending; // Observe UIApplicationDidEnterBackgroundNotification to cancel editing mode when app leaves the foreground state. - id UIApplicationDidEnterBackgroundNotificationObserver; + __weak id UIApplicationDidEnterBackgroundNotificationObserver; // Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar. - id kAppDelegateDidTapStatusBarNotificationObserver; + __weak id kAppDelegateDidTapStatusBarNotificationObserver; MXHTTPOperation *currentRequest; @@ -39,7 +39,7 @@ UISearchBar *tableSearchBar; // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. - id kThemeServiceDidChangeThemeNotificationObserver; + __weak id kThemeServiceDidChangeThemeNotificationObserver; } @end @@ -98,11 +98,15 @@ self.groupsTableView.estimatedSectionHeaderHeight = 30; self.groupsTableView.estimatedSectionFooterHeight = 0; + MXWeakify(self); + // Observe UIApplicationDidEnterBackgroundNotification to refresh bubbles when app leaves the foreground state. UIApplicationDidEnterBackgroundNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + // Leave potential editing mode - [self cancelEditionMode:isRefreshPending]; + [self cancelEditionMode:self->isRefreshPending]; }]; @@ -117,6 +121,8 @@ // Observe user interface theme change. kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + [self userInterfaceThemeDidChange]; }]; @@ -207,9 +213,13 @@ [self.groupsTableView deselectRowAtIndexPath:indexPath animated:NO]; } + MXWeakify(self); + // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + [self scrollToTop:YES]; }]; diff --git a/Riot/Modules/Communities/TabDetail/GroupDetailsCoordinator.swift b/Riot/Modules/Communities/TabDetail/GroupDetailsCoordinator.swift index 27c3d7df0..976c188ef 100644 --- a/Riot/Modules/Communities/TabDetail/GroupDetailsCoordinator.swift +++ b/Riot/Modules/Communities/TabDetail/GroupDetailsCoordinator.swift @@ -43,6 +43,10 @@ final class GroupDetailsCoordinator: GroupDetailsCoordinatorProtocol { self.groupDetailsViewController = groupDetailsViewController } + deinit { + groupDetailsViewController.destroy() + } + // MARK: - Public func start() { diff --git a/Riot/Modules/Contacts/ContactsTableViewController.h b/Riot/Modules/Contacts/ContactsTableViewController.h index 8b535495b..f1b3effaf 100644 --- a/Riot/Modules/Contacts/ContactsTableViewController.h +++ b/Riot/Modules/Contacts/ContactsTableViewController.h @@ -122,7 +122,7 @@ /** The delegate for the view controller. */ -@property (nonatomic) id contactsTableViewControllerDelegate; +@property (nonatomic, weak) id contactsTableViewControllerDelegate; @end diff --git a/Riot/Modules/Contacts/ContactsTableViewController.m b/Riot/Modules/Contacts/ContactsTableViewController.m index adbc1f9e9..f208f6f67 100644 --- a/Riot/Modules/Contacts/ContactsTableViewController.m +++ b/Riot/Modules/Contacts/ContactsTableViewController.m @@ -33,12 +33,12 @@ /** Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar. */ - id kAppDelegateDidTapStatusBarNotificationObserver; + __weak id kAppDelegateDidTapStatusBarNotificationObserver; /** Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. */ - id kThemeServiceDidChangeThemeNotificationObserver; + __weak id kThemeServiceDidChangeThemeNotificationObserver; } @property (nonatomic, strong) FindYourContactsFooterView *findYourContactsFooterView; @@ -102,9 +102,13 @@ self.contactsTableView.tableFooterView = [[UIView alloc] init]; self.contactsAreFilteredWithSearch = NO; + MXWeakify(self); + // Observe user interface theme change. kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + [self userInterfaceThemeDidChange]; }]; @@ -158,10 +162,14 @@ // Screen tracking [[Analytics sharedInstance] trackScreen:_screenName]; + + MXWeakify(self); // Observe kAppDelegateDidTapStatusBarNotification. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + [self.contactsTableView setContentOffset:CGPointMake(-self.contactsTableView.adjustedContentInset.left, -self.contactsTableView.adjustedContentInset.top) animated:YES]; }]; diff --git a/Riot/Modules/Contacts/Details/ContactDetailsViewController.m b/Riot/Modules/Contacts/Details/ContactDetailsViewController.m index e26c94ab1..dda2d7fa2 100644 --- a/Riot/Modules/Contacts/Details/ContactDetailsViewController.m +++ b/Riot/Modules/Contacts/Details/ContactDetailsViewController.m @@ -47,12 +47,12 @@ /** Observe UIApplicationWillChangeStatusBarOrientationNotification to hide/show bubbles bg. */ - id UIApplicationWillChangeStatusBarOrientationNotificationObserver; + __weak id UIApplicationWillChangeStatusBarOrientationNotificationObserver; /** The observer of the presence for matrix user. */ - id mxPresenceObserver; + __weak id mxPresenceObserver; /** List of the basic actions on this contact. @@ -79,7 +79,7 @@ /** Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. */ - id kThemeServiceDidChangeThemeNotificationObserver; + __weak id kThemeServiceDidChangeThemeNotificationObserver; /** The current visibility of the status bar in this view controller. @@ -182,9 +182,13 @@ self.bottomImageView.hidden = (orientation.integerValue == UIInterfaceOrientationLandscapeLeft || orientation.integerValue == UIInterfaceOrientationLandscapeRight); }]; + MXWeakify(self); + // Observe user interface theme change. kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + [self userInterfaceThemeDidChange]; }]; @@ -379,9 +383,13 @@ // Be warned when the thumbnail is updated [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onThumbnailUpdate:) name:kMXKContactThumbnailUpdateNotification object:nil]; + MXWeakify(self); + // Observe contact presence change mxPresenceObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKContactManagerMatrixUserPresenceChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + NSString* matrixId = self.firstMatrixId; if (matrixId && [matrixId isEqualToString:notif.object]) diff --git a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewController.swift b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewController.swift index e68aedb51..d89e60635 100644 --- a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewController.swift +++ b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewController.swift @@ -105,8 +105,8 @@ final class EnterNewRoomDetailsViewController: UIViewController { var section3: Section? if RiotSettings.shared.roomCreationScreenAllowEncryptionConfiguration { - let row_3_0 = Row(type: .withSwitch(isOn: viewModel.roomCreationParameters.isEncrypted, onValueChanged: { (theSwitch) in - self.viewModel.roomCreationParameters.isEncrypted = theSwitch.isOn + let row_3_0 = Row(type: .withSwitch(isOn: viewModel.roomCreationParameters.isEncrypted, onValueChanged: { [weak self] (theSwitch) in + self?.viewModel.roomCreationParameters.isEncrypted = theSwitch.isOn }), text: VectorL10n.createRoomEnableEncryption, accessoryType: .none) { // no-op } @@ -117,11 +117,20 @@ final class EnterNewRoomDetailsViewController: UIViewController { var section4: Section? if RiotSettings.shared.roomCreationScreenAllowRoomTypeConfiguration { - let row_4_0 = Row(type: .default, text: VectorL10n.createRoomTypePrivate, accessoryType: viewModel.roomCreationParameters.isPublic ? .none : .checkmark) { + let row_4_0 = Row(type: .default, text: VectorL10n.createRoomTypePrivate, accessoryType: viewModel.roomCreationParameters.isPublic ? .none : .checkmark) { [weak self] in + guard let self = self else { + return + } + self.viewModel.roomCreationParameters.isPublic = false self.updateSections() } - let row_4_1 = Row(type: .default, text: VectorL10n.createRoomTypePublic, accessoryType: viewModel.roomCreationParameters.isPublic ? .checkmark : .none) { + let row_4_1 = Row(type: .default, text: VectorL10n.createRoomTypePublic, accessoryType: viewModel.roomCreationParameters.isPublic ? .checkmark : .none) { [weak self] in + + guard let self = self else { + return + } + self.viewModel.roomCreationParameters.isPublic = true self.updateSections() // scroll bottom to show user new fields @@ -149,8 +158,8 @@ final class EnterNewRoomDetailsViewController: UIViewController { } if viewModel.roomCreationParameters.isPublic { - let row_5_0 = Row(type: .withSwitch(isOn: viewModel.roomCreationParameters.showInDirectory, onValueChanged: { (theSwitch) in - self.viewModel.roomCreationParameters.showInDirectory = theSwitch.isOn + let row_5_0 = Row(type: .withSwitch(isOn: viewModel.roomCreationParameters.showInDirectory, onValueChanged: { [weak self] (theSwitch) in + self?.viewModel.roomCreationParameters.showInDirectory = theSwitch.isOn }), text: VectorL10n.createRoomShowInDirectory, accessoryType: .none) { // no-op } @@ -389,7 +398,10 @@ extension EnterNewRoomDetailsViewController: UITableViewDataSource { cell.mxkLabel.text = row.text cell.mxkSwitch.isOn = isOn cell.mxkSwitch.removeTarget(nil, action: nil, for: .valueChanged) - cell.mxkSwitch.vc_addAction(for: .valueChanged) { + cell.mxkSwitch.vc_addAction(for: .valueChanged) { [weak cell] in + guard let cell = cell else { + return + } onValueChanged?(cell.mxkSwitch) } cell.mxkLabelLeadingConstraint.constant = cell.vc_separatorInset.left diff --git a/Riot/Modules/GlobalSearch/Rooms/DirectoryViewController.m b/Riot/Modules/GlobalSearch/Rooms/DirectoryViewController.m index 2f6e791d7..254831046 100644 --- a/Riot/Modules/GlobalSearch/Rooms/DirectoryViewController.m +++ b/Riot/Modules/GlobalSearch/Rooms/DirectoryViewController.m @@ -190,7 +190,7 @@ MXPublicRoom *publicRoom = [dataSource roomAtIndexPath:indexPath]; // Check whether the user has already joined the selected public room - if ([dataSource.mxSession roomWithRoomId:publicRoom.roomId]) + if ([dataSource.mxSession isJoinedOnRoom:publicRoom.roomId]) { // Open the public room. [self showRoomWithId:publicRoom.roomId inMatrixSession:dataSource.mxSession]; diff --git a/Riot/Modules/Integrations/Widgets/Jitsi/JitsiViewController.h b/Riot/Modules/Integrations/Widgets/Jitsi/JitsiViewController.h index 653608c47..2fb2b368f 100644 --- a/Riot/Modules/Integrations/Widgets/Jitsi/JitsiViewController.h +++ b/Riot/Modules/Integrations/Widgets/Jitsi/JitsiViewController.h @@ -81,7 +81,7 @@ /** The delegate for the view controller. */ -@property (nonatomic) id delegate; +@property (nonatomic, weak) id delegate; @end diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index 9817f503d..a83ba10da 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -44,7 +44,7 @@ const CGFloat kTypingCellHeight = 24; // Timer used to debounce cells refresh @property (nonatomic, strong) NSTimer *refreshCellsTimer; -@property (nonatomic, readonly) id roomDataSourceDelegate; +@property (nonatomic, weak, readonly) id roomDataSourceDelegate; @property(nonatomic, readwrite) RoomEncryptionTrustLevel encryptionTrustLevel; diff --git a/Riot/Modules/Room/Members/Detail/Views/RoomMemberTitleView.h b/Riot/Modules/Room/Members/Detail/Views/RoomMemberTitleView.h index 009b77e22..6111b6939 100644 --- a/Riot/Modules/Room/Members/Detail/Views/RoomMemberTitleView.h +++ b/Riot/Modules/Room/Members/Detail/Views/RoomMemberTitleView.h @@ -54,6 +54,6 @@ /** The delegate. */ -@property (nonatomic) id delegate; +@property (nonatomic, weak) id delegate; @end diff --git a/Riot/Modules/Room/Members/RoomParticipantsViewController.h b/Riot/Modules/Room/Members/RoomParticipantsViewController.h index c0b8dc3f8..757bb2fca 100644 --- a/Riot/Modules/Room/Members/RoomParticipantsViewController.h +++ b/Riot/Modules/Room/Members/RoomParticipantsViewController.h @@ -90,7 +90,7 @@ /** The delegate for the view controller. */ -@property (nonatomic) id delegate; +@property (nonatomic, weak) id delegate; /** Returns the `UINib` object initialized for a `RoomParticipantsViewController`. diff --git a/Riot/Modules/Room/RoomCoordinator.swift b/Riot/Modules/Room/RoomCoordinator.swift index dd3ca7d2f..3c707c668 100644 --- a/Riot/Modules/Room/RoomCoordinator.swift +++ b/Riot/Modules/Room/RoomCoordinator.swift @@ -77,6 +77,10 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol { super.init() } + + deinit { + roomViewController.destroy() + } // MARK: - Public @@ -90,9 +94,12 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol { self.roomViewController.delegate = self // Detect when view controller has been dismissed by gesture when presented modally (not in full screen). - self.roomViewController.presentationController?.delegate = self + // FIXME: Find a better way to manage modal dismiss. This makes the `roomViewController` to never be released + // self.roomViewController.presentationController?.delegate = self - if let eventId = self.selectedEventId { + if let previewData = self.parameters.previewData { + self.loadRoomPreview(withData: previewData, completion: completion) + } else if let eventId = self.selectedEventId { self.loadRoom(withId: self.parameters.roomId, and: eventId, completion: completion) } else { self.loadRoom(withId: self.parameters.roomId, completion: completion) @@ -178,6 +185,13 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol { completion?() } } + + private func loadRoomPreview(withData previewData: RoomPreviewData, completion: (() -> Void)?) { + + self.roomViewController.displayRoomPreview(previewData) + + completion?() + } } // MARK: - RoomIdentifiable diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift index f65c06071..ccfe76a7e 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift @@ -152,11 +152,10 @@ final class RoomInfoCoordinator: NSObject, RoomInfoCoordinatorType { case .search: MXKRoomDataSourceManager.sharedManager(forMatrixSession: session)?.roomDataSource(forRoom: self.room.roomId, create: false, onComplete: { (roomDataSource) in guard let dataSource = roomDataSource else { return } - let storyboard = UIStoryboard(name: "Main", bundle: nil) - if let search = storyboard.instantiateViewController(withIdentifier: "RoomSearch") as? RoomSearchViewController { - search.roomDataSource = dataSource - self.navigationRouter.push(search, animated: animated, popCompletion: nil) - } + let roomSearchViewController: RoomSearchViewController = RoomSearchViewController.instantiate() + roomSearchViewController.loadViewIfNeeded() + roomSearchViewController.roomDataSource = dataSource + self.navigationRouter.push(roomSearchViewController, animated: animated, popCompletion: nil) }) case .notifications: let coordinator = createRoomNotificationSettingsCoordinator() diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift index daa9b2947..3590b5526 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift @@ -50,8 +50,8 @@ final class RoomInfoListViewController: UIViewController { private lazy var basicInfoView: RoomInfoBasicView = { let view = RoomInfoBasicView.loadFromNib() - view.onTopicSizeChange = { _ in - self.view.setNeedsLayout() + view.onTopicSizeChange = { [weak self] _ in + self?.view.setNeedsLayout() } return view }() diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 5b4ad0c2b..d056b0267 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -144,7 +144,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; { // The preview header - PreviewRoomTitleView *previewHeader; + __weak PreviewRoomTitleView *previewHeader; // The customized room data source for Vector RoomDataSource *customizedRoomDataSource; @@ -156,7 +156,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; NSArray *currentTypingUsers; // Typing notifications listener. - id typingNotifListener; + __weak id typingNotifListener; // The position of the first touch down event stored in case of scrolling when the expanded header is visible. CGPoint startScrollingPoint; @@ -168,33 +168,33 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; UIView *missedDiscussionsDotView; // Potential encryption details view. - EncryptionInfoView *encryptionInfoView; + __weak EncryptionInfoView *encryptionInfoView; // The list of unknown devices that prevent outgoing messages from being sent MXUsersDevicesMap *unknownDevices; // Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar. - id kAppDelegateDidTapStatusBarNotificationObserver; + __weak id kAppDelegateDidTapStatusBarNotificationObserver; // Observe kAppDelegateNetworkStatusDidChangeNotification to handle network status change. - id kAppDelegateNetworkStatusDidChangeNotificationObserver; + __weak id kAppDelegateNetworkStatusDidChangeNotificationObserver; // Observers to manage MXSession state (and sync errors) - id kMXSessionStateDidChangeObserver; + __weak id kMXSessionStateDidChangeObserver; // Observers to manage ongoing conference call banner - id kMXCallStateDidChangeObserver; - id kMXCallManagerConferenceStartedObserver; - id kMXCallManagerConferenceFinishedObserver; + __weak id kMXCallStateDidChangeObserver; + __weak id kMXCallManagerConferenceStartedObserver; + __weak id kMXCallManagerConferenceFinishedObserver; // Observers to manage widgets - id kMXKWidgetManagerDidUpdateWidgetObserver; + __weak id kMXKWidgetManagerDidUpdateWidgetObserver; // Observer kMXRoomSummaryDidChangeNotification to keep updated the missed discussion count - id mxRoomSummaryDidChangeObserver; + __weak id mxRoomSummaryDidChangeObserver; // Observer for removing the re-request explanation/waiting dialog - id mxEventDidDecryptNotificationObserver; + __weak id mxEventDidDecryptNotificationObserver; // The table view cell in which the read marker is displayed (nil by default). MXKRoomBubbleTableViewCell *readMarkerTableViewCell; @@ -209,13 +209,13 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; NSArray *rightBarButtonItems; // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. - id kThemeServiceDidChangeThemeNotificationObserver; + __weak id kThemeServiceDidChangeThemeNotificationObserver; // Observe URL preview updates to refresh cells. - id URLPreviewDidUpdateNotificationObserver; + __weak id URLPreviewDidUpdateNotificationObserver; // Listener for `m.room.tombstone` event type - id tombstoneEventNotificationsListener; + __weak id tombstoneEventNotificationsListener; // Homeserver notices MXServerNotices *serverNotices; @@ -454,9 +454,13 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; self.jumpToLastUnreadLabel.text = [VectorL10n roomJumpToFirstUnread]; + MXWeakify(self); + // Observe user interface theme change. kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + [self userInterfaceThemeDidChange]; }]; @@ -587,9 +591,13 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [self listenTombstoneEventNotifications]; [self listenMXSessionStateChangeNotifications]; + MXWeakify(self); + // Observe kAppDelegateDidTapStatusBarNotification. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + [self setBubbleTableViewContentOffset:CGPointMake(-self.bubblesTableView.adjustedContentInset.left, -self.bubblesTableView.adjustedContentInset.top) animated:YES]; }]; @@ -661,9 +669,13 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [AppDelegate theDelegate].visibleRoomId = self.roomDataSource.roomId; } + MXWeakify(self); + // Observe network reachability kAppDelegateNetworkStatusDidChangeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateNetworkStatusDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + [self refreshActivitiesViewDisplay]; }]; @@ -673,6 +685,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; // Observe missed notifications mxRoomSummaryDidChangeObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXRoomSummaryDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + MXRoomSummary *roomSummary = notif.object; if ([roomSummary.roomId isEqualToString:self.roomDataSource.roomId]) @@ -1084,16 +1098,12 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; if (self.roomDataSource) { - // Restore tool bar view and room activities view if none - if (!self.inputToolbarView) - { - [self updateRoomInputToolbarViewClassIfNeeded]; - - [self refreshRoomInputToolbar]; - - self.inputToolbarView.hidden = (self.roomDataSource.state != MXKDataSourceStateReady); - } + // Update the input toolbar class and update the layout + [self updateRoomInputToolbarViewClassIfNeeded]; + self.inputToolbarView.hidden = (self.roomDataSource.state != MXKDataSourceStateReady); + + // Restore room activities view if none if (!self.activitiesView) { // And the extra area @@ -1178,6 +1188,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } [self updateInputToolBarViewHeight]; + [self refreshRoomInputToolbar]; } } @@ -1398,8 +1409,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } if (URLPreviewDidUpdateNotificationObserver) { - [NSNotificationCenter.defaultCenter removeObserver:URLPreviewDidUpdateNotificationObserver]; - URLPreviewDidUpdateNotificationObserver = nil; + [NSNotificationCenter.defaultCenter removeObserver:URLPreviewDidUpdateNotificationObserver]; } [self removeCallNotificationsListeners]; @@ -1555,8 +1565,12 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; - (void)registerURLPreviewNotifications { + MXWeakify(self); + URLPreviewDidUpdateNotificationObserver = [NSNotificationCenter.defaultCenter addObserverForName:URLPreviewDidUpdateNotification object:nil queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull notification) { + MXStrongifyAndReturnIfNil(self); + // Ensure this is the correct room if (![(NSString*)notification.userInfo[@"roomId"] isEqualToString:self.roomDataSource.roomId]) { @@ -2045,20 +2059,22 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [VectorL10n widgetStickerPickerNoStickerpacksAlert], [VectorL10n widgetStickerPickerNoStickerpacksAlertAddNow]]; - currentAlert = [UIAlertController alertControllerWithTitle:nil message:alertMessage preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *installPrompt = [UIAlertController alertControllerWithTitle:nil + message:alertMessage + preferredStyle:UIAlertControllerStyleAlert]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n no] - style:UIAlertActionStyleCancel - handler:^(UIAlertAction * action) + [installPrompt addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n no] + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); self->currentAlert = nil; }]]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n yes] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) + [installPrompt addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n yes] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); self->currentAlert = nil; @@ -2073,8 +2089,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [self presentViewController:modularVC animated:NO completion:nil]; }]]; - [currentAlert mxk_setAccessibilityIdentifier:@"RoomVCStickerPickerAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; + [installPrompt mxk_setAccessibilityIdentifier:@"RoomVCStickerPickerAlert"]; + [self presentViewController:installPrompt animated:YES completion:nil]; + currentAlert = installPrompt; } } @@ -2240,6 +2257,12 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } UIViewController *suggestionsViewController = self.userSuggestionCoordinator.toPresentable; + + if (!suggestionsViewController) + { + return; + } + [suggestionsViewController.view setTranslatesAutoresizingMaskIntoConstraints:NO]; [self addChildViewController:suggestionsViewController]; @@ -2336,11 +2359,14 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; if (isVisible) { - previewHeader = [PreviewRoomTitleView roomTitleView]; + PreviewRoomTitleView *previewHeader = [PreviewRoomTitleView roomTitleView]; previewHeader.delegate = self; previewHeader.tapGestureDelegate = self; previewHeader.translatesAutoresizingMaskIntoConstraints = NO; [self.previewHeaderContainer addSubview:previewHeader]; + + self->previewHeader = previewHeader; + // Force preview header in full width NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:previewHeader attribute:NSLayoutAttributeLeading @@ -3119,14 +3145,14 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } __weak __typeof(self) weakSelf = self; - currentAlert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; + UIAlertController *actionsMenu = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; // Add actions for a failed event if (selectedEvent.sentState == MXEventSentStateFailed) { - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n retry] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n retry] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3140,9 +3166,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionDelete] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionDelete] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3177,9 +3203,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; selectedEvent.sentState == MXEventSentStateEncrypting || selectedEvent.sentState == MXEventSentStateSending) { - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionCancelSend] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) + [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionCancelSend] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3197,9 +3223,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; } - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionForward] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionForward] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { self.shareManager = [[ShareManager alloc] initWithShareItemProvider:[[SimpleShareItemProvider alloc] initWithTextMessage:selectedComponent.textMessage] type:ShareManagerTypeForward]; @@ -3216,9 +3242,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; if (!isJitsiCallEvent) { - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionQuote] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionQuote] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3238,9 +3264,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; if (!isJitsiCallEvent && BuildSettings.messageDetailsAllowShare) { - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionShare] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionShare] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3272,9 +3298,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; attachment.type == MXKAttachmentTypeVideo || attachment.type == MXKAttachmentTypeVoiceMessage) { - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionForward] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionForward] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { self.shareManager = [[ShareManager alloc] initWithShareItemProvider:[[SimpleShareItemProvider alloc] initWithAttachment:attachment] type:ShareManagerTypeForward]; @@ -3294,9 +3320,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; { if (attachment.type == MXKAttachmentTypeImage || attachment.type == MXKAttachmentTypeVideo) { - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionSave] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionSave] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3339,9 +3365,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; NSString *uploadId = roomBubbleTableViewCell.bubbleData.attachment.contentURL; if ([MXMediaManager existingUploaderWithId:uploadId]) { - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionCancelSend] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionCancelSend] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { // Get again the loader MXMediaLoader *loader = [MXMediaManager existingUploaderWithId:uploadId]; @@ -3377,9 +3403,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; { if (BuildSettings.messageDetailsAllowShare) { - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionShare] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionShare] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3427,9 +3453,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; NSString *downloadId = roomBubbleTableViewCell.bubbleData.attachment.downloadId; if ([MXMediaManager existingDownloaderWithIdentifier:downloadId]) { - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionCancelDownload] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionCancelDownload] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3455,9 +3481,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; // because it breaks everything if (selectedEvent.eventType != MXEventTypeRoomEncryption) { - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionRedact] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionRedact] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3489,9 +3515,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; if (BuildSettings.messageDetailsAllowPermalink) { - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionPermalink] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionPermalink] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3518,9 +3544,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; // Add reaction history if event contains reactions if (roomBubbleTableViewCell.bubbleData.reactions[selectedEvent.eventId].aggregatedReactionsWithNonZeroCount) { - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionReactionHistory] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionReactionHistory] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { [self cancelEventSelection]; @@ -3531,9 +3557,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; if (BuildSettings.messageDetailsAllowViewSource) { - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionViewSource] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionViewSource] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3551,9 +3577,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; // Add "View Decrypted Source" for e2ee event we can decrypt if (selectedEvent.isEncrypted && selectedEvent.clearEvent) { - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionViewDecryptedSource] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionViewDecryptedSource] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3571,9 +3597,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; if (![selectedEvent.sender isEqualToString:self.mainSession.myUser.userId] && RiotSettings.shared.roomContextualMenuShowReportContentOption) { - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionReport] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionReport] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3582,15 +3608,15 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [self cancelEventSelection]; // Prompt user to enter a description of the problem content. - self->currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n roomEventActionReportPromptReason] message:nil preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *reportReasonAlert = [UIAlertController alertControllerWithTitle:[VectorL10n roomEventActionReportPromptReason] message:nil preferredStyle:UIAlertControllerStyleAlert]; - [self->currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + [reportReasonAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { textField.secureTextEntry = NO; textField.placeholder = nil; textField.keyboardType = UIKeyboardTypeDefault; }]; - [self->currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + [reportReasonAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3606,9 +3632,11 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [self stopActivityIndicator]; // Prompt user to ignore content from this user - self->currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n roomEventActionReportPromptIgnoreUser] message:nil preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *ignoreUserAlert = [UIAlertController alertControllerWithTitle:[VectorL10n roomEventActionReportPromptIgnoreUser] + message:nil + preferredStyle:UIAlertControllerStyleAlert]; - [self->currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n yes] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + [ignoreUserAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n yes] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3637,7 +3665,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; - [self->currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n no] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + [ignoreUserAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n no] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3647,7 +3675,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; - [self presentViewController:self->currentAlert animated:YES completion:nil]; + [self presentViewController:ignoreUserAlert animated:YES completion:nil]; + self->currentAlert = ignoreUserAlert; } failure:^(NSError *error) { @@ -3663,7 +3692,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; - [self->currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { + [reportReasonAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3673,7 +3702,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; - [self presentViewController:self->currentAlert animated:YES completion:nil]; + [self presentViewController:reportReasonAlert animated:YES completion:nil]; + self->currentAlert = reportReasonAlert; } }]]; @@ -3681,9 +3711,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; if (!isJitsiCallEvent && self.roomDataSource.room.summary.isEncrypted) { - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionViewEncryption] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionViewEncryption] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3698,9 +3728,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } } - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] - style:UIAlertActionStyleCancel - handler:^(UIAlertAction * action) { + [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3711,20 +3741,17 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; // Do not display empty action sheet - if (currentAlert.actions.count > 1) + if (actionsMenu.actions.count > 1) { NSInteger bubbleComponentIndex = [roomBubbleTableViewCell.bubbleData bubbleComponentIndexForEventId:selectedEvent.eventId]; CGRect sourceRect = [roomBubbleTableViewCell componentFrameInContentViewForIndex:bubbleComponentIndex]; - [currentAlert mxk_setAccessibilityIdentifier:@"RoomVCEventMenuAlert"]; - [currentAlert popoverPresentationController].sourceView = roomBubbleTableViewCell; - [currentAlert popoverPresentationController].sourceRect = sourceRect; - [self presentViewController:currentAlert animated:animated completion:nil]; - } - else - { - currentAlert = nil; + [actionsMenu mxk_setAccessibilityIdentifier:@"RoomVCEventMenuAlert"]; + [actionsMenu popoverPresentationController].sourceView = roomBubbleTableViewCell; + [actionsMenu popoverPresentationController].sourceRect = sourceRect; + [self presentViewController:actionsMenu animated:animated completion:nil]; + currentAlert = actionsMenu; } } @@ -4097,14 +4124,14 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; - (void)showVoiceCallActionSheet { // Ask the user the kind of the call: voice or dialpad? - currentAlert = [UIAlertController alertControllerWithTitle:nil - message:nil - preferredStyle:UIAlertControllerStyleActionSheet]; + UIAlertController *callActionSheet = [UIAlertController alertControllerWithTitle:nil + message:nil + preferredStyle:UIAlertControllerStyleActionSheet]; __weak typeof(self) weakSelf = self; - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomPlaceVoiceCall] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [callActionSheet addAction:[UIAlertAction actionWithTitle:[VectorL10n roomPlaceVoiceCall] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -4116,9 +4143,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomOpenDialpad] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [callActionSheet addAction:[UIAlertAction actionWithTitle:[VectorL10n roomOpenDialpad] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -4130,9 +4157,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] - style:UIAlertActionStyleCancel - handler:^(UIAlertAction * action) { + [callActionSheet addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -4142,9 +4169,10 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; - [currentAlert popoverPresentationController].barButtonItem = self.navigationItem.rightBarButtonItems.firstObject; - [currentAlert popoverPresentationController].permittedArrowDirections = UIPopoverArrowDirectionUp; - [self presentViewController:currentAlert animated:YES completion:nil]; + [callActionSheet popoverPresentationController].barButtonItem = self.navigationItem.rightBarButtonItems.firstObject; + [callActionSheet popoverPresentationController].permittedArrowDirections = UIPopoverArrowDirectionUp; + [self presentViewController:callActionSheet animated:YES completion:nil]; + currentAlert = callActionSheet; } - (void)placeCallWithVideo2:(BOOL)video @@ -4196,20 +4224,21 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; MXWeakify(self); [currentAlert dismissViewControllerAnimated:NO completion:nil]; - currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n roomNoPrivilegesToCreateGroupCall] - message:nil - preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *unprivilegedAlert = [UIAlertController alertControllerWithTitle:[VectorL10n roomNoPrivilegesToCreateGroupCall] + message:nil + preferredStyle:UIAlertControllerStyleAlert]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) + [unprivilegedAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); self->currentAlert = nil; }]]; - [currentAlert mxk_setAccessibilityIdentifier:@"RoomVCCallAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; + [unprivilegedAlert mxk_setAccessibilityIdentifier:@"RoomVCCallAlert"]; + [self presentViewController:unprivilegedAlert animated:YES completion:nil]; + currentAlert = unprivilegedAlert; } } } @@ -4768,10 +4797,14 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; - (void)listenCallNotifications { + MXWeakify(self); + kMXCallStateDidChangeObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXCallStateDidChange object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + MXCall *call = notif.object; - if ([call.room.roomId isEqualToString:customizedRoomDataSource.roomId]) + if ([call.room.roomId isEqualToString:self->customizedRoomDataSource.roomId]) { [self refreshActivitiesViewDisplay]; [self refreshRoomInputToolbar]; @@ -4779,16 +4812,20 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]; kMXCallManagerConferenceStartedObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXCallManagerConferenceStarted object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + NSString *roomId = notif.object; - if ([roomId isEqualToString:customizedRoomDataSource.roomId]) + if ([roomId isEqualToString:self->customizedRoomDataSource.roomId]) { [self refreshActivitiesViewDisplay]; } }]; kMXCallManagerConferenceFinishedObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXCallManagerConferenceFinished object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + NSString *roomId = notif.object; - if ([roomId isEqualToString:customizedRoomDataSource.roomId]) + if ([roomId isEqualToString:self->customizedRoomDataSource.roomId]) { [self refreshActivitiesViewDisplay]; [self refreshRoomInputToolbar]; @@ -5129,6 +5166,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [VectorL10n roomUnsentMessagesUnknownDevicesNotification] : [VectorL10n roomUnsentMessagesNotification]; + MXWeakify(self); RoomActivitiesView *roomActivitiesView = (RoomActivitiesView*) self.activitiesView; self.activitiesViewExpanded = YES; [roomActivitiesView displayUnsentMessagesNotification:notification withResendLink:^{ @@ -5140,57 +5178,53 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [self cancelAllUnsentMessages]; } andIconTapGesture:^{ + MXStrongifyAndReturnIfNil(self); - if (currentAlert) + if (self->currentAlert) { - [currentAlert dismissViewControllerAnimated:NO completion:nil]; + [self->currentAlert dismissViewControllerAnimated:NO completion:nil]; } - __weak __typeof(self) weakSelf = self; - currentAlert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; + MXWeakify(self); + UIAlertController *resendAlert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomResendUnsentMessages] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [resendAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomResendUnsentMessages] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { - if (weakSelf) - { - typeof(self) self = weakSelf; - [self resendAllUnsentMessages]; - self->currentAlert = nil; - } + MXStrongifyAndReturnIfNil(self); + + [self resendAllUnsentMessages]; + self->currentAlert = nil; }]]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomDeleteUnsentMessages] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [resendAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomDeleteUnsentMessages] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { - if (weakSelf) - { - typeof(self) self = weakSelf; - [self cancelAllUnsentMessages]; - self->currentAlert = nil; - } + MXStrongifyAndReturnIfNil(self); + + [self cancelAllUnsentMessages]; + self->currentAlert = nil; }]]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] - style:UIAlertActionStyleCancel - handler:^(UIAlertAction * action) { + [resendAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) { - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - } + MXStrongifyAndReturnIfNil(self); + + self->currentAlert = nil; }]]; - [currentAlert mxk_setAccessibilityIdentifier:@"RoomVCUnsentMessagesMenuAlert"]; - [currentAlert popoverPresentationController].sourceView = roomActivitiesView; - [currentAlert popoverPresentationController].sourceRect = roomActivitiesView.bounds; - [self presentViewController:currentAlert animated:YES completion:nil]; + [resendAlert mxk_setAccessibilityIdentifier:@"RoomVCUnsentMessagesMenuAlert"]; + [resendAlert popoverPresentationController].sourceView = roomActivitiesView; + [resendAlert popoverPresentationController].sourceRect = roomActivitiesView.bounds; + [self presentViewController:resendAlert animated:YES completion:nil]; + self->currentAlert = resendAlert; }]; } @@ -5230,13 +5264,13 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } } - currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n unknownDevicesAlertTitle] - message:[VectorL10n unknownDevicesAlert] - preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *unknownDevicesAlert = [UIAlertController alertControllerWithTitle:[VectorL10n unknownDevicesAlertTitle] + message:[VectorL10n unknownDevicesAlert] + preferredStyle:UIAlertControllerStyleAlert]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n unknownDevicesVerify] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [unknownDevicesAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n unknownDevicesVerify] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -5248,9 +5282,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n unknownDevicesSendAnyway] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [unknownDevicesAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n unknownDevicesSendAnyway] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -5271,8 +5305,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; - [currentAlert mxk_setAccessibilityIdentifier:@"RoomVCUnknownDevicesAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; + [unknownDevicesAlert mxk_setAccessibilityIdentifier:@"RoomVCUnknownDevicesAlert"]; + [self presentViewController:unknownDevicesAlert animated:YES completion:nil]; + currentAlert = unknownDevicesAlert; } } @@ -5334,15 +5369,17 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; - (void)cancelAllUnsentMessages { - currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n roomUnsentMessagesCancelTitle] message:[VectorL10n roomUnsentMessagesCancelMessage] preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *cancelAlert = [UIAlertController alertControllerWithTitle:[VectorL10n roomUnsentMessagesCancelTitle] + message:[VectorL10n roomUnsentMessagesCancelMessage] + preferredStyle:UIAlertControllerStyleAlert]; MXWeakify(self); - [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { + [cancelAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); self->currentAlert = nil; }]]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n delete] style:UIAlertActionStyleDestructive handler:^(UIAlertAction * action) { + [cancelAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n delete] style:UIAlertActionStyleDestructive handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); // Remove unsent event ids for (NSUInteger index = 0; index < self.roomDataSource.room.outgoingMessages.count;) @@ -5359,9 +5396,11 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } [self refreshActivitiesViewDisplay]; + self->currentAlert = nil; }]]; - [self presentViewController:currentAlert animated:YES completion:nil]; + [self presentViewController:cancelAlert animated:YES completion:nil]; + currentAlert = cancelAlert; } # pragma mark - Encryption Information view @@ -5373,7 +5412,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; // Remove potential existing subviews [self dismissTemporarySubViews]; - encryptionInfoView = [[EncryptionInfoView alloc] initWithEvent:event andMatrixSession:self.roomDataSource.mxSession]; + EncryptionInfoView *encryptionInfoView = [[EncryptionInfoView alloc] initWithEvent:event andMatrixSession:self.roomDataSource.mxSession]; // Add shadow on added view encryptionInfoView.layer.cornerRadius = 5; @@ -5383,6 +5422,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; // Add the view and define edge constraints [self.view addSubview:encryptionInfoView]; + self->encryptionInfoView = encryptionInfoView; + [self.view addConstraint:[NSLayoutConstraint constraintWithItem:encryptionInfoView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual @@ -5588,11 +5629,11 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; // Invite ? NSString *promptMsg = [VectorL10n roomParticipantsInvitePromptMsg:contact.displayName]; - currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n roomParticipantsInvitePromptTitle] - message:promptMsg - preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *invitePrompt = [UIAlertController alertControllerWithTitle:[VectorL10n roomParticipantsInvitePromptTitle] + message:promptMsg + preferredStyle:UIAlertControllerStyleAlert]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] + [invitePrompt addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { @@ -5604,7 +5645,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n invite] + [invitePrompt addAction:[UIAlertAction actionWithTitle:[VectorL10n invite] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { @@ -5699,8 +5740,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; - [currentAlert mxk_setAccessibilityIdentifier:@"RoomVCInviteAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; + [invitePrompt mxk_setAccessibilityIdentifier:@"RoomVCInviteAlert"]; + [self presentViewController:invitePrompt animated:YES completion:nil]; + currentAlert = invitePrompt; } #pragma mark - Re-request encryption keys @@ -5744,8 +5786,6 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; alert = [UIAlertController alertControllerWithTitle:VectorL10n.rerequestKeysAlertTitle message:[VectorL10n e2eRoomKeyRequestMessage:AppInfo.current.displayName] preferredStyle:UIAlertControllerStyleAlert]; - currentAlert = alert; - [alert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok] style:UIAlertActionStyleDefault @@ -5759,7 +5799,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; self->currentAlert = nil; }]]; - [self presentViewController:currentAlert animated:YES completion:nil]; + [self presentViewController:alert animated:YES completion:nil]; + currentAlert = alert; } - (void)presentReviewUnverifiedSessionsAlert @@ -5839,8 +5880,12 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; - (void)listenMXSessionStateChangeNotifications { + MXWeakify(self); + kMXSessionStateDidChangeObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXSessionStateDidChangeNotification object:self.roomDataSource.mxSession queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + if (self.roomDataSource.mxSession.state == MXSessionStateSyncError || self.roomDataSource.mxSession.state == MXSessionStateRunning) { @@ -6029,18 +6074,19 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [self hideContextualMenuAnimated:YES cancelEventSelection:YES completion:^{ MXStrongifyAndReturnIfNil(self); - self->currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n roomEventActionDeleteConfirmationTitle] - message:[VectorL10n roomEventActionDeleteConfirmationMessage] - preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *deleteConfirmation = [UIAlertController alertControllerWithTitle:[VectorL10n roomEventActionDeleteConfirmationTitle] + message:[VectorL10n roomEventActionDeleteConfirmationMessage] + preferredStyle:UIAlertControllerStyleAlert]; - [self->currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + [deleteConfirmation addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { }]]; - [self->currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n delete] style:UIAlertActionStyleDestructive handler:^(UIAlertAction * action) { + [deleteConfirmation addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n delete] style:UIAlertActionStyleDestructive handler:^(UIAlertAction * action) { [self.roomDataSource removeEventWithEventId:event.eventId]; }]]; - [self presentViewController:self->currentAlert animated:YES completion:nil]; + [self presentViewController:deleteConfirmation animated:YES completion:nil]; + self->currentAlert = deleteConfirmation; }]; }; diff --git a/Riot/Modules/Room/RoomViewController.xib b/Riot/Modules/Room/RoomViewController.xib index 1c8f83913..b1f933efe 100644 --- a/Riot/Modules/Room/RoomViewController.xib +++ b/Riot/Modules/Room/RoomViewController.xib @@ -163,7 +163,7 @@ - + diff --git a/Riot/Modules/Room/Search/RoomSearchViewController.h b/Riot/Modules/Room/Search/RoomSearchViewController.h index b1168107b..14c1460b5 100644 --- a/Riot/Modules/Room/Search/RoomSearchViewController.h +++ b/Riot/Modules/Room/Search/RoomSearchViewController.h @@ -25,4 +25,6 @@ */ @property (nonatomic) MXKRoomDataSource *roomDataSource; ++ (instancetype)instantiate; + @end diff --git a/Riot/Modules/Room/Search/RoomSearchViewController.m b/Riot/Modules/Room/Search/RoomSearchViewController.m index b6a17df46..08374baea 100644 --- a/Riot/Modules/Room/Search/RoomSearchViewController.m +++ b/Riot/Modules/Room/Search/RoomSearchViewController.m @@ -37,6 +37,13 @@ @implementation RoomSearchViewController ++ (instancetype)instantiate +{ + UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]]; + RoomSearchViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:@"RoomSearch"]; + return viewController; +} + - (void)finalizeInit { [super finalizeInit]; diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarTextView.swift b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarTextView.swift index 729b3c2e7..8a1097647 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarTextView.swift +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarTextView.swift @@ -14,26 +14,100 @@ // limitations under the License. // -import GrowingTextView - @objc protocol RoomInputToolbarTextViewDelegate: AnyObject { + func textView(_ textView: RoomInputToolbarTextView, didChangeHeight height: CGFloat) func textView(_ textView: RoomInputToolbarTextView, didReceivePasteForMediaFromSender sender: Any?) } -class RoomInputToolbarTextView: GrowingTextView { +@objcMembers +class RoomInputToolbarTextView: UITextView { - @objc weak var toolbarDelegate: RoomInputToolbarTextViewDelegate? - - override var keyCommands: [UIKeyCommand]? { - return [UIKeyCommand(input: "\r", modifierFlags: [], action: #selector(keyCommandSelector(_:)))] + private var heightConstraint: NSLayoutConstraint! + + weak var toolbarDelegate: RoomInputToolbarTextViewDelegate? + + var placeholder: String? { + didSet { + setNeedsDisplay() + } } - @objc private func keyCommandSelector(_ keyCommand: UIKeyCommand) { - guard keyCommand.input == "\r", let delegate = (self.delegate as? RoomInputToolbarView) else { + var placeholderColor: UIColor = UIColor(white: 0.8, alpha: 1.0) { + didSet { + setNeedsDisplay() + } + } + + var minHeight: CGFloat = 30.0 { + didSet { + updateUI() + } + } + + var maxHeight: CGFloat = 0.0 { + didSet { + updateUI() + } + } + + override var text: String! { + didSet { + updateUI() + } + } + + override init(frame: CGRect, textContainer: NSTextContainer?) { + super.init(frame: frame, textContainer: textContainer) + commonInit() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + commonInit() + } + + private func commonInit() { + contentMode = .redraw + + NotificationCenter.default.addObserver(self, selector: #selector(textDidChange), name: UITextView.textDidChangeNotification, object: self) + + if let heightConstraint = constraints.filter({ $0.firstAttribute == .height && $0.relation == .equal }).first { + self.heightConstraint = heightConstraint + } else { + heightConstraint = self.heightAnchor.constraint(equalToConstant: minHeight) + addConstraint(heightConstraint) + } + } + + // MARK: - Overrides + + override func layoutSubviews() { + super.layoutSubviews() + updateUI() + } + + override func draw(_ rect: CGRect) { + super.draw(rect) + + guard text.isEmpty, let placeholder = placeholder else { return } - delegate.onTouchUp(inside: delegate.rightInputToolbarButton) + var attributes: [NSAttributedString.Key: Any] = [.foregroundColor: placeholderColor] + if let font = font { + attributes[.font] = font + } + + let frame = rect.inset(by: .init(top: textContainerInset.top, + left: textContainerInset.left + textContainer.lineFragmentPadding, + bottom: textContainerInset.bottom, + right: textContainerInset.right)) + + placeholder.draw(in: frame, withAttributes: attributes) + } + + override var keyCommands: [UIKeyCommand]? { + return [UIKeyCommand(input: "\r", modifierFlags: [], action: #selector(keyCommandSelector(_:)))] } /// Overrides paste to handle images pasted from Safari, passing them up to the input toolbar. @@ -49,4 +123,36 @@ class RoomInputToolbarTextView: GrowingTextView { super.paste(sender) } } + + // MARK: - Private + + @objc private func textDidChange(notification: Notification) { + if let sender = notification.object as? RoomInputToolbarTextView, sender == self { + updateUI() + } + } + + private func updateUI() { + var height = sizeThatFits(CGSize(width: bounds.size.width, height: CGFloat.greatestFiniteMagnitude)).height + height = minHeight > 0 ? max(height, minHeight) : height + height = maxHeight > 0 ? min(height, maxHeight) : height + + // Update placeholder + self.setNeedsDisplay() + + guard height != heightConstraint.constant else { + return + } + + heightConstraint.constant = height + toolbarDelegate?.textView(self, didChangeHeight: height) + } + + @objc private func keyCommandSelector(_ keyCommand: UIKeyCommand) { + guard keyCommand.input == "\r", let delegate = (self.delegate as? RoomInputToolbarView) else { + return + } + + delegate.onTouchUp(inside: delegate.rightInputToolbarButton) + } } diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h index 3099fd50f..d126ba22f 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h @@ -60,28 +60,10 @@ typedef enum : NSUInteger */ @property (nonatomic, weak) id delegate; -@property (weak, nonatomic) IBOutlet UIView *mainToolbarView; - -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *mainToolbarMinHeightConstraint; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *mainToolbarHeightConstraint; - -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *messageComposerContainerTrailingConstraint; - -@property (weak, nonatomic) IBOutlet UIButton *attachMediaButton; - -@property (weak, nonatomic) IBOutlet UIImageView *inputTextBackgroundView; - -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *inputContextViewHeightConstraint; -@property (weak, nonatomic) IBOutlet UIImageView *inputContextImageView; -@property (weak, nonatomic) IBOutlet UILabel *inputContextLabel; -@property (weak, nonatomic) IBOutlet UIButton *inputContextButton; -@property (weak, nonatomic) IBOutlet RoomActionsBar *actionsBar; -@property (weak, nonatomic) UIView *voiceMessageToolbarView; - /** Tell whether the filled data will be sent encrypted. NO by default. */ -@property (nonatomic) BOOL isEncryptionEnabled; +@property (nonatomic, assign) BOOL isEncryptionEnabled; /** Sender of the event being edited / replied. @@ -91,11 +73,31 @@ typedef enum : NSUInteger /** Destination of the message in the composer. */ -@property (nonatomic) RoomInputToolbarViewSendMode sendMode; +@property (nonatomic, assign) RoomInputToolbarViewSendMode sendMode; /** YES if action menu is opened. NO otherwise */ -@property (nonatomic, getter=isActionMenuOpened) BOOL actionMenuOpened; +@property (nonatomic, assign) BOOL actionMenuOpened; + +/** + The input toolbar's main height constraint + */ +@property (nonatomic, weak, readonly) NSLayoutConstraint *mainToolbarHeightConstraint; + +/** + The input toolbar's action bar + */ +@property (nonatomic, weak, readonly) RoomActionsBar *actionsBar; + +/** + The attach media button + */ +@property (nonatomic, weak, readonly) UIButton *attachMediaButton; + +/** + Adds a voice message toolbar view to be displayed inside this input toolbar + */ +- (void)setVoiceMessageToolbarView:(UIView *)toolbarView; @end diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m index 8cc889940..bf1bda55c 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m @@ -17,34 +17,40 @@ #import "RoomInputToolbarView.h" -#import "ThemeService.h" #import "Riot-Swift.h" - #import "GBDeviceInfo_iOS.h" -#import "UINavigationController+Riot.h" +static const CGFloat kContextBarHeight = 24; +static const CGFloat kActionMenuAttachButtonSpringVelocity = 7; +static const CGFloat kActionMenuAttachButtonSpringDamping = .45; -#import "WidgetManager.h" -#import "IntegrationManagerViewController.h" +static const NSTimeInterval kSendModeAnimationDuration = .15; +static const NSTimeInterval kActionMenuAttachButtonAnimationDuration = .4; +static const NSTimeInterval kActionMenuContentAlphaAnimationDuration = .2; +static const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3; -@import GrowingTextView; +@interface RoomInputToolbarView() -const double kContextBarHeight = 24; -const NSTimeInterval kSendModeAnimationDuration = .15; -const NSTimeInterval kActionMenuAttachButtonAnimationDuration = .4; -const CGFloat kActionMenuAttachButtonSpringVelocity = 7; -const CGFloat kActionMenuAttachButtonSpringDamping = .45; -const NSTimeInterval kActionMenuContentAlphaAnimationDuration = .2; -const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3; -const CGFloat kComposerContainerTrailingPadding = 12; +@property (nonatomic, weak) IBOutlet UIView *mainToolbarView; -@interface RoomInputToolbarView() -{ - // The intermediate action sheet - UIAlertController *actionSheet; -} +@property (nonatomic, weak) IBOutlet UIButton *attachMediaButton; @property (nonatomic, weak) IBOutlet RoomInputToolbarTextView *textView; +@property (nonatomic, weak) IBOutlet UIImageView *inputTextBackgroundView; + +@property (nonatomic, weak) IBOutlet UIImageView *inputContextImageView; +@property (nonatomic, weak) IBOutlet UILabel *inputContextLabel; +@property (nonatomic, weak) IBOutlet UIButton *inputContextButton; + +@property (nonatomic, weak) IBOutlet RoomActionsBar *actionsBar; + +@property (nonatomic, weak) IBOutlet NSLayoutConstraint *mainToolbarMinHeightConstraint; +@property (nonatomic, weak) IBOutlet NSLayoutConstraint *mainToolbarHeightConstraint; +@property (nonatomic, weak) IBOutlet NSLayoutConstraint *messageComposerContainerTrailingConstraint; +@property (nonatomic, weak) IBOutlet NSLayoutConstraint *inputContextViewHeightConstraint; + +@property (nonatomic, weak) UIView *voiceMessageToolbarView; + @property (nonatomic, assign) CGFloat expandedMainToolbarHeight; @end @@ -52,22 +58,10 @@ const CGFloat kComposerContainerTrailingPadding = 12; @implementation RoomInputToolbarView @dynamic delegate; -+ (UINib *)nib -{ - return [UINib nibWithNibName:NSStringFromClass([RoomInputToolbarView class]) - bundle:[NSBundle bundleForClass:[RoomInputToolbarView class]]]; -} - + (instancetype)roomInputToolbarView { - if ([[self class] nib]) - { - return [[[self class] nib] instantiateWithOwner:nil options:nil].firstObject; - } - else - { - return [[self alloc] init]; - } + UINib *nib = [UINib nibWithNibName:NSStringFromClass([RoomInputToolbarView class]) bundle:nil]; + return [nib instantiateWithOwner:nil options:nil].firstObject; } - (void)awakeFromNib @@ -85,6 +79,10 @@ const CGFloat kComposerContainerTrailingPadding = 12; [self updateUIWithTextMessage:nil animated:NO]; self.textView.toolbarDelegate = self; + + // Add an accessory view to the text view in order to retrieve keyboard view. + inputAccessoryView = [[UIView alloc] initWithFrame:CGRectZero]; + self.textView.inputAccessoryView = inputAccessoryView; } - (void)setVoiceMessageToolbarView:(UIView *)voiceMessageToolbarView @@ -311,6 +309,11 @@ const CGFloat kComposerContainerTrailingPadding = 12; self.textView.placeholder = inPlaceholder; } +- (void)pasteText:(NSString *)text +{ + self.textMessage = [self.textView.text stringByReplacingCharactersInRange:self.textView.selectedRange withString:text]; +} + #pragma mark - Actions - (IBAction)cancelAction:(id)sender @@ -321,7 +324,7 @@ const CGFloat kComposerContainerTrailingPadding = 12; } } -#pragma mark - GrowingTextViewDelegate +#pragma mark - UITextViewDelegate - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text { @@ -347,7 +350,9 @@ const CGFloat kComposerContainerTrailingPadding = 12; [self.delegate roomInputToolbarViewDidChangeTextMessage:self]; } -- (void)textViewDidChangeHeight:(GrowingTextView *)textView height:(CGFloat)height +#pragma mark - RoomInputToolbarTextViewDelegate + +- (void)textView:(RoomInputToolbarTextView *)textView didChangeHeight:(CGFloat)height { // Update height of the main toolbar (message composer) CGFloat updatedHeight = height + (self.messageComposerContainerTopConstraint.constant + self.messageComposerContainerBottomConstraint.constant) + self.inputContextViewHeightConstraint.constant; @@ -372,13 +377,18 @@ const CGFloat kComposerContainerTrailingPadding = 12; } } +- (void)textView:(RoomInputToolbarTextView *)textView didReceivePasteForMediaFromSender:(id)sender +{ + [self paste:sender]; +} + #pragma mark - Override MXKRoomInputToolbarView - (IBAction)onTouchUpInside:(UIButton*)button { if (button == self.attachMediaButton) { - self.actionMenuOpened = !self.isActionMenuOpened; + self.actionMenuOpened = !self.actionMenuOpened; } [super onTouchUpInside:button]; @@ -396,12 +406,6 @@ const CGFloat kComposerContainerTrailingPadding = 12; - (void)destroy { - if (actionSheet) - { - [actionSheet dismissViewControllerAnimated:NO completion:nil]; - actionSheet = nil; - } - [super destroy]; } @@ -458,20 +462,6 @@ const CGFloat kComposerContainerTrailingPadding = 12; } } -#pragma mark - Clipboard - Handle image/data paste from general pasteboard - -- (void)paste:(id)sender -{ - // TODO Custom here the validation screen for each available item - - [super paste:sender]; -} - -- (void)textView:(GrowingTextView *)textView didReceivePasteForMediaFromSender:(id)sender -{ - [self paste:sender]; -} - #pragma mark - Private - (void)updateUIWithTextMessage:(NSString *)textMessage animated:(BOOL)animated diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 0a5acf57c..0be7f5736 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -178,16 +178,16 @@ ServiceTermsModalCoordinatorBridgePresenterDelegate, TableViewSectionsDelegate> { // Current alert (if any). - UIAlertController *currentAlert; + __weak UIAlertController *currentAlert; // listener - id removedAccountObserver; - id accountUserInfoObserver; - id pushInfoUpdateObserver; + __weak id removedAccountObserver; + __weak id accountUserInfoObserver; + __weak id pushInfoUpdateObserver; - id notificationCenterWillUpdateObserver; - id notificationCenterDidUpdateObserver; - id notificationCenterDidFailObserver; + __weak id notificationCenterWillUpdateObserver; + __weak id notificationCenterDidUpdateObserver; + __weak id notificationCenterDidFailObserver; // profile updates // avatar @@ -216,10 +216,10 @@ TableViewSectionsDelegate> GroupsDataSource *groupsDataSource; // Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar. - id kAppDelegateDidTapStatusBarNotificationObserver; + __weak id kAppDelegateDidTapStatusBarNotificationObserver; // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. - id kThemeServiceDidChangeThemeNotificationObserver; + __weak id kThemeServiceDidChangeThemeNotificationObserver; // Postpone destroy operation when saving, pwd reset or email binding is in progress BOOL isSavingInProgress; @@ -541,13 +541,8 @@ TableViewSectionsDelegate> sectionAbout.headerTitle = VectorL10n.settingsAbout; if (BuildSettings.settingsScreenShowAdvancedSettings) - { - sectionAbout.footerTitle = [NSString stringWithFormat:@"Element %@ (%@) / Olm %@\n%@\n%@", - AppInfo.current.appVersion.bundleShortVersion, - AppInfo.current.appVersion.bundleVersion, - [OLMKit versionString], - [MatrixKitL10n settingsConfigUserId:account.mxCredentials.userId], - [MatrixKitL10n settingsConfigHomeServer:account.mxCredentials.homeServer]]; + { + sectionAbout.footerTitle = [self buildAboutSectionFooterTitleWithAccount:account]; } [tmpSections addObject:sectionAbout]; @@ -612,9 +607,13 @@ TableViewSectionsDelegate> self.tableView.sectionFooterHeight = UITableViewAutomaticDimension; self.tableView.estimatedSectionFooterHeight = 50; + MXWeakify(self); + // Add observer to handle removed accounts removedAccountObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountManagerDidRemoveAccountNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + if ([MXKAccountManager sharedManager].accounts.count) { // Refresh table to remove this account @@ -626,6 +625,8 @@ TableViewSectionsDelegate> // Add observer to handle accounts update accountUserInfoObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountUserInfoDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + [self stopActivityIndicator]; [self refreshSettings]; @@ -635,6 +636,8 @@ TableViewSectionsDelegate> // Add observer to push settings pushInfoUpdateObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountAPNSActivityDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + [self stopActivityIndicator]; [self refreshSettings]; @@ -663,6 +666,8 @@ TableViewSectionsDelegate> // Observe user interface theme change. kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + [self userInterfaceThemeDidChange]; }]; @@ -777,10 +782,14 @@ TableViewSectionsDelegate> // Refresh linked emails and phone numbers in parallel [self loadAccount3PIDs]; + + MXWeakify(self); // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + [self.tableView setContentOffset:CGPointMake(-self.tableView.adjustedContentInset.left, -self.tableView.adjustedContentInset.top) animated:YES]; }]; @@ -977,9 +986,11 @@ TableViewSectionsDelegate> { MXWeakify(self); [currentAlert dismissViewControllerAnimated:NO completion:nil]; - currentAlert = [UIAlertController alertControllerWithTitle:[MatrixKitL10n accountEmailValidationTitle] message:message preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *validationAlert = [UIAlertController alertControllerWithTitle:[MatrixKitL10n accountEmailValidationTitle] + message:message + preferredStyle:UIAlertControllerStyleAlert]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + [validationAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); self->currentAlert = nil; [self stopActivityIndicator]; @@ -988,14 +999,15 @@ TableViewSectionsDelegate> self.newEmailEditingEnabled = NO; }]]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n continue] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + [validationAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n continue] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); [self tryFinaliseAddEmailSession:threePidAddSession withAuthenticationParameters:authenticationParameters threePidAddManager:threePidAddManager]; }]]; - [currentAlert mxk_setAccessibilityIdentifier:@"SettingsVCEmailValidationAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; + [validationAlert mxk_setAccessibilityIdentifier:@"SettingsVCEmailValidationAlert"]; + [self presentViewController:validationAlert animated:YES completion:nil]; + currentAlert = validationAlert; } - (void)tryFinaliseAddEmailSession:(MX3PidAddSession*)threePidAddSession withAuthenticationParameters:(NSDictionary*)authParams threePidAddManager:(MX3PidAddManager*)threePidAddManager @@ -1035,11 +1047,13 @@ TableViewSectionsDelegate> MXLogDebug(@"[SettingsViewController] tryFinaliseAddEmailSession: Wrong credentials"); // Ask password again - self->currentAlert = [UIAlertController alertControllerWithTitle:nil - message:[VectorL10n settingsAdd3pidInvalidPasswordMessage] - preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *passwordPrompt = [UIAlertController alertControllerWithTitle:nil + message:[VectorL10n settingsAdd3pidInvalidPasswordMessage] + preferredStyle:UIAlertControllerStyleAlert]; - [self->currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n retry] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + MXWeakify(self); + [passwordPrompt addAction:[UIAlertAction actionWithTitle:[VectorL10n retry] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); self->currentAlert = nil; [self showAuthenticationIfNeededForAdding:kMX3PIDMediumEmail withSession:self.mainSession completion:^(NSDictionary *authParams) { @@ -1047,7 +1061,8 @@ TableViewSectionsDelegate> }]; }]]; - [self presentViewController:self->currentAlert animated:YES completion:nil]; + [self presentViewController:passwordPrompt animated:YES completion:nil]; + self->currentAlert = passwordPrompt; return; } @@ -1088,9 +1103,11 @@ TableViewSectionsDelegate> MXWeakify(self); [currentAlert dismissViewControllerAnimated:NO completion:nil]; - currentAlert = [UIAlertController alertControllerWithTitle:[MatrixKitL10n accountMsisdnValidationTitle] message:message preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *validationAlert = [UIAlertController alertControllerWithTitle:[MatrixKitL10n accountMsisdnValidationTitle] + message:message + preferredStyle:UIAlertControllerStyleAlert]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + [validationAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); self->currentAlert = nil; @@ -1101,13 +1118,13 @@ TableViewSectionsDelegate> self.newPhoneEditingEnabled = NO; }]]; - [currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + [validationAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { textField.secureTextEntry = NO; textField.placeholder = nil; textField.keyboardType = UIKeyboardTypeDecimalPad; }]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n submit] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + [validationAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n submit] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); @@ -1126,8 +1143,9 @@ TableViewSectionsDelegate> } }]]; - [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCMsisdnValidationAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; + [validationAlert mxk_setAccessibilityIdentifier: @"SettingsVCMsisdnValidationAlert"]; + [self presentViewController:validationAlert animated:YES completion:nil]; + currentAlert = validationAlert; } - (void)finaliseAddPhoneNumberSession:(MX3PidAddSession*)threePidAddSession withToken:(NSString*)token andAuthenticationParameters:(NSDictionary*)authParams message:(NSString*)message threePidAddManager:(MX3PidAddManager*)threePidAddManager @@ -1166,11 +1184,13 @@ TableViewSectionsDelegate> MXLogDebug(@"[SettingsViewController] finaliseAddPhoneNumberSession: Wrong authentication credentials"); // Ask password again - self->currentAlert = [UIAlertController alertControllerWithTitle:nil - message:[VectorL10n settingsAdd3pidInvalidPasswordMessage] - preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *passwordPrompt = [UIAlertController alertControllerWithTitle:nil + message:[VectorL10n settingsAdd3pidInvalidPasswordMessage] + preferredStyle:UIAlertControllerStyleAlert]; - [self->currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n retry] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + MXWeakify(self); + [passwordPrompt addAction:[UIAlertAction actionWithTitle:[VectorL10n retry] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); self->currentAlert = nil; [self showAuthenticationIfNeededForAdding:kMX3PIDMediumMSISDN withSession:self.mainSession completion:^(NSDictionary *authParams) { @@ -1178,7 +1198,8 @@ TableViewSectionsDelegate> }]; }]]; - [self presentViewController:self->currentAlert animated:YES completion:nil]; + [self presentViewController:passwordPrompt animated:YES completion:nil]; + self->currentAlert = passwordPrompt; return; } @@ -1218,17 +1239,20 @@ TableViewSectionsDelegate> } - self->currentAlert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *errorAlert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert]; - [self->currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + MXWeakify(self); + [errorAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); self->currentAlert = nil; // Ask again the sms token [self showValidationMsisdnDialogWithMessage:message for3PidAddSession:threePidAddSession threePidAddManager:threePidAddManager authenticationParameters:authParams]; }]]; - [self->currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCErrorAlert"]; - [self presentViewController:self->currentAlert animated:YES completion:nil]; + [errorAlert mxk_setAccessibilityIdentifier: @"SettingsVCErrorAlert"]; + [self presentViewController:errorAlert animated:YES completion:nil]; + self->currentAlert = errorAlert; } }]; } @@ -1396,6 +1420,35 @@ TableViewSectionsDelegate> } } +- (NSString*)buildAboutSectionFooterTitleWithAccount:(MXKAccount*)account +{ + NSMutableString *footerText = [NSMutableString new]; + + AppInfo *appInfo = AppInfo.current; + + NSString *appName = appInfo.displayName; + NSString *appVersion = appInfo.appVersion.bundleShortVersion; + NSString *buildVersion = appInfo.appVersion.bundleVersion; + + NSString *appVersionInfo = [NSString stringWithFormat:@"%@ %@ (%@)", appName, appVersion, buildVersion]; + + NSString *loggedUserInfo = [MatrixKitL10n settingsConfigUserId:account.mxCredentials.userId]; + + NSString *homeserverInfo = [MatrixKitL10n settingsConfigHomeServer:account.mxCredentials.homeServer]; + + NSString *sdkVersionInfo = [NSString stringWithFormat:@"Matrix SDK %@", MatrixSDKVersion]; + + NSString *olmVersionInfo = [NSString stringWithFormat:@"OLM %@", [OLMKit versionString]]; + + [footerText appendFormat:@"%@\n", loggedUserInfo]; + [footerText appendFormat:@"%@\n", homeserverInfo]; + [footerText appendFormat:@"%@\n", appVersionInfo]; + [footerText appendFormat:@"%@\n", sdkVersionInfo]; + [footerText appendFormat:@"%@", olmVersionInfo]; + + return [footerText copy]; +} + #pragma mark - 3Pid Add - (void)showAuthenticationIfNeededForAdding:(MX3PIDMedium)medium withSession:(MXSession*)session completion:(void (^)(NSDictionary* authParams))completion @@ -2592,11 +2645,11 @@ TableViewSectionsDelegate> __weak typeof(self) weakSelf = self; - currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n settingsUnignoreUser:ignoredUserId] message:nil preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *unignorePrompt = [UIAlertController alertControllerWithTitle:[VectorL10n settingsUnignoreUser:ignoredUserId] message:nil preferredStyle:UIAlertControllerStyleAlert]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n yes] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [unignorePrompt addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n yes] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -2625,9 +2678,9 @@ TableViewSectionsDelegate> }]]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n no] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [unignorePrompt addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n no] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -2637,8 +2690,9 @@ TableViewSectionsDelegate> }]]; - [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCUnignoreAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; + [unignorePrompt mxk_setAccessibilityIdentifier: @"SettingsVCUnignoreAlert"]; + [self presentViewController:unignorePrompt animated:YES completion:nil]; + currentAlert = unignorePrompt; } } else if (section == SECTION_TAG_ABOUT) @@ -2824,9 +2878,9 @@ TableViewSectionsDelegate> } // Remove ? - currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n settingsRemovePromptTitle] message:promptMsg preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *removePrompt = [UIAlertController alertControllerWithTitle:[VectorL10n settingsRemovePromptTitle] message:promptMsg preferredStyle:UIAlertControllerStyleAlert]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] + [removePrompt addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { @@ -2838,7 +2892,7 @@ TableViewSectionsDelegate> }]]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n remove] + [removePrompt addAction:[UIAlertAction actionWithTitle:[VectorL10n remove] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { @@ -2878,8 +2932,9 @@ TableViewSectionsDelegate> }]]; - [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCRemove3PIDAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; + [removePrompt mxk_setAccessibilityIdentifier: @"SettingsVCRemove3PIDAlert"]; + [self presentViewController:removePrompt animated:YES completion:nil]; + currentAlert = removePrompt; } } } @@ -2901,9 +2956,9 @@ TableViewSectionsDelegate> NSString *title = [VectorL10n settingsNotificationsDisabledAlertTitle]; NSString *message = [VectorL10n settingsNotificationsDisabledAlertMessage]; - currentAlert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *showSettingsPrompt = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] + [showSettingsPrompt addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { @@ -2927,13 +2982,14 @@ TableViewSectionsDelegate> } }]; - [currentAlert addAction:settingsAction]; - currentAlert.preferredAction = settingsAction; + [showSettingsPrompt addAction:settingsAction]; + showSettingsPrompt.preferredAction = settingsAction; - [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCPushNotificationsAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; + [showSettingsPrompt mxk_setAccessibilityIdentifier: @"SettingsVCPushNotificationsAlert"]; + [self presentViewController:showSettingsPrompt animated:YES completion:nil]; + currentAlert = showSettingsPrompt; - // Keep off the switch + // Keep the the switch off. sender.on = NO; } else if ([MXKAccountManager sharedManager].activeAccounts.count) @@ -3322,11 +3378,11 @@ TableViewSectionsDelegate> [currentAlert dismissViewControllerAnimated:NO completion:nil]; - currentAlert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *errorAlert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [errorAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3347,9 +3403,9 @@ TableViewSectionsDelegate> }]]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n retry] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [errorAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n retry] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3363,8 +3419,9 @@ TableViewSectionsDelegate> }]]; - [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCSaveChangesFailedAlert"]; - [rootViewController presentViewController:currentAlert animated:YES completion:nil]; + [errorAlert mxk_setAccessibilityIdentifier: @"SettingsVCSaveChangesFailedAlert"]; + [rootViewController presentViewController:errorAlert animated:YES completion:nil]; + currentAlert = errorAlert; } } @@ -3385,13 +3442,13 @@ TableViewSectionsDelegate> [currentAlert dismissViewControllerAnimated:NO completion:nil]; - currentAlert = [UIAlertController alertControllerWithTitle:[MatrixKitL10n accountErrorEmailWrongTitle] - message:[MatrixKitL10n accountErrorEmailWrongDescription] - preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *errorAlert = [UIAlertController alertControllerWithTitle:[MatrixKitL10n accountErrorEmailWrongTitle] + message:[MatrixKitL10n accountErrorEmailWrongDescription] + preferredStyle:UIAlertControllerStyleAlert]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [errorAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { if (weakSelf) { @@ -3402,8 +3459,9 @@ TableViewSectionsDelegate> }]]; - [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCAddEmailAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; + [errorAlert mxk_setAccessibilityIdentifier: @"SettingsVCAddEmailAlert"]; + [self presentViewController:errorAlert animated:YES completion:nil]; + currentAlert = errorAlert; return; } @@ -3495,11 +3553,11 @@ TableViewSectionsDelegate> [currentAlert dismissViewControllerAnimated:NO completion:nil]; __weak typeof(self) weakSelf = self; - currentAlert = [UIAlertController alertControllerWithTitle:[MatrixKitL10n accountErrorMsisdnWrongTitle] - message:[MatrixKitL10n accountErrorMsisdnWrongDescription] - preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *errorAlert = [UIAlertController alertControllerWithTitle:[MatrixKitL10n accountErrorMsisdnWrongTitle] + message:[MatrixKitL10n accountErrorMsisdnWrongDescription] + preferredStyle:UIAlertControllerStyleAlert]; - [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok] + [errorAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { @@ -3511,8 +3569,9 @@ TableViewSectionsDelegate> }]]; - [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCAddMsisdnAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; + [errorAlert mxk_setAccessibilityIdentifier: @"SettingsVCAddMsisdnAlert"]; + [self presentViewController:errorAlert animated:YES completion:nil]; + currentAlert = errorAlert; return; } @@ -3868,9 +3927,9 @@ TableViewSectionsDelegate> { [self->currentAlert dismissViewControllerAnimated:NO completion:nil]; - self->currentAlert = [UIAlertController alertControllerWithTitle:nil message:[VectorL10n settingsPasswordUpdated] preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *successAlert = [UIAlertController alertControllerWithTitle:nil message:[VectorL10n settingsPasswordUpdated] preferredStyle:UIAlertControllerStyleAlert]; - [self->currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok] + [successAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { @@ -3890,8 +3949,9 @@ TableViewSectionsDelegate> }]]; - [self->currentAlert mxk_setAccessibilityIdentifier:@"SettingsVCOnPasswordUpdatedAlert"]; - [self presentViewController:self->currentAlert animated:YES completion:nil]; + [successAlert mxk_setAccessibilityIdentifier:@"SettingsVCOnPasswordUpdatedAlert"]; + [self presentViewController:successAlert animated:YES completion:nil]; + self->currentAlert = successAlert; } else { @@ -3916,9 +3976,9 @@ TableViewSectionsDelegate> { [self->currentAlert dismissViewControllerAnimated:NO completion:nil]; - self->currentAlert = [UIAlertController alertControllerWithTitle:nil message:[VectorL10n settingsFailToUpdatePassword] preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *errorAlert = [UIAlertController alertControllerWithTitle:nil message:[VectorL10n settingsFailToUpdatePassword] preferredStyle:UIAlertControllerStyleAlert]; - [self->currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok] + [errorAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { @@ -3939,8 +3999,9 @@ TableViewSectionsDelegate> }]]; - [self->currentAlert mxk_setAccessibilityIdentifier:@"SettingsVCPasswordChangeFailedAlert"]; - [rootViewController presentViewController:self->currentAlert animated:YES completion:nil]; + [errorAlert mxk_setAccessibilityIdentifier:@"SettingsVCPasswordChangeFailedAlert"]; + [rootViewController presentViewController:errorAlert animated:YES completion:nil]; + self->currentAlert = errorAlert; } } diff --git a/Riot/Modules/Spaces/SpaceList/SpaceListViewModel.swift b/Riot/Modules/Spaces/SpaceList/SpaceListViewModel.swift index 3f9924531..62770e05e 100644 --- a/Riot/Modules/Spaces/SpaceList/SpaceListViewModel.swift +++ b/Riot/Modules/Spaces/SpaceList/SpaceListViewModel.swift @@ -131,6 +131,9 @@ final class SpaceListViewModel: SpaceListViewModelType { private func loadData() { guard let session = self.userSessionsService.mainUserSession?.matrixSession else { + // If there is no main session, reset current selection and give an empty section list + // It can happen when the user make a clear cache or logout + self.resetList() return } @@ -243,4 +246,15 @@ final class SpaceListViewModel: SpaceListViewModelType { return spaceViewData.spaceId } } + + private func resetList() { + self.sections = [] + + let selectedIndexPath = IndexPath(row: 0, section: 0) + + self.selectedIndexPath = selectedIndexPath + self.homeIndexPath = selectedIndexPath + + self.update(viewState: .loaded([])) + } } diff --git a/Riot/Modules/SplitView/SplitViewCoordinator.swift b/Riot/Modules/SplitView/SplitViewCoordinator.swift index 479ebf702..ee1ad5af7 100644 --- a/Riot/Modules/SplitView/SplitViewCoordinator.swift +++ b/Riot/Modules/SplitView/SplitViewCoordinator.swift @@ -125,20 +125,20 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType { } // TODO: Do not expose publicly this method - func restorePlaceholderDetails() { + func resetDetails(animated: Bool) { // Be sure that the primary is then visible too. if splitViewController.displayMode == .primaryHidden { splitViewController.preferredDisplayMode = .allVisible } - self.resetDetailNavigationControllerWithPlaceholder(animated: false) + self.resetDetailNavigationController(animated: animated) // Release the current selected item (room/contact/group...). self.tabBarCoordinator?.releaseSelectedItems() - } + } func popToHome(animated: Bool, completion: (() -> Void)?) { - self.resetDetailNavigationControllerWithPlaceholder(animated: animated) + self.resetDetails(animated: animated) // Force back to the main screen if this is not the one that is displayed self.tabBarCoordinator?.popToHome(animated: animated, completion: completion) @@ -172,6 +172,17 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType { // Set placeholder screen as root controller of detail navigation controller let placeholderDetailsVC = self.createPlaceholderDetailsViewController() detailNavigationRouter.setRootModule(placeholderDetailsVC, hideNavigationBar: false, animated: animated, popCompletion: nil) + } + + private func resetDetailNavigationController(animated: Bool) { + + if self.splitViewController.isCollapsed { + if let topMostNavigationController = self.selectedNavigationRouter?.modules.last as? UINavigationController, topMostNavigationController == self.detailNavigationController { + self.selectedNavigationRouter?.popModule(animated: animated) + } + } else { + self.resetDetailNavigationControllerWithPlaceholder(animated: animated) + } } private func isPlaceholderShown(from secondaryViewController: UIViewController) -> Bool { @@ -270,7 +281,7 @@ extension SplitViewCoordinator: UISplitViewControllerDelegate { } // Restore detail navigation controller with placeholder as root - self.resetDetailNavigationControllerWithPlaceholder(animated: false) + self.resetDetailNavigationController(animated: false) // Return up to date detail navigation controller // In any cases `detailNavigationController` will be used as secondary view of the split view controller. @@ -353,6 +364,6 @@ extension SplitViewCoordinator: SplitViewMasterPresentableDelegate { } func splitViewMasterPresentableWantsToResetDetail(_ presentable: Presentable) { - self.resetDetailNavigationControllerWithPlaceholder(animated: false) + self.resetDetails(animated: false) } } diff --git a/Riot/Modules/SplitView/SplitViewCoordinatorType.swift b/Riot/Modules/SplitView/SplitViewCoordinatorType.swift index e49a54879..4fd2cfef6 100644 --- a/Riot/Modules/SplitView/SplitViewCoordinatorType.swift +++ b/Riot/Modules/SplitView/SplitViewCoordinatorType.swift @@ -30,8 +30,10 @@ protocol SplitViewCoordinatorType: Coordinator, Presentable { /// - Parameter spaceId: The id of the Space to use. func start(with spaceId: String?) + /// Restore navigation stack and show home screen func popToHome(animated: Bool, completion: (() -> Void)?) - + // TODO: Do not expose publicly this method - func restorePlaceholderDetails() + /// Remove detail screens and display placeholder if needed + func resetDetails(animated: Bool) } diff --git a/Riot/Modules/TabBar/TabBarCoordinator.swift b/Riot/Modules/TabBar/TabBarCoordinator.swift index cb8fbaa6c..63bf354a2 100644 --- a/Riot/Modules/TabBar/TabBarCoordinator.swift +++ b/Riot/Modules/TabBar/TabBarCoordinator.swift @@ -41,7 +41,8 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { private let masterNavigationController: UINavigationController private var currentSpaceId: String? - private var homeViewControllerWrapperViewController: HomeViewControllerWithBannerWrapperViewController? + + private weak var versionCheckCoordinator: VersionCheckCoordinator? private var currentMatrixSession: MXSession? { return parameters.userSessionsService.mainUserSession?.matrixSession @@ -77,8 +78,7 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { } func start(with spaceId: String?) { - self.currentSpaceId = spaceId - + // If start has been done once do not setup view controllers again if self.hasStartedOnce == false { let masterTabBarController = self.createMasterTabBarController() @@ -98,16 +98,12 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { self.registerUserSessionsServiceNotifications() self.registerSessionChange() - if let homeViewController = homeViewControllerWrapperViewController { - let versionCheckCoordinator = VersionCheckCoordinator(rootViewController: masterTabBarController, - bannerPresenter: homeViewController, - themeService: ThemeService.shared()) - versionCheckCoordinator.start() - add(childCoordinator: versionCheckCoordinator) - } + self.updateMasterTabBarController(with: spaceId, forceReload: true) + } else { + self.updateMasterTabBarController(with: spaceId) } - - self.updateMasterTabBarController(with: spaceId) + + self.currentSpaceId = spaceId } func toPresentable() -> UIViewController { @@ -211,21 +207,25 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { } searchBarButtonItem.accessibilityLabel = VectorL10n.searchDefaultPlaceholder - tabBarController.navigationItem.rightBarButtonItem = searchBarButtonItem - - self.updateTabControllers(for: tabBarController, showCommunities: true) + tabBarController.navigationItem.rightBarButtonItem = searchBarButtonItem return tabBarController } - private func createHomeViewController() -> UIViewController { + private func createVersionCheckCoordinator(withRootViewController rootViewController: UIViewController, bannerPresentrer: BannerPresentationProtocol) -> VersionCheckCoordinator { + let versionCheckCoordinator = VersionCheckCoordinator(rootViewController: rootViewController, + bannerPresenter: bannerPresentrer, + themeService: ThemeService.shared()) + return versionCheckCoordinator + } + + private func createHomeViewController() -> HomeViewControllerWithBannerWrapperViewController { let homeViewController: HomeViewController = HomeViewController.instantiate() homeViewController.tabBarItem.tag = Int(TABBAR_HOME_INDEX) homeViewController.tabBarItem.image = homeViewController.tabBarItem.image homeViewController.accessibilityLabel = VectorL10n.titleHome - let wrapperViewController = HomeViewControllerWithBannerWrapperViewController(viewController: homeViewController) - homeViewControllerWrapperViewController = wrapperViewController + let wrapperViewController = HomeViewControllerWithBannerWrapperViewController(viewController: homeViewController) return wrapperViewController } @@ -280,18 +280,35 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { gesture.delegate = self } - private func updateMasterTabBarController(with spaceId: String?) { + private func updateMasterTabBarController(with spaceId: String?, forceReload: Bool = false) { + + guard forceReload || spaceId != self.currentSpaceId else { return } self.updateTabControllers(for: self.masterTabBarController, showCommunities: spaceId == nil) self.masterTabBarController.filterRooms(withParentId: spaceId, inMatrixSession: self.currentMatrixSession) } + // TODO: Avoid to reinstantiate controllers everytime private func updateTabControllers(for tabBarController: MasterTabBarController, showCommunities: Bool) { var viewControllers: [UIViewController] = [] - + let homeViewController = self.createHomeViewController() + viewControllers.append(homeViewController) + if let existingVersionCheckCoordinator = self.versionCheckCoordinator { + self.remove(childCoordinator: existingVersionCheckCoordinator) + } + + if let masterTabBarController = self.masterTabBarController { + + let versionCheckCoordinator = self.createVersionCheckCoordinator(withRootViewController: masterTabBarController, bannerPresentrer: homeViewController) + versionCheckCoordinator.start() + self.add(childCoordinator: versionCheckCoordinator) + + self.versionCheckCoordinator = versionCheckCoordinator + } + if RiotSettings.shared.homeScreenShowFavouritesTab { let favouritesViewController = self.createFavouritesViewController() viewControllers.append(favouritesViewController) diff --git a/Riot/Routers/NavigationRouter.swift b/Riot/Routers/NavigationRouter.swift index 880dbd9ee..25a0aa333 100755 --- a/Riot/Routers/NavigationRouter.swift +++ b/Riot/Routers/NavigationRouter.swift @@ -299,13 +299,12 @@ final class NavigationRouter: NSObject, NavigationRouterType { self.postNotification(withName: NavigationRouter.willPopModule, for: viewController) } - private func didPopViewController(_ viewController: UIViewController) { + private func didPopViewController(_ viewController: UIViewController) { + self.postNotification(withName: NavigationRouter.didPopModule, for: viewController) // Call completion closure associated to the view controller // So associated coordinator can be deallocated runCompletion(for: viewController) - - self.postNotification(withName: NavigationRouter.didPopModule, for: viewController) self.removeModule(for: viewController) } diff --git a/RiotShareExtension/Shared/ShareDataSource.m b/RiotShareExtension/Shared/ShareDataSource.m index a06a92e36..aa064c5a5 100644 --- a/RiotShareExtension/Shared/ShareDataSource.m +++ b/RiotShareExtension/Shared/ShareDataSource.m @@ -86,7 +86,7 @@ for (MXRoomSummary *roomSummary in roomsSummaries) { - if (!roomSummary.hiddenFromUser) + if (!roomSummary.hiddenFromUser && roomSummary.roomType == MXRoomTypeRoom) { [roomSummary setMatrixSession:session]; diff --git a/RiotShareExtension/Shared/ShareManager.m b/RiotShareExtension/Shared/ShareManager.m index 5ca23a2b1..fdec722ed 100644 --- a/RiotShareExtension/Shared/ShareManager.m +++ b/RiotShareExtension/Shared/ShareManager.m @@ -32,6 +32,8 @@ static const CGFloat kLargeImageSizeMaxDimension = 2048.0; static const CGSize kThumbnailSize = {800.0, 600.0}; +/// A safe maximum file size for an image to send the original. +static const NSUInteger kImageMaxFileSize = 20 * 1024 * 1024; typedef NS_ENUM(NSInteger, ImageCompressionMode) { @@ -314,30 +316,43 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) if ([self.shareItemProvider areAllItemsImages]) { + // When all items are images, they're processed together from the + // pending list, immediately after the final image has been loaded. [self.pendingImages addObject:imageData]; } else { - CGSize imageSize = [self imageSizeFromImageData:imageData]; + // Otherwise, the image is sent as is, without prompting for a resize + // as that wouldn't make much sense with multiple content types. self.imageCompressionMode = ImageCompressionModeNone; - self.actualLargeSize = MAX(imageSize.width, imageSize.height); - [self sendImageData:imageData toRooms:rooms success:requestSuccess failure:requestFailure]; } - // Only prompt for image resize if all items are images - // Ignore showMediaCompressionPrompt setting due to memory constraints with full size images. + // When there are multiple content types the image will have been sent above. + // Otherwise, if we have loaded all of the images we can send them all together. if ([self.shareItemProvider areAllItemsImages]) { if ([self.shareItemProvider areAllItemsLoaded]) { - UIAlertController *compressionPrompt = [self compressionPromptForPendingImagesWithShareBlock:^{ + MXWeakify(self); + void (^sendPendingImages)(void) = ^void() { + MXStrongifyAndReturnIfNil(self); [self sendImageDatas:self.pendingImages.copy toRooms:rooms success:requestSuccess failure:requestFailure]; - }]; + }; - if (compressionPrompt) + if (RiotSettings.shared.showMediaCompressionPrompt) { - [self presentCompressionPrompt:compressionPrompt]; + // Create a compression prompt which will be nil when the sizes can't be determined or if there are no pending images. + UIAlertController *compressionPrompt = [self compressionPromptForPendingImagesWithShareBlock:sendPendingImages]; + if (compressionPrompt) + { + [self presentCompressionPrompt:compressionPrompt]; + } + } + else + { + self.imageCompressionMode = ImageCompressionModeNone; + sendPendingImages(); } } else @@ -410,26 +425,26 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) } } -- (void)resetPendingData +- (BOOL)roomsContainEncryptedRoom:(NSArray *)rooms { - [self.pendingImages removeAllObjects]; - [self.imageUploadProgresses removeAllObjects]; -} - -- (BOOL)isAPendingImageNotOrientedUp -{ - BOOL isAPendingImageNotOrientedUp = NO; + BOOL foundEncryptedRoom = NO; - for (NSData *imageData in self.pendingImages) + for (MXRoom *room in rooms) { - if ([self isImageOrientationNotUpOrUndeterminedForImageData:imageData]) + if (room.summary.isEncrypted) { - isAPendingImageNotOrientedUp = YES; + foundEncryptedRoom = YES; break; } } - return isAPendingImageNotOrientedUp; + return foundEncryptedRoom; +} + +- (void)resetPendingData +{ + [self.pendingImages removeAllObjects]; + [self.imageUploadProgresses removeAllObjects]; } // TODO: When select multiple images: @@ -442,8 +457,6 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) return nil; } - BOOL isAPendingImageNotOrientedUp = [self isAPendingImageNotOrientedUp]; - NSData *firstImageData = self.pendingImages.firstObject; UIImage *firstImage = [UIImage imageWithData:firstImageData]; @@ -451,16 +464,8 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) if (compressionSizes.small.fileSize == 0 && compressionSizes.medium.fileSize == 0 && compressionSizes.large.fileSize == 0) { - if (isAPendingImageNotOrientedUp && self.pendingImages.count > 1) - { - self.imageCompressionMode = ImageCompressionModeSmall; - } - else - { - self.imageCompressionMode = ImageCompressionModeNone; - } - - MXLogDebug(@"[ShareManager] Send %lu image(s) without compression prompt using compression mode: %ld", (unsigned long)self.pendingImages.count, (long)self.imageCompressionMode); + self.imageCompressionMode = ImageCompressionModeNone; + MXLogDebug(@"[ShareManager] Bypass compression prompt and send originals for %lu image(s) due to undetermined file sizes", (unsigned long)self.pendingImages.count); shareBlock(); @@ -480,7 +485,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) MXStrongifyAndReturnIfNil(self); self.imageCompressionMode = ImageCompressionModeSmall; - [self logCompressionSizeChoice:compressionSizes.large]; + [self logCompressionSizeChoice:compressionSizes.small]; shareBlock(); }]]; @@ -495,7 +500,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) MXStrongifyAndReturnIfNil(self); self.imageCompressionMode = ImageCompressionModeMedium; - [self logCompressionSizeChoice:compressionSizes.large]; + [self logCompressionSizeChoice:compressionSizes.medium]; shareBlock(); }]]; @@ -520,8 +525,8 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) }]]; } - // To limit memory consumption, we suggest the original resolution only if the image orientation is up, or if the image size is moderate - if (!isAPendingImageNotOrientedUp || !compressionSizes.large.fileSize) + // To limit memory consumption when encrypting, we suggest the original resolution only if the image size is moderate + if (compressionSizes.original.fileSize < kImageMaxFileSize) { NSString *fileSizeString = [MXTools fileSizeToString:compressionSizes.original.fileSize]; @@ -532,7 +537,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) MXStrongifyAndReturnIfNil(self); self.imageCompressionMode = ImageCompressionModeNone; - [self logCompressionSizeChoice:compressionSizes.large]; + [self logCompressionSizeChoice:compressionSizes.original]; shareBlock(); }]]; @@ -626,46 +631,6 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) return CGSizeMake(width, height); } -- (NSNumber*)cgImageimageOrientationNumberFromImageData:(NSData*)imageData -{ - NSNumber *orientationNumber; - - CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)imageData, NULL); - - CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL); - - CFRelease(imageSource); - - if (imageProperties != NULL) - { - CFNumberRef orientationNum = CFDictionaryGetValue(imageProperties, kCGImagePropertyOrientation); - - // Check orientation and flip size if required - if (orientationNum != NULL) - { - orientationNumber = (__bridge NSNumber *)orientationNum; - } - - CFRelease(imageProperties); - } - - return orientationNumber; -} - -- (BOOL)isImageOrientationNotUpOrUndeterminedForImageData:(NSData*)imageData -{ - BOOL isImageNotOrientedUp = YES; - - NSNumber *cgImageOrientationNumber = [self cgImageimageOrientationNumberFromImageData:imageData]; - - if (cgImageOrientationNumber && cgImageOrientationNumber.unsignedIntegerValue == (NSUInteger)kCGImagePropertyOrientationUp) - { - isImageNotOrientedUp = NO; - } - - return isImageNotOrientedUp; -} - - (void)logCompressionSizeChoice:(MXKImageCompressionSize)compressionSize { NSString *fileSize = [MXTools fileSizeToString:compressionSize.fileSize round:NO]; @@ -826,19 +791,9 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) MXWeakify(self); - // Ignore showMediaCompressionPrompt setting due to memory constraints when encrypting large videos. - UIAlertController *compressionPrompt = [MXKTools videoConversionPromptForVideoAsset:videoAsset withCompletion:^(NSString *presetName) { + void (^sendVideo)(void) = ^void() { MXStrongifyAndReturnIfNil(self); - // If the preset name is nil, the user cancelled. - if (!presetName) - { - return; - } - - // Set the chosen video conversion preset. - [MXSDKOptions sharedInstance].videoConversionPresetName = presetName; - [self didStartSending]; if (!videoLocalUrl) { @@ -876,9 +831,57 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) success(); } }); - }]; + }; - [self presentCompressionPrompt:compressionPrompt]; + BOOL allRoomsAreUnencrypted = ![self roomsContainEncryptedRoom:rooms]; + + // When rooms are unencrypted convert the video according to the user's normal preferences + if (allRoomsAreUnencrypted) + { + if (!RiotSettings.shared.showMediaCompressionPrompt) + { + [MXSDKOptions sharedInstance].videoConversionPresetName = AVCaptureSessionPreset1920x1080; + sendVideo(); + } + else + { + UIAlertController *compressionPrompt = [MXKTools videoConversionPromptForVideoAsset:videoAsset withCompletion:^(NSString *presetName) { + // If the preset name is nil, the user cancelled. + if (!presetName) + { + return; + } + + // Set the chosen video conversion preset. + [MXSDKOptions sharedInstance].videoConversionPresetName = presetName; + sendVideo(); + }]; + + [self presentCompressionPrompt:compressionPrompt]; + } + } + else + { + // When rooms are encrypted we quickly run out of memory encrypting the video + // Prompt the user if they're happy to send a low quality video (320p). + UIAlertController *lowQualityPrompt = [UIAlertController alertControllerWithTitle:VectorL10n.shareExtensionLowQualityVideoTitle + message:[VectorL10n shareExtensionLowQualityVideoMessage:AppInfo.current.displayName] + preferredStyle:UIAlertControllerStyleAlert]; + + UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:MatrixKitL10n.cancel style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { + // Do nothing + }]; + UIAlertAction *sendAction = [UIAlertAction actionWithTitle:VectorL10n.shareExtensionSendNow style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [MXSDKOptions sharedInstance].videoConversionPresetName = AVAssetExportPresetMediumQuality; + sendVideo(); + }]; + + [lowQualityPrompt addAction:cancelAction]; + [lowQualityPrompt addAction:sendAction]; + [lowQualityPrompt setPreferredAction:sendAction]; + + [self presentCompressionPrompt:lowQualityPrompt]; + } } - (void)sendVoiceMessage:(NSURL *)fileUrl @@ -1021,17 +1024,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) break; } - if (CGSizeEqualToSize(newImageSize, CGSizeZero)) - { - // No resize to make - // Make sure the uploaded image orientation is up - if ([self isImageOrientationNotUpOrUndeterminedForImageData:imageData]) - { - UIImage *image = [UIImage imageWithData:imageData]; - convertedImage = [MXKTools forceImageOrientationUp:image]; - } - } - else + if (!CGSizeEqualToSize(newImageSize, CGSizeZero)) { // Resize the image and set image in right orientation too convertedImage = [MXKTools resizeImageWithData:imageData toFitInSize:newImageSize]; diff --git a/RiotShareExtension/Shared/View/RecentRoomTableViewCell.m b/RiotShareExtension/Shared/View/RecentRoomTableViewCell.m index 31beb9cbf..ed7eb32a8 100644 --- a/RiotShareExtension/Shared/View/RecentRoomTableViewCell.m +++ b/RiotShareExtension/Shared/View/RecentRoomTableViewCell.m @@ -57,6 +57,7 @@ self.roomTitleLabel.textColor = ThemeService.shared.theme.textPrimaryColor; self.contentView.backgroundColor = ThemeService.shared.theme.backgroundColor; + self.selectionButton.tintColor = ThemeService.shared.theme.tintColor; [self.selectionButton setImage:[UIImage imageNamed:@"radio-button-default"] forState:UIControlStateNormal]; [self.selectionButton setImage:[UIImage imageNamed:@"radio-button-selected"] forState:UIControlStateSelected]; @@ -85,7 +86,7 @@ [self.avatarImageView vc_setRoomAvatarImageWith:roomCellData.avatarUrl roomId:roomCellData.roomIdentifier displayName:roomCellData.roomDisplayname - mediaManager:roomCellData.mxSession.mediaManager]; + mediaManager:roomCellData.roomSummary.mxSession.mediaManager]; self.roomTitleLabel.text = roomCellData.roomDisplayname; if (!self.roomTitleLabel.text.length) diff --git a/RiotShareExtension/Shared/View/RecentRoomTableViewCell.xib b/RiotShareExtension/Shared/View/RecentRoomTableViewCell.xib index fe39d2a67..a61bc36e6 100644 --- a/RiotShareExtension/Shared/View/RecentRoomTableViewCell.xib +++ b/RiotShareExtension/Shared/View/RecentRoomTableViewCell.xib @@ -1,9 +1,9 @@ - + - + @@ -40,14 +40,15 @@ - diff --git a/RiotShareExtension/Sources/ShareExtensionShareItemProvider.swift b/RiotShareExtension/Sources/ShareExtensionShareItemProvider.swift index df80ab563..6a83e25c5 100644 --- a/RiotShareExtension/Sources/ShareExtensionShareItemProvider.swift +++ b/RiotShareExtension/Sources/ShareExtensionShareItemProvider.swift @@ -28,18 +28,18 @@ private class ShareExtensionItem: ShareItemProtocol { } var type: ShareItemType { - if itemProvider.hasItemConformingToTypeIdentifier(MXKUTI.text.rawValue) { - return .text - } else if itemProvider.hasItemConformingToTypeIdentifier(MXKUTI.url.rawValue) { - return .URL - } else if itemProvider.hasItemConformingToTypeIdentifier(MXKUTI.fileUrl.rawValue) { - return .fileURL - } else if itemProvider.hasItemConformingToTypeIdentifier(MXKUTI.image.rawValue) { + if itemProvider.hasItemConformingToTypeIdentifier(MXKUTI.image.rawValue) { return .image } else if itemProvider.hasItemConformingToTypeIdentifier(MXKUTI.video.rawValue) { return .video } else if itemProvider.hasItemConformingToTypeIdentifier(MXKUTI.movie.rawValue) { return .movie + } else if itemProvider.hasItemConformingToTypeIdentifier(MXKUTI.fileUrl.rawValue) { + return .fileURL + } else if itemProvider.hasItemConformingToTypeIdentifier(MXKUTI.url.rawValue) { + return .URL + } else if itemProvider.hasItemConformingToTypeIdentifier(MXKUTI.text.rawValue) { + return .text } return .unknown diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinator.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinator.swift index bc50aae42..e7308710d 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinator.swift @@ -34,11 +34,10 @@ final class UserSuggestionCoordinator: Coordinator { private let parameters: UserSuggestionCoordinatorParameters - private var userSuggestionHostingController: UIViewController! - private var userSuggestionService: UserSuggestionServiceProtocol! - private var userSuggestionViewModel: UserSuggestionViewModelProtocol! - - private var roomMembers: [MXRoomMember] = [] + private var userSuggestionHostingController: UIViewController + private var userSuggestionService: UserSuggestionServiceProtocol + private var userSuggestionViewModel: UserSuggestionViewModelProtocol + private var roomMemberProvider: UserSuggestionCoordinatorRoomMemberProvider // MARK: Public @@ -54,9 +53,10 @@ final class UserSuggestionCoordinator: Coordinator { init(parameters: UserSuggestionCoordinatorParameters) { self.parameters = parameters - userSuggestionService = UserSuggestionService(roomMembersProvider: self) + roomMemberProvider = UserSuggestionCoordinatorRoomMemberProvider(room: parameters.room) + userSuggestionService = UserSuggestionService(roomMemberProvider: roomMemberProvider) userSuggestionViewModel = UserSuggestionViewModel.makeUserSuggestionViewModel(userSuggestionService: userSuggestionService) - + let view = UserSuggestionList(viewModel: userSuggestionViewModel.context) .addDependency(AvatarService.instantiate(mediaManager: parameters.mediaManager)) @@ -69,7 +69,7 @@ final class UserSuggestionCoordinator: Coordinator { switch result { case .selectedItemWithIdentifier(let identifier): - guard let member = self.roomMembers.filter({ $0.userId == identifier }).first else { + guard let member = self.roomMemberProvider.roomMembers.filter({ $0.userId == identifier }).first else { return } @@ -93,9 +93,18 @@ final class UserSuggestionCoordinator: Coordinator { } @available(iOS 14.0, *) -extension UserSuggestionCoordinator: RoomMembersProviderProtocol { +private class UserSuggestionCoordinatorRoomMemberProvider: RoomMembersProviderProtocol { + + private let room: MXRoom + + var roomMembers: [MXRoomMember] = [] + + init(room: MXRoom) { + self.room = room; + } + func fetchMembers(_ members: @escaping ([RoomMembersProviderMember]) -> Void) { - parameters.room.members({ [weak self] roomMembers in + room.members({ [weak self] roomMembers in guard let self = self, let joinedMembers = roomMembers?.joinedMembers else { return } @@ -108,7 +117,7 @@ extension UserSuggestionCoordinator: RoomMembersProviderProtocol { self.roomMembers = joinedMembers members(self.roomMembersToProviderMembers(joinedMembers)) }, failure: { error in - MXLog.error("[UserSuggestionCoordinator] Failed loading room with error: \(String(describing: error))") + MXLog.error("[UserSuggestionCoordinatorRoomMemberProvider] Failed loading room with error: \(String(describing: error))") }) } diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/Service/Mock/MockUserSuggestionScreenState.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/Service/Mock/MockUserSuggestionScreenState.swift index 7897c608d..7d4180201 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/Service/Mock/MockUserSuggestionScreenState.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/Service/Mock/MockUserSuggestionScreenState.swift @@ -30,7 +30,7 @@ enum MockUserSuggestionScreenState: MockScreenState, CaseIterable { } var screenView: ([Any], AnyView) { - let service = UserSuggestionService(roomMembersProvider: self) + let service = UserSuggestionService(roomMemberProvider: self) let listViewModel = UserSuggestionViewModel.makeUserSuggestionViewModel(userSuggestionService: service) let viewModel = UserSuggestionListWithInputViewModel(listViewModel: listViewModel) { textMessage in diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionService.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionService.swift index ad9de7e56..6e4fab347 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionService.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionService.swift @@ -45,7 +45,7 @@ class UserSuggestionService: UserSuggestionServiceProtocol { // MARK: Private - private let roomMembersProvider: RoomMembersProviderProtocol + private let roomMemberProvider: RoomMembersProviderProtocol private var suggestionItems: [UserSuggestionItemProtocol] = [] private let currentTextTriggerSubject = CurrentValueSubject(nil) @@ -61,13 +61,13 @@ class UserSuggestionService: UserSuggestionServiceProtocol { // MARK: - Setup - init(roomMembersProvider: RoomMembersProviderProtocol) { - self.roomMembersProvider = roomMembersProvider + init(roomMemberProvider: RoomMembersProviderProtocol) { + self.roomMemberProvider = roomMemberProvider currentTextTriggerSubject .debounce(for: 0.5, scheduler: RunLoop.main) .removeDuplicates() - .sink { self.fetchAndFilterMembersForTextTrigger($0) } + .sink { [weak self] in self?.fetchAndFilterMembersForTextTrigger($0) } .store(in: &cancellables) } @@ -96,7 +96,7 @@ class UserSuggestionService: UserSuggestionServiceProtocol { partialName.removeFirst() // remove the '@' prefix - roomMembersProvider.fetchMembers { [weak self] members in + roomMemberProvider.fetchMembers { [weak self] members in guard let self = self else { return }