diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index d507125294..c105f7ebfa 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/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index 6dab241e02..5e6c9928e9 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.7 +CURRENT_PROJECT_VERSION = 1.6.7 diff --git a/Podfile b/Podfile index dec1aaefba..f2fab251b8 100644 --- a/Podfile +++ b/Podfile @@ -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 35812152a8..74d91f39dc 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 a69d70fe66..a7cfab75b3 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 8446263626..0ff5e437d1 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/Generated/Strings.swift b/Riot/Generated/Strings.swift index 54ca11b35d..f3622c6084 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 ffefe0d994..b2dbbfc774 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/Modules/Application/AppCoordinator.swift b/Riot/Modules/Application/AppCoordinator.swift index 32121a0f6b..d60faf924d 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 b0c993f1e8..6a475073a8 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 f47f08a9df..585f79c5f6 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.m +++ b/Riot/Modules/Common/Recents/RecentsViewController.m @@ -50,13 +50,13 @@ @interface RecentsViewController () isRefreshPending]; }]; @@ -170,6 +174,8 @@ - (void)viewDidLoad // 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 @@ - (void)viewWillAppear:(BOOL)animated [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 @@ - (void)viewWillAppear:(BOOL)animated // Observe kMXNotificationCenterDidUpdateRules to refresh missed messages counts kMXNotificationCenterDidUpdateRulesObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXNotificationCenterDidUpdateRules object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + MXStrongifyAndReturnIfNil(self); + [self refreshRecentsTable]; }]; diff --git a/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift b/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift index cda8f716de..1fbc043715 100644 --- a/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift +++ b/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift @@ -440,7 +440,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/SegmentedViewController/SegmentedViewController.m b/Riot/Modules/Common/SegmentedViewController/SegmentedViewController.m index 8480205af8..56b3caf917 100644 --- a/Riot/Modules/Common/SegmentedViewController/SegmentedViewController.m +++ b/Riot/Modules/Common/SegmentedViewController/SegmentedViewController.m @@ -50,7 +50,7 @@ @interface SegmentedViewController () NSLayoutConstraint *leftMarkerViewConstraint; // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. - id kThemeServiceDidChangeThemeNotificationObserver; + __weak id kThemeServiceDidChangeThemeNotificationObserver; } @end @@ -183,9 +183,13 @@ - (void)viewDidLoad [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 916c344ee8..d58d78c2f3 100644 --- a/Riot/Modules/Communities/GroupsViewController.m +++ b/Riot/Modules/Communities/GroupsViewController.m @@ -27,10 +27,10 @@ @interface GroupsViewController () 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 @@ @interface GroupsViewController () UISearchBar *tableSearchBar; // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. - id kThemeServiceDidChangeThemeNotificationObserver; + __weak id kThemeServiceDidChangeThemeNotificationObserver; } @end @@ -98,11 +98,15 @@ - (void)viewDidLoad 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 @@ - (void)viewDidLoad // 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 @@ - (void)viewWillAppear:(BOOL)animated [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 27c3d7df02..976c188efd 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 8b535495b5..f1b3effaf4 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 adbc1f9e98..f208f6f671 100644 --- a/Riot/Modules/Contacts/ContactsTableViewController.m +++ b/Riot/Modules/Contacts/ContactsTableViewController.m @@ -33,12 +33,12 @@ @interface ContactsTableViewController () /** 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 @@ Current alert (if any). /** 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 @@ - (void)viewDidLoad 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 @@ - (void)registerOnContactChangeNotifications // 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 e68aedb514..d89e606356 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/Integrations/Widgets/Jitsi/JitsiViewController.h b/Riot/Modules/Integrations/Widgets/Jitsi/JitsiViewController.h index 653608c477..2fb2b368ff 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 9817f503d4..a83ba10dac 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -44,7 +44,7 @@ @interface RoomDataSource() 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 009b77e229..6111b69391 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 c0b8dc3f8e..757bb2fca3 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 dd3ca7d2ff..4764e03e97 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,7 +94,8 @@ 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 { self.loadRoom(withId: self.parameters.roomId, and: eventId, completion: completion) diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift index f65c06071c..ccfe76a7e8 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 daa9b29479..3590b5526a 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 5b4ad0c2bc..6d989d669b 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -144,7 +144,7 @@ @interface RoomViewController () *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 @@ @interface RoomViewController () *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 @@ - (void)viewDidLoad 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 @@ - (void)viewWillAppear:(BOOL)animated [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 @@ - (void)viewDidAppear:(BOOL)animated [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 @@ - (void)viewDidAppear:(BOOL)animated // 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]) @@ -1398,8 +1412,7 @@ - (void)destroy } if (URLPreviewDidUpdateNotificationObserver) { - [NSNotificationCenter.defaultCenter removeObserver:URLPreviewDidUpdateNotificationObserver]; - URLPreviewDidUpdateNotificationObserver = nil; + [NSNotificationCenter.defaultCenter removeObserver:URLPreviewDidUpdateNotificationObserver]; } [self removeCallNotificationsListeners]; @@ -1555,8 +1568,12 @@ - (BOOL)canEditJitsiWidget - (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]) { @@ -2240,6 +2257,12 @@ - (void)setupUserSuggestionView } UIViewController *suggestionsViewController = self.userSuggestionCoordinator.toPresentable; + + if (!suggestionsViewController) + { + return; + } + [suggestionsViewController.view setTranslatesAutoresizingMaskIntoConstraints:NO]; [self addChildViewController:suggestionsViewController]; @@ -2336,11 +2359,14 @@ - (void)showPreviewHeader:(BOOL)isVisible 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 @@ -4768,10 +4794,14 @@ - (void)removeCallNotificationsListeners - (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 +4809,20 @@ - (void)listenCallNotifications }]; 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]; @@ -5373,7 +5407,7 @@ - (void)showEncryptionInformation:(MXEvent *)event // 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 +5417,8 @@ - (void)showEncryptionInformation:(MXEvent *)event // Add the view and define edge constraints [self.view addSubview:encryptionInfoView]; + self->encryptionInfoView = encryptionInfoView; + [self.view addConstraint:[NSLayoutConstraint constraintWithItem:encryptionInfoView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual @@ -5839,8 +5875,12 @@ - (void)removeTombstoneEventNotificationsListener - (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) { diff --git a/Riot/Modules/Room/RoomViewController.xib b/Riot/Modules/Room/RoomViewController.xib index 1c8f839135..b1f933efe1 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 b1168107bb..14c1460b5b 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 b6a17df468..08374baea9 100644 --- a/Riot/Modules/Room/Search/RoomSearchViewController.m +++ b/Riot/Modules/Room/Search/RoomSearchViewController.m @@ -37,6 +37,13 @@ @interface RoomSearchViewController () @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 729b3c2e7a..8a10976476 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? + private var heightConstraint: NSLayoutConstraint! + + weak var toolbarDelegate: RoomInputToolbarTextViewDelegate? + + var placeholder: String? { + didSet { + setNeedsDisplay() + } + } - override var keyCommands: [UIKeyCommand]? { - return [UIKeyCommand(input: "\r", modifierFlags: [], action: #selector(keyCommandSelector(_:)))] + var placeholderColor: UIColor = UIColor(white: 0.8, alpha: 1.0) { + didSet { + setNeedsDisplay() + } } - @objc private func keyCommandSelector(_ keyCommand: UIKeyCommand) { - guard keyCommand.input == "\r", let delegate = (self.delegate as? RoomInputToolbarView) else { + 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 3099fd50f5..d126ba22f0 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 8cc8899401..bf1bda55cf 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 @@ @interface RoomInputToolbarView() { // 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 @@ @interface SettingsViewController () 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 e49a548790..4fd2cfef62 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 cb8fbaa6c3..63bf354a20 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 880dbd9ee1..25a0aa3333 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 a06a92e365..aa064c5a5a 100644 --- a/RiotShareExtension/Shared/ShareDataSource.m +++ b/RiotShareExtension/Shared/ShareDataSource.m @@ -86,7 +86,7 @@ - (void)loadCellData 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 5ca23a2b12..fdec722ed4 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 @@ - (void)sendContentToRooms:(NSArray *)rooms success:(void(^)(void))suc 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 @@ - (void)checkUserAccount } } -- (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 @@ - (UIAlertController *)compressionPromptForPendingImagesWithShareBlock:(void(^)( return nil; } - BOOL isAPendingImageNotOrientedUp = [self isAPendingImageNotOrientedUp]; - NSData *firstImageData = self.pendingImages.firstObject; UIImage *firstImage = [UIImage imageWithData:firstImageData]; @@ -451,16 +464,8 @@ - (UIAlertController *)compressionPromptForPendingImagesWithShareBlock:(void(^)( 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 @@ - (UIAlertController *)compressionPromptForPendingImagesWithShareBlock:(void(^)( MXStrongifyAndReturnIfNil(self); self.imageCompressionMode = ImageCompressionModeSmall; - [self logCompressionSizeChoice:compressionSizes.large]; + [self logCompressionSizeChoice:compressionSizes.small]; shareBlock(); }]]; @@ -495,7 +500,7 @@ - (UIAlertController *)compressionPromptForPendingImagesWithShareBlock:(void(^)( MXStrongifyAndReturnIfNil(self); self.imageCompressionMode = ImageCompressionModeMedium; - [self logCompressionSizeChoice:compressionSizes.large]; + [self logCompressionSizeChoice:compressionSizes.medium]; shareBlock(); }]]; @@ -520,8 +525,8 @@ - (UIAlertController *)compressionPromptForPendingImagesWithShareBlock:(void(^)( }]]; } - // 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 @@ - (UIAlertController *)compressionPromptForPendingImagesWithShareBlock:(void(^)( MXStrongifyAndReturnIfNil(self); self.imageCompressionMode = ImageCompressionModeNone; - [self logCompressionSizeChoice:compressionSizes.large]; + [self logCompressionSizeChoice:compressionSizes.original]; shareBlock(); }]]; @@ -626,46 +631,6 @@ - (CGSize)imageSizeFromImageData:(NSData*)imageData 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 @@ - (void)sendVideo:(NSURL *)videoLocalUrl 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 @@ - (void)sendVideo:(NSURL *)videoLocalUrl 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 @@ - (void)sendImageData:(NSData *)imageData 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 31beb9cbf2..ed7eb32a82 100644 --- a/RiotShareExtension/Shared/View/RecentRoomTableViewCell.m +++ b/RiotShareExtension/Shared/View/RecentRoomTableViewCell.m @@ -57,6 +57,7 @@ - (void)awakeFromNib 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 @@ - (void)render:(MXKCellData *)cellData [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 fe39d2a67e..a61bc36e61 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 df80ab563d..6a83e25c5e 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 bc50aae42c..e7308710d5 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 7897c608dc..7d4180201f 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 ad9de7e569..6e4fab347c 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 } diff --git a/changelog.d/4384.change b/changelog.d/4384.change new file mode 100644 index 0000000000..9103529a3a --- /dev/null +++ b/changelog.d/4384.change @@ -0,0 +1 @@ +Using mutable room list fetch sort options after chaning them to be a structure. \ No newline at end of file diff --git a/changelog.d/4815.change b/changelog.d/4815.change new file mode 100644 index 0000000000..757ff6d222 --- /dev/null +++ b/changelog.d/4815.change @@ -0,0 +1 @@ +Share Extension: Remove the image compression prompt when the showMediaSizeSelection setting is disabled. \ No newline at end of file diff --git a/changelog.d/4976.change b/changelog.d/4976.change new file mode 100644 index 0000000000..7c52865d37 --- /dev/null +++ b/changelog.d/4976.change @@ -0,0 +1 @@ +Replaced GrowingTextView with simpler, custom implementation. Cleaned up the RoomInputToolbar header. \ No newline at end of file diff --git a/changelog.d/4987.misc b/changelog.d/4987.misc new file mode 100644 index 0000000000..ac7a294f35 --- /dev/null +++ b/changelog.d/4987.misc @@ -0,0 +1 @@ +Improve wording around rageshakes in the defect issue template. diff --git a/changelog.d/5041.bugfix b/changelog.d/5041.bugfix new file mode 100644 index 0000000000..07882c0732 --- /dev/null +++ b/changelog.d/5041.bugfix @@ -0,0 +1 @@ +Fixed share extension and message forwarding room list accessory view icon. \ No newline at end of file diff --git a/changelog.d/5042.bugfix b/changelog.d/5042.bugfix new file mode 100644 index 0000000000..481c4c0535 --- /dev/null +++ b/changelog.d/5042.bugfix @@ -0,0 +1 @@ +Fixed message composer not following keyboard when swiping to dismiss. \ No newline at end of file diff --git a/changelog.d/5055.bugfix b/changelog.d/5055.bugfix new file mode 100644 index 0000000000..a969f4fef2 --- /dev/null +++ b/changelog.d/5055.bugfix @@ -0,0 +1 @@ +RoomVC: Fix retain cycles that prevents `RoomViewController` to be deallocated. \ No newline at end of file diff --git a/changelog.d/5057.bugfix b/changelog.d/5057.bugfix new file mode 100644 index 0000000000..b3f2b97446 --- /dev/null +++ b/changelog.d/5057.bugfix @@ -0,0 +1 @@ +Share Extension: Fix missing avatars and don't list spaces as rooms. \ No newline at end of file diff --git a/changelog.d/5058.bugfix b/changelog.d/5058.bugfix new file mode 100644 index 0000000000..1e8112a369 --- /dev/null +++ b/changelog.d/5058.bugfix @@ -0,0 +1 @@ +Fix retain cycles that prevents deallocation in several classes. \ No newline at end of file diff --git a/changelog.d/5063.bugfix b/changelog.d/5063.bugfix new file mode 100644 index 0000000000..d1ee811c17 --- /dev/null +++ b/changelog.d/5063.bugfix @@ -0,0 +1 @@ +Fixed retain cycles between the user suggestion coordinator and the suggestion service, and in the suggestion service currentTextTrigger subject sink. \ No newline at end of file diff --git a/changelog.d/5084.bugfix b/changelog.d/5084.bugfix new file mode 100644 index 0000000000..c7826659d8 --- /dev/null +++ b/changelog.d/5084.bugfix @@ -0,0 +1 @@ +Do not make the placeholder appearing when leaving a room on iPhone. \ No newline at end of file