2014-10-02 15:02:47 +00:00
/ *
Copyright 2014 OpenMarket Ltd
2017-03-07 14:40:07 +00:00
Copyright 2017 Vector Creations Ltd
2018-03-30 10:04:50 +00:00
Copyright 2018 New Vector Ltd
2017-07-14 14:41:25 +00:00
2014-10-02 15:02:47 +00:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : // www . apache . org / licenses / LICENSE -2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2014-11-17 16:32:46 +00:00
2014-10-03 17:26:39 +00:00
# import "RoomViewController.h"
2015-03-31 13:09:17 +00:00
2015-12-11 13:01:56 +00:00
# import "RoomDataSource.h"
2017-06-01 15:20:08 +00:00
# import "RoomBubbleCellData.h"
2015-12-11 13:01:56 +00:00
2015-08-18 08:04:30 +00:00
# import "RoomInputToolbarView.h"
2018-02-21 15:10:38 +00:00
# import "DisabledRoomInputToolbarView.h"
2015-08-18 08:04:30 +00:00
2015-11-24 12:34:08 +00:00
# import "RoomActivitiesView.h"
2015-11-18 13:42:25 +00:00
2016-06-13 15:28:17 +00:00
# import "AttachmentsViewController.h"
2017-08-14 18:01:59 +00:00
# import "EventDetailsView.h"
2016-02-10 09:59:48 +00:00
# import "RoomAvatarTitleView.h"
# import "ExpandedRoomTitleView.h"
2016-03-04 13:14:08 +00:00
# import "SimpleRoomTitleView.h"
2016-04-14 00:34:30 +00:00
# import "PreviewRoomTitleView.h"
2015-11-30 13:34:12 +00:00
2016-05-03 21:30:46 +00:00
# import "RoomMemberDetailsViewController.h"
2016-08-26 14:00:32 +00:00
# import "ContactDetailsViewController.h"
2015-09-11 11:03:20 +00:00
2015-11-26 15:47:24 +00:00
# import "SegmentedViewController.h"
# import "RoomSettingsViewController.h"
2016-10-12 10:03:39 +00:00
# import "RoomFilesViewController.h"
2015-12-31 09:12:45 +00:00
# import "RoomSearchViewController.h"
2015-11-20 13:12:37 +00:00
2017-02-14 17:21:46 +00:00
# import "UsersDevicesViewController.h"
2017-06-21 18:28:16 +00:00
# import "ReadReceiptsViewController.h"
2017-08-04 11:47:50 +00:00
# import "JitsiViewController.h"
2017-06-13 15:43:32 +00:00
# import "RoomEmptyBubbleCell.h"
2015-12-11 13:01:56 +00:00
# import "RoomIncomingTextMsgBubbleCell.h"
# import "RoomIncomingTextMsgWithoutSenderInfoBubbleCell.h"
2015-12-08 09:10:59 +00:00
# import "RoomIncomingTextMsgWithPaginationTitleBubbleCell.h"
2016-01-21 16:57:02 +00:00
# import "RoomIncomingTextMsgWithoutSenderNameBubbleCell.h"
# import "RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h"
2015-12-11 13:01:56 +00:00
# import "RoomIncomingAttachmentBubbleCell.h"
# import "RoomIncomingAttachmentWithoutSenderInfoBubbleCell.h"
2015-12-08 09:10:59 +00:00
# import "RoomIncomingAttachmentWithPaginationTitleBubbleCell.h"
2016-11-08 16:31:24 +00:00
# import "RoomIncomingEncryptedTextMsgBubbleCell.h"
# import "RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.h"
# import "RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.h"
# import "RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.h"
# import "RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h"
# import "RoomIncomingEncryptedAttachmentBubbleCell.h"
# import "RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.h"
# import "RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.h"
2015-12-08 09:10:59 +00:00
# import "RoomOutgoingTextMsgBubbleCell.h"
2015-12-08 09:54:48 +00:00
# import "RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.h"
2015-12-08 09:10:59 +00:00
# import "RoomOutgoingTextMsgWithPaginationTitleBubbleCell.h"
2016-01-21 16:57:02 +00:00
# import "RoomOutgoingTextMsgWithoutSenderNameBubbleCell.h"
# import "RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h"
2015-12-11 13:01:56 +00:00
# import "RoomOutgoingAttachmentBubbleCell.h"
# import "RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.h"
# import "RoomOutgoingAttachmentWithPaginationTitleBubbleCell.h"
2016-11-08 16:31:24 +00:00
# import "RoomOutgoingEncryptedTextMsgBubbleCell.h"
# import "RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.h"
# import "RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.h"
# import "RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.h"
# import "RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h"
# import "RoomOutgoingEncryptedAttachmentBubbleCell.h"
# import "RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.h"
# import "RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.h"
2017-07-03 15:57:10 +00:00
# import "RoomMembershipBubbleCell.h"
2017-07-10 08:25:06 +00:00
# import "RoomMembershipWithPaginationTitleBubbleCell.h"
2017-07-07 06:21:53 +00:00
# import "RoomMembershipCollapsedBubbleCell.h"
2017-07-10 12:30:53 +00:00
# import "RoomMembershipCollapsedWithPaginationTitleBubbleCell.h"
2017-07-07 09:11:42 +00:00
# import "RoomMembershipExpandedBubbleCell.h"
2017-07-10 13:05:21 +00:00
# import "RoomMembershipExpandedWithPaginationTitleBubbleCell.h"
2020-09-14 12:39:08 +00:00
# import "RoomCreationWithPaginationCollapsedBubbleCell.h"
# import "RoomCreationCollapsedBubbleCell.h"
2017-07-03 15:57:10 +00:00
2018-04-02 21:59:47 +00:00
# import "RoomSelectedStickerBubbleCell.h"
2018-08-07 09:27:37 +00:00
# import "RoomPredecessorBubbleCell.h"
2018-04-02 21:59:47 +00:00
2017-03-09 10:18:18 +00:00
# import "MXKRoomBubbleTableViewCell+Riot.h"
2015-12-07 10:50:13 +00:00
2015-12-04 13:28:15 +00:00
# import "AvatarGenerator.h"
2016-04-29 14:19:13 +00:00
# import "Tools.h"
2017-08-04 11:47:50 +00:00
# import "WidgetManager.h"
2015-12-04 13:28:15 +00:00
2016-04-26 07:49:48 +00:00
# import "GBDeviceInfo_iOS.h"
2016-11-16 08:31:58 +00:00
# import "RoomEncryptedDataBubbleCell.h"
# import "EncryptionInfoView.h"
2017-04-18 13:55:51 +00:00
# import "MXRoom+Riot.h"
2017-09-15 11:27:13 +00:00
# import "IntegrationManagerViewController.h"
2017-09-27 13:21:38 +00:00
# import "WidgetPickerViewController.h"
2018-05-24 13:31:27 +00:00
# import "StickerPickerViewController.h"
2017-09-15 11:27:13 +00:00
2018-06-08 15:21:52 +00:00
# import "EventFormatter.h"
2018-07-24 11:31:37 +00:00
# import < MatrixKit / MXKSlashCommands . h >
2018-06-08 15:21:52 +00:00
2018-07-02 12:51:47 +00:00
# import "Riot-Swift.h"
2019-09-19 15:21:03 +00:00
@ interface RoomViewController ( ) < UISearchBarDelegate , UIGestureRecognizerDelegate , UIScrollViewAccessibilityDelegate , RoomTitleViewTapGestureDelegate , RoomParticipantsViewControllerDelegate , MXKRoomMemberDetailsViewControllerDelegate , ContactsTableViewControllerDelegate , MXServerNoticesDelegate , RoomContextualMenuViewControllerDelegate ,
2019-07-30 15:18:39 +00:00
ReactionsMenuViewModelCoordinatorDelegate , EditHistoryCoordinatorBridgePresenterDelegate , MXKDocumentPickerPresenterDelegate , EmojiPickerCoordinatorBridgePresenterDelegate ,
2020-01-22 15:27:04 +00:00
ReactionHistoryCoordinatorBridgePresenterDelegate , CameraPresenterDelegate , MediaPickerCoordinatorBridgePresenterDelegate ,
2020-09-18 15:57:40 +00:00
RoomDataSourceDelegate , RoomCreationModalCoordinatorBridgePresenterDelegate , RoomInfoCoordinatorBridgePresenterDelegate >
2015-06-16 11:54:12 +00:00
{
2016-02-10 09:59:48 +00:00
2016-04-14 00:34:30 +00:00
// The preview header
PreviewRoomTitleView * previewHeader ;
2015-12-11 13:01:56 +00:00
// The customized room data source for Vector
RoomDataSource * customizedRoomDataSource ;
2016-08-26 14:00:32 +00:00
// The user taps on a user id contained in a message
MXKContact * selectedContact ;
2017-07-14 14:41:25 +00:00
2015-11-18 08:38:55 +00:00
// List of members who are typing in the room .
NSArray * currentTypingUsers ;
// Typing notifications listener .
id typingNotifListener ;
2016-04-06 07:41:23 +00:00
2016-04-20 12:09:14 +00:00
// The position of the first touch down event stored in case of scrolling when the expanded header is visible .
CGPoint startScrollingPoint ;
2016-05-19 15:22:29 +00:00
2016-08-31 18:57:08 +00:00
// Missed discussions badge
2016-08-31 13:12:46 +00:00
NSUInteger missedDiscussionsCount ;
2016-09-05 12:00:56 +00:00
NSUInteger missedHighlightCount ;
2016-08-31 13:12:46 +00:00
UIBarButtonItem * missedDiscussionsButton ;
UILabel * missedDiscussionsBadgeLabel ;
UIView * missedDiscussionsBadgeLabelBgView ;
2016-08-31 18:57:08 +00:00
UIView * missedDiscussionsBarButtonCustomView ;
2016-08-31 13:12:46 +00:00
2016-11-16 08:31:58 +00:00
// Potential encryption details view .
EncryptionInfoView * encryptionInfoView ;
2017-07-14 14:41:25 +00:00
2017-02-14 17:21:46 +00:00
// The list of unknown devices that prevent outgoing messages from being sent
MXUsersDevicesMap < MXDeviceInfo * > * unknownDevices ;
2016-11-16 08:31:58 +00:00
2016-05-19 15:22:29 +00:00
// Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar .
id kAppDelegateDidTapStatusBarNotificationObserver ;
2016-08-11 08:38:10 +00:00
// Observe kAppDelegateNetworkStatusDidChangeNotification to handle network status change .
id kAppDelegateNetworkStatusDidChangeNotificationObserver ;
2018-08-21 16:25:56 +00:00
// Observers to manage MXSession state ( and sync errors )
id kMXSessionStateDidChangeObserver ;
2016-08-08 16:40:22 +00:00
// Observers to manage ongoing conference call banner
2016-08-09 10:47:52 +00:00
id kMXCallStateDidChangeObserver ;
2016-08-08 16:40:22 +00:00
id kMXCallManagerConferenceStartedObserver ;
id kMXCallManagerConferenceFinishedObserver ;
2017-08-09 15:31:15 +00:00
// Observers to manage widgets
id kMXKWidgetManagerDidUpdateWidgetObserver ;
2016-08-31 13:12:46 +00:00
2017-04-18 13:55:51 +00:00
// Observer kMXRoomSummaryDidChangeNotification to keep updated the missed discussion count
id mxRoomSummaryDidChangeObserver ;
2018-06-12 16:21:04 +00:00
// Observer for removing the re - request explanation / waiting dialog
id mxEventDidDecryptNotificationObserver ;
2017-06-01 15:20:08 +00:00
// The table view cell in which the read marker is displayed ( nil by default ) .
MXKRoomBubbleTableViewCell * readMarkerTableViewCell ;
// Tell whether the view controller is appeared or not .
BOOL isAppeared ;
2017-06-22 08:51:40 +00:00
2017-09-15 11:27:13 +00:00
// The right bar button items back up .
NSArray < UIBarButtonItem * > * rightBarButtonItems ;
2019-01-11 09:32:56 +00:00
// Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change .
id kThemeServiceDidChangeThemeNotificationObserver ;
2018-07-23 14:56:03 +00:00
2018-07-27 09:56:51 +00:00
// Listener for ` m . room . tombstone` event type
id tombstoneEventNotificationsListener ;
2018-10-02 13:30:00 +00:00
// Homeserver notices
MXServerNotices * serverNotices ;
2020-07-14 12:39:04 +00:00
// Formatted body parser for events
FormattedBodyParser * formattedBodyParser ;
2020-11-11 21:49:46 +00:00
// Time to display notification content in the timeline
MXTaskProfile * notificationTaskProfile ;
2014-10-14 15:56:03 +00:00
}
2014-10-02 15:02:47 +00:00
2019-05-15 21:24:34 +00:00
@ property ( nonatomic , weak ) IBOutlet UIView * overlayContainerView ;
2019-06-27 12:37:17 +00:00
@ property ( nonatomic , strong ) RoomContextualMenuViewController * roomContextualMenuViewController ;
2019-05-15 21:24:34 +00:00
@ property ( nonatomic , strong ) RoomContextualMenuPresenter * roomContextualMenuPresenter ;
2019-06-11 15:43:32 +00:00
@ property ( nonatomic , strong ) MXKErrorAlertPresentation * errorPresenter ;
2019-06-13 14:38:20 +00:00
@ property ( nonatomic , strong ) NSString * textMessageBeforeEditing ;
2019-07-04 17:24:15 +00:00
@ property ( nonatomic , strong ) EditHistoryCoordinatorBridgePresenter * editHistoryPresenter ;
2019-07-11 17:17:55 +00:00
@ property ( nonatomic , strong ) MXKDocumentPickerPresenter * documentPickerPresenter ;
2019-07-25 14:46:35 +00:00
@ property ( nonatomic , strong ) EmojiPickerCoordinatorBridgePresenter * emojiPickerCoordinatorBridgePresenter ;
2019-07-30 15:18:39 +00:00
@ property ( nonatomic , strong ) ReactionHistoryCoordinatorBridgePresenter * reactionHistoryCoordinatorBridgePresenter ;
2019-08-02 15:19:29 +00:00
@ property ( nonatomic , strong ) CameraPresenter * cameraPresenter ;
@ property ( nonatomic , strong ) MediaPickerCoordinatorBridgePresenter * mediaPickerPresenter ;
2019-12-03 18:58:51 +00:00
@ property ( nonatomic , strong ) RoomMessageURLParser * roomMessageURLParser ;
2020-09-14 16:44:30 +00:00
@ property ( nonatomic , strong ) RoomCreationModalCoordinatorBridgePresenter * roomCreationModalCoordinatorBridgePresenter ;
2020-09-18 15:57:40 +00:00
@ property ( nonatomic , strong ) RoomInfoCoordinatorBridgePresenter * roomInfoCoordinatorBridgePresenter ;
2019-05-15 21:24:34 +00:00
2014-10-02 15:02:47 +00:00
@ end
2014-10-03 17:26:39 +00:00
@ implementation RoomViewController
2016-06-10 15:15:42 +00:00
@ synthesize roomPreviewData ;
2014-10-02 15:02:47 +00:00
2016-02-10 09:59:48 +00:00
# pragma mark - Class methods
+ ( UINib * ) nib
{
return [ UINib nibWithNibName : NSStringFromClass ( self . class )
bundle : [ NSBundle bundleForClass : self . class ] ] ;
}
+ ( instancetype ) roomViewController
{
return [ [ [ self class ] alloc ] initWithNibName : NSStringFromClass ( self . class )
bundle : [ NSBundle bundleForClass : self . class ] ] ;
}
# pragma mark -
2016-04-14 00:34:30 +00:00
- ( instancetype ) initWithNibName : ( nullable NSString * ) nibNameOrNil bundle : ( nullable NSBundle * ) nibBundleOrNil
{
self = [ super initWithNibName : nibNameOrNil bundle : nibBundleOrNil ] ;
if ( self )
{
// Disable auto join
self . autoJoinInvitedRoom = NO ;
2016-09-08 12:34:10 +00:00
// Disable auto scroll to bottom on keyboard presentation
self . scrollHistoryToTheBottomOnKeyboardPresentation = NO ;
2016-04-14 00:34:30 +00:00
}
return self ;
}
- ( nullable instancetype ) initWithCoder : ( NSCoder * ) aDecoder
{
self = [ super initWithCoder : aDecoder ] ;
if ( self )
{
// Disable auto join
self . autoJoinInvitedRoom = NO ;
2016-09-08 12:34:10 +00:00
// Disable auto scroll to bottom on keyboard presentation
self . scrollHistoryToTheBottomOnKeyboardPresentation = NO ;
2016-04-14 00:34:30 +00:00
}
return self ;
}
# pragma mark -
2017-01-03 13:40:23 +00:00
- ( void ) finalizeInit
2015-06-16 11:54:12 +00:00
{
2017-01-03 13:40:23 +00:00
[ super finalizeInit ] ;
2015-02-23 14:46:57 +00:00
2017-01-03 13:40:23 +00:00
// Setup ` MXKViewControllerHandling` properties
2016-02-10 21:12:17 +00:00
self . enableBarTintColorStatusChange = NO ;
2017-01-03 13:40:23 +00:00
self . rageShakeManager = [ RageShakeManager sharedManager ] ;
2020-07-14 12:39:04 +00:00
formattedBodyParser = [ FormattedBodyParser new ] ;
2017-07-14 14:41:25 +00:00
2018-01-16 22:49:04 +00:00
_showMissedDiscussionsBadge = YES ;
2017-02-14 10:01:38 +00:00
// Listen to the event sent state changes
[ [ NSNotificationCenter defaultCenter ] addObserver : self selector : @ selector ( eventDidChangeSentState : ) name : kMXEventDidChangeSentStateNotification object : nil ] ;
2019-06-27 10:25:01 +00:00
[ [ NSNotificationCenter defaultCenter ] addObserver : self selector : @ selector ( eventDidChangeIdentifier : ) name : kMXEventDidChangeIdentifierNotification object : nil ] ;
2017-01-03 13:40:23 +00:00
}
- ( void ) viewDidLoad
{
[ super viewDidLoad ] ;
2016-02-10 21:12:17 +00:00
2015-12-07 12:57:21 +00:00
// Register first customized cell view classes used to render bubbles
2015-12-11 13:01:56 +00:00
[ self . bubblesTableView registerClass : RoomIncomingTextMsgBubbleCell . class forCellReuseIdentifier : RoomIncomingTextMsgBubbleCell . defaultReuseIdentifier ] ;
[ self . bubblesTableView registerClass : RoomIncomingTextMsgWithoutSenderInfoBubbleCell . class forCellReuseIdentifier : RoomIncomingTextMsgWithoutSenderInfoBubbleCell . defaultReuseIdentifier ] ;
2015-12-08 09:10:59 +00:00
[ self . bubblesTableView registerClass : RoomIncomingTextMsgWithPaginationTitleBubbleCell . class forCellReuseIdentifier : RoomIncomingTextMsgWithPaginationTitleBubbleCell . defaultReuseIdentifier ] ;
2015-12-11 13:01:56 +00:00
[ self . bubblesTableView registerClass : RoomIncomingAttachmentBubbleCell . class forCellReuseIdentifier : RoomIncomingAttachmentBubbleCell . defaultReuseIdentifier ] ;
[ self . bubblesTableView registerClass : RoomIncomingAttachmentWithoutSenderInfoBubbleCell . class forCellReuseIdentifier : RoomIncomingAttachmentWithoutSenderInfoBubbleCell . defaultReuseIdentifier ] ;
2015-12-08 09:10:59 +00:00
[ self . bubblesTableView registerClass : RoomIncomingAttachmentWithPaginationTitleBubbleCell . class forCellReuseIdentifier : RoomIncomingAttachmentWithPaginationTitleBubbleCell . defaultReuseIdentifier ] ;
2016-01-21 16:57:02 +00:00
[ self . bubblesTableView registerClass : RoomIncomingTextMsgWithoutSenderNameBubbleCell . class forCellReuseIdentifier : RoomIncomingTextMsgWithoutSenderNameBubbleCell . defaultReuseIdentifier ] ;
[ self . bubblesTableView registerClass : RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell . class forCellReuseIdentifier : RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell . defaultReuseIdentifier ] ;
2015-12-08 09:10:59 +00:00
2016-11-08 16:31:24 +00:00
[ self . bubblesTableView registerClass : RoomIncomingEncryptedTextMsgBubbleCell . class forCellReuseIdentifier : RoomIncomingEncryptedTextMsgBubbleCell . defaultReuseIdentifier ] ;
[ self . bubblesTableView registerClass : RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell . class forCellReuseIdentifier : RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell . defaultReuseIdentifier ] ;
[ self . bubblesTableView registerClass : RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell . class forCellReuseIdentifier : RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell . defaultReuseIdentifier ] ;
[ self . bubblesTableView registerClass : RoomIncomingEncryptedAttachmentBubbleCell . class forCellReuseIdentifier : RoomIncomingEncryptedAttachmentBubbleCell . defaultReuseIdentifier ] ;
[ self . bubblesTableView registerClass : RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell . class forCellReuseIdentifier : RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell . defaultReuseIdentifier ] ;
[ self . bubblesTableView registerClass : RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell . class forCellReuseIdentifier : RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell . defaultReuseIdentifier ] ;
[ self . bubblesTableView registerClass : RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell . class forCellReuseIdentifier : RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell . defaultReuseIdentifier ] ;
[ self . bubblesTableView registerClass : RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell . class forCellReuseIdentifier : RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell . defaultReuseIdentifier ] ;
2015-12-08 09:10:59 +00:00
[ self . bubblesTableView registerClass : RoomOutgoingAttachmentBubbleCell . class forCellReuseIdentifier : RoomOutgoingAttachmentBubbleCell . defaultReuseIdentifier ] ;
2015-12-08 09:54:48 +00:00
[ self . bubblesTableView registerClass : RoomOutgoingAttachmentWithoutSenderInfoBubbleCell . class forCellReuseIdentifier : RoomOutgoingAttachmentWithoutSenderInfoBubbleCell . defaultReuseIdentifier ] ;
2015-12-08 09:10:59 +00:00
[ self . bubblesTableView registerClass : RoomOutgoingAttachmentWithPaginationTitleBubbleCell . class forCellReuseIdentifier : RoomOutgoingAttachmentWithPaginationTitleBubbleCell . defaultReuseIdentifier ] ;
[ self . bubblesTableView registerClass : RoomOutgoingTextMsgBubbleCell . class forCellReuseIdentifier : RoomOutgoingTextMsgBubbleCell . defaultReuseIdentifier ] ;
2015-12-08 09:54:48 +00:00
[ self . bubblesTableView registerClass : RoomOutgoingTextMsgWithoutSenderInfoBubbleCell . class forCellReuseIdentifier : RoomOutgoingTextMsgWithoutSenderInfoBubbleCell . defaultReuseIdentifier ] ;
2015-12-08 09:10:59 +00:00
[ self . bubblesTableView registerClass : RoomOutgoingTextMsgWithPaginationTitleBubbleCell . class forCellReuseIdentifier : RoomOutgoingTextMsgWithPaginationTitleBubbleCell . defaultReuseIdentifier ] ;
2016-01-21 16:57:02 +00:00
[ self . bubblesTableView registerClass : RoomOutgoingTextMsgWithoutSenderNameBubbleCell . class forCellReuseIdentifier : RoomOutgoingTextMsgWithoutSenderNameBubbleCell . defaultReuseIdentifier ] ;
[ self . bubblesTableView registerClass : RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell . class forCellReuseIdentifier : RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell . defaultReuseIdentifier ] ;
2015-12-07 12:57:21 +00:00
2016-11-08 16:31:24 +00:00
[ self . bubblesTableView registerClass : RoomOutgoingEncryptedAttachmentBubbleCell . class forCellReuseIdentifier : RoomOutgoingEncryptedAttachmentBubbleCell . defaultReuseIdentifier ] ;
[ self . bubblesTableView registerClass : RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell . class forCellReuseIdentifier : RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell . defaultReuseIdentifier ] ;
[ self . bubblesTableView registerClass : RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell . class forCellReuseIdentifier : RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell . defaultReuseIdentifier ] ;
[ self . bubblesTableView registerClass : RoomOutgoingEncryptedTextMsgBubbleCell . class forCellReuseIdentifier : RoomOutgoingEncryptedTextMsgBubbleCell . defaultReuseIdentifier ] ;
[ self . bubblesTableView registerClass : RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell . class forCellReuseIdentifier : RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell . defaultReuseIdentifier ] ;
[ self . bubblesTableView registerClass : RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell . class forCellReuseIdentifier : RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell . defaultReuseIdentifier ] ;
[ self . bubblesTableView registerClass : RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell . class forCellReuseIdentifier : RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell . defaultReuseIdentifier ] ;
[ self . bubblesTableView registerClass : RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell . class forCellReuseIdentifier : RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell . defaultReuseIdentifier ] ;
2017-06-13 15:43:32 +00:00
[ self . bubblesTableView registerClass : RoomEmptyBubbleCell . class forCellReuseIdentifier : RoomEmptyBubbleCell . defaultReuseIdentifier ] ;
2017-07-14 14:41:25 +00:00
2017-07-03 15:57:10 +00:00
[ self . bubblesTableView registerClass : RoomMembershipBubbleCell . class forCellReuseIdentifier : RoomMembershipBubbleCell . defaultReuseIdentifier ] ;
2017-07-10 08:25:06 +00:00
[ self . bubblesTableView registerClass : RoomMembershipWithPaginationTitleBubbleCell . class forCellReuseIdentifier : RoomMembershipWithPaginationTitleBubbleCell . defaultReuseIdentifier ] ;
2017-07-07 06:21:53 +00:00
[ self . bubblesTableView registerClass : RoomMembershipCollapsedBubbleCell . class forCellReuseIdentifier : RoomMembershipCollapsedBubbleCell . defaultReuseIdentifier ] ;
2017-07-10 12:30:53 +00:00
[ self . bubblesTableView registerClass : RoomMembershipCollapsedWithPaginationTitleBubbleCell . class forCellReuseIdentifier : RoomMembershipCollapsedWithPaginationTitleBubbleCell . defaultReuseIdentifier ] ;
2017-07-07 09:11:42 +00:00
[ self . bubblesTableView registerClass : RoomMembershipExpandedBubbleCell . class forCellReuseIdentifier : RoomMembershipExpandedBubbleCell . defaultReuseIdentifier ] ;
2017-07-10 13:05:21 +00:00
[ self . bubblesTableView registerClass : RoomMembershipExpandedWithPaginationTitleBubbleCell . class forCellReuseIdentifier : RoomMembershipExpandedWithPaginationTitleBubbleCell . defaultReuseIdentifier ] ;
2017-06-13 15:43:32 +00:00
2018-04-02 21:59:47 +00:00
[ self . bubblesTableView registerClass : RoomSelectedStickerBubbleCell . class forCellReuseIdentifier : RoomSelectedStickerBubbleCell . defaultReuseIdentifier ] ;
2018-08-07 09:27:37 +00:00
[ self . bubblesTableView registerClass : RoomPredecessorBubbleCell . class forCellReuseIdentifier : RoomPredecessorBubbleCell . defaultReuseIdentifier ] ;
2018-04-02 21:59:47 +00:00
2019-12-20 09:43:07 +00:00
[ self . bubblesTableView registerClass : KeyVerificationIncomingRequestApprovalBubbleCell . class forCellReuseIdentifier : KeyVerificationIncomingRequestApprovalBubbleCell . defaultReuseIdentifier ] ;
2020-01-14 19:23:36 +00:00
[ self . bubblesTableView registerClass : KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell . class forCellReuseIdentifier : KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell . defaultReuseIdentifier ] ;
2019-12-20 09:43:07 +00:00
[ self . bubblesTableView registerClass : KeyVerificationRequestStatusBubbleCell . class forCellReuseIdentifier : KeyVerificationRequestStatusBubbleCell . defaultReuseIdentifier ] ;
2020-01-14 19:23:36 +00:00
[ self . bubblesTableView registerClass : KeyVerificationRequestStatusWithPaginationTitleBubbleCell . class forCellReuseIdentifier : KeyVerificationRequestStatusWithPaginationTitleBubbleCell . defaultReuseIdentifier ] ;
2019-12-20 09:43:07 +00:00
[ self . bubblesTableView registerClass : KeyVerificationConclusionBubbleCell . class forCellReuseIdentifier : KeyVerificationConclusionBubbleCell . defaultReuseIdentifier ] ;
2020-01-14 19:23:36 +00:00
[ self . bubblesTableView registerClass : KeyVerificationConclusionWithPaginationTitleBubbleCell . class forCellReuseIdentifier : KeyVerificationConclusionWithPaginationTitleBubbleCell . defaultReuseIdentifier ] ;
2020-09-14 12:39:08 +00:00
[ self . bubblesTableView registerClass : RoomCreationCollapsedBubbleCell . class forCellReuseIdentifier : RoomCreationCollapsedBubbleCell . defaultReuseIdentifier ] ;
[ self . bubblesTableView registerClass : RoomCreationWithPaginationCollapsedBubbleCell . class forCellReuseIdentifier : RoomCreationWithPaginationCollapsedBubbleCell . defaultReuseIdentifier ] ;
2020-10-19 19:37:28 +00:00
[ self vc_removeBackTitle ] ;
2015-08-18 08:04:30 +00:00
// Replace the default input toolbar view .
2015-12-07 12:57:21 +00:00
// Note : this operation will force the layout of subviews . That is why cell view classes must be registered before .
2018-08-27 13:32:37 +00:00
[ self updateRoomInputToolbarViewClassIfNeeded ] ;
2015-04-01 22:25:41 +00:00
2015-11-18 13:42:25 +00:00
// set extra area
2015-11-24 12:34:08 +00:00
[ self setRoomActivitiesViewClass : RoomActivitiesView . class ] ;
2015-11-18 13:42:25 +00:00
2016-06-13 15:28:17 +00:00
// Custom the attachmnet viewer
[ self setAttachmentsViewerClass : AttachmentsViewController . class ] ;
2017-08-14 18:01:59 +00:00
// Custom the event details view
[ self setEventDetailsViewClass : EventDetailsView . class ] ;
2016-03-22 16:23:44 +00:00
// Update navigation bar items
2017-09-15 11:27:13 +00:00
for ( UIBarButtonItem * barButtonItem in self . navigationItem . rightBarButtonItems )
{
barButtonItem . target = self ;
barButtonItem . action = @ selector ( onButtonPressed : ) ;
}
2017-09-15 11:36:26 +00:00
2018-01-16 22:49:04 +00:00
// Prepare missed dicussion badge ( if any )
self . showMissedDiscussionsBadge = _showMissedDiscussionsBadge ;
2016-08-31 13:12:46 +00:00
2016-04-18 13:22:57 +00:00
// Set up the room title view according to the data source ( if any )
[ self refreshRoomTitle ] ;
// Refresh tool bar if the room data source is set .
2015-11-18 14:59:04 +00:00
if ( self . roomDataSource )
{
2016-03-22 16:23:44 +00:00
[ self refreshRoomInputToolbar ] ;
}
2017-07-21 09:28:17 +00:00
2019-05-15 21:24:34 +00:00
self . roomContextualMenuPresenter = [ RoomContextualMenuPresenter new ] ;
2019-06-11 15:43:32 +00:00
self . errorPresenter = [ MXKErrorAlertPresentation new ] ;
2019-12-03 18:58:51 +00:00
self . roomMessageURLParser = [ RoomMessageURLParser new ] ;
2019-05-15 21:24:34 +00:00
2020-07-10 12:47:37 +00:00
self . jumpToLastUnreadLabel . text = NSLocalizedStringFromTable ( @ "room_jump_to_first_unread" , @ "Vector" , nil ) ;
2017-07-21 09:28:17 +00:00
// Observe user interface theme change .
2019-01-11 09:32:56 +00:00
kThemeServiceDidChangeThemeNotificationObserver = [ [ NSNotificationCenter defaultCenter ] addObserverForName : kThemeServiceDidChangeThemeNotification object : nil queue : [ NSOperationQueue mainQueue ] usingBlock : ^ ( NSNotification * notif ) {
2017-07-21 09:28:17 +00:00
[ self userInterfaceThemeDidChange ] ;
} ] ;
[ self userInterfaceThemeDidChange ] ;
}
- ( void ) userInterfaceThemeDidChange
{
2019-02-15 14:21:33 +00:00
// Consider the main navigation controller if the current view controller is embedded inside a split view controller .
UINavigationController * mainNavigationController = self . navigationController ;
if ( self . splitViewController . isCollapsed && self . splitViewController . viewControllers . count )
{
mainNavigationController = self . splitViewController . viewControllers . firstObject ;
}
2019-01-11 10:45:27 +00:00
[ ThemeService . shared . theme applyStyleOnNavigationBar : self . navigationController . navigationBar ] ;
2019-02-15 14:21:33 +00:00
if ( mainNavigationController )
{
[ ThemeService . shared . theme applyStyleOnNavigationBar : mainNavigationController . navigationBar ] ;
}
2018-11-29 16:31:41 +00:00
2019-02-15 14:21:33 +00:00
// Keep navigation bar transparent in some cases
2020-09-18 11:11:45 +00:00
if ( ! self . previewHeaderContainer . hidden )
2019-02-15 14:21:33 +00:00
{
self . navigationController . navigationBar . translucent = YES ;
mainNavigationController . navigationBar . translucent = YES ;
}
2020-08-27 09:29:39 +00:00
[ self . inputToolbarView customizeViewRendering ] ;
2019-01-11 10:45:27 +00:00
self . activityIndicator . backgroundColor = ThemeService . shared . theme . overlayBackgroundColor ;
2017-08-11 14:56:09 +00:00
// Prepare jump to last unread banner
2019-01-11 10:45:27 +00:00
self . jumpToLastUnreadBannerContainer . backgroundColor = ThemeService . shared . theme . backgroundColor ;
2020-07-10 22:56:15 +00:00
self . jumpToLastUnreadImageView . tintColor = ThemeService . shared . theme . textPrimaryColor ;
2020-07-10 12:47:37 +00:00
self . jumpToLastUnreadLabel . textColor = ThemeService . shared . theme . textPrimaryColor ;
self . jumpToLastUnreadBannerSeparatorView . backgroundColor = ThemeService . shared . theme . lineBreakColor ;
2017-08-11 14:56:09 +00:00
2019-01-11 10:45:27 +00:00
self . previewHeaderContainer . backgroundColor = ThemeService . shared . theme . headerBackgroundColor ;
2017-08-11 14:56:09 +00:00
2019-02-21 12:07:37 +00:00
missedDiscussionsBadgeLabel . textColor = ThemeService . shared . theme . baseTextPrimaryColor ;
2017-08-11 14:56:09 +00:00
missedDiscussionsBadgeLabel . font = [ UIFont boldSystemFontOfSize : 14 ] ;
missedDiscussionsBadgeLabel . backgroundColor = [ UIColor clearColor ] ;
// Check the table view style to select its bg color .
2019-01-11 10:45:27 +00:00
self . bubblesTableView . backgroundColor = ( ( self . bubblesTableView . style = = UITableViewStylePlain ) ? ThemeService . shared . theme . backgroundColor : ThemeService . shared . theme . headerBackgroundColor ) ;
2019-02-18 11:53:13 +00:00
self . bubblesTableView . separatorColor = ThemeService . shared . theme . lineBreakColor ;
2017-08-11 14:56:09 +00:00
self . view . backgroundColor = self . bubblesTableView . backgroundColor ;
if ( self . bubblesTableView . dataSource )
{
[ self . bubblesTableView reloadData ] ;
}
2020-04-08 10:58:12 +00:00
[ self setNeedsStatusBarAppearanceUpdate ] ;
2017-08-11 14:56:09 +00:00
}
- ( UIStatusBarStyle ) preferredStatusBarStyle
{
2019-01-11 10:45:27 +00:00
return ThemeService . shared . theme . statusBarStyle ;
2014-10-02 15:02:47 +00:00
}
2015-06-16 11:54:12 +00:00
- ( void ) didReceiveMemoryWarning
{
2014-10-02 15:02:47 +00:00
[ super didReceiveMemoryWarning ] ;
// Dispose of any resources that can be recreated .
}
2015-06-16 11:54:12 +00:00
- ( void ) viewWillAppear : ( BOOL ) animated
{
2014-10-13 16:53:33 +00:00
[ super viewWillAppear : animated ] ;
2017-12-22 12:19:40 +00:00
// Screen tracking
2018-06-27 07:55:06 +00:00
[ [ Analytics sharedInstance ] trackScreen : @ "ChatRoom" ] ;
2016-06-03 15:29:34 +00:00
2016-04-18 13:22:57 +00:00
// Refresh the room title view
[ self refreshRoomTitle ] ;
// Refresh tool bar if the room data source is set .
if ( self . roomDataSource )
{
[ self refreshRoomInputToolbar ] ;
}
2015-11-18 08:38:55 +00:00
[ self listenTypingNotifications ] ;
2016-08-09 10:47:52 +00:00
[ self listenCallNotifications ] ;
2017-08-09 15:31:15 +00:00
[ self listenWidgetNotifications ] ;
2018-07-27 09:56:51 +00:00
[ self listenTombstoneEventNotifications ] ;
2018-08-21 16:25:56 +00:00
[ self listenMXSessionStateChangeNotifications ] ;
2016-04-18 14:56:44 +00:00
2016-08-11 08:38:10 +00:00
// Observe kAppDelegateDidTapStatusBarNotification .
2016-05-19 15:22:29 +00:00
kAppDelegateDidTapStatusBarNotificationObserver = [ [ NSNotificationCenter defaultCenter ] addObserverForName : kAppDelegateDidTapStatusBarNotification object : nil queue : [ NSOperationQueue mainQueue ] usingBlock : ^ ( NSNotification * notif ) {
2019-10-28 17:55:55 +00:00
[ self setBubbleTableViewContentOffset : CGPointMake ( - self . bubblesTableView . mxk_adjustedContentInset . left , - self . bubblesTableView . mxk_adjustedContentInset . top ) animated : YES ] ;
2016-05-19 15:22:29 +00:00
} ] ;
2020-09-23 13:53:40 +00:00
if ( [ self . roomDataSource . roomId isEqualToString : [ LegacyAppDelegate theDelegate ] . lastNavigatedRoomIdFromPush ] )
{
[ self startActivityIndicator ] ;
[ self . roomDataSource reload ] ;
[ LegacyAppDelegate theDelegate ] . lastNavigatedRoomIdFromPush = nil ;
2020-11-11 21:49:46 +00:00
notificationTaskProfile = [ MXSDKOptions . sharedInstance . profiler startMeasuringTaskWithName : AnalyticsNoficationsTimeToDisplayContent
category : AnalyticsNoficationsCategory ] ;
2020-09-23 13:53:40 +00:00
}
2014-10-13 16:53:33 +00:00
}
2015-06-16 11:54:12 +00:00
- ( void ) viewWillDisappear : ( BOOL ) animated
{
2014-10-13 16:53:33 +00:00
[ super viewWillDisappear : animated ] ;
2014-11-04 16:26:33 +00:00
// hide action
2016-05-03 16:29:54 +00:00
if ( currentAlert )
2015-08-19 09:05:47 +00:00
{
2017-07-14 14:41:25 +00:00
[ currentAlert dismissViewControllerAnimated : NO completion : nil ] ;
2016-05-03 16:29:54 +00:00
currentAlert = nil ;
2015-08-19 09:05:47 +00:00
}
2015-11-18 08:38:55 +00:00
[ self removeTypingNotificationsListener ] ;
2015-12-11 13:01:56 +00:00
if ( customizedRoomDataSource )
{
2016-04-06 07:41:23 +00:00
// Cancel potential selected event ( to leave edition mode )
if ( customizedRoomDataSource . selectedEventId )
{
[ self cancelEventSelection ] ;
}
2015-12-11 13:01:56 +00:00
}
2016-02-10 09:59:48 +00:00
2020-09-18 11:11:45 +00:00
// Hide preview header to restore navigation bar settings
2016-04-14 00:34:30 +00:00
[ self showPreviewHeader : NO ] ;
2016-05-19 15:22:29 +00:00
if ( kAppDelegateDidTapStatusBarNotificationObserver )
{
[ [ NSNotificationCenter defaultCenter ] removeObserver : kAppDelegateDidTapStatusBarNotificationObserver ] ;
kAppDelegateDidTapStatusBarNotificationObserver = nil ;
}
2017-07-14 14:41:25 +00:00
2016-08-09 10:47:52 +00:00
[ self removeCallNotificationsListeners ] ;
2017-08-09 15:31:15 +00:00
[ self removeWidgetNotificationsListeners ] ;
2018-07-27 09:56:51 +00:00
[ self removeTombstoneEventNotificationsListener ] ;
2018-08-21 16:25:56 +00:00
[ self removeMXSessionStateChangeNotificationsListener ] ;
2017-08-09 15:31:15 +00:00
2017-06-01 15:20:08 +00:00
// Re - enable the read marker display , and disable its update .
self . roomDataSource . showReadMarker = YES ;
self . updateRoomReadMarker = NO ;
isAppeared = NO ;
2014-10-13 16:53:33 +00:00
}
2015-06-16 11:54:12 +00:00
- ( void ) viewDidAppear : ( BOOL ) animated
{
2014-10-14 15:56:03 +00:00
[ super viewDidAppear : animated ] ;
2017-06-06 11:31:58 +00:00
isAppeared = YES ;
[ self checkReadMarkerVisibility ] ;
2015-06-16 11:54:12 +00:00
if ( self . roomDataSource )
{
2015-05-12 12:29:42 +00:00
// Set visible room id
2015-11-17 23:15:52 +00:00
[ AppDelegate theDelegate ] . visibleRoomId = self . roomDataSource . roomId ;
2016-08-11 08:38:10 +00:00
}
// Observe network reachability
kAppDelegateNetworkStatusDidChangeNotificationObserver = [ [ NSNotificationCenter defaultCenter ] addObserverForName : kAppDelegateNetworkStatusDidChangeNotification object : nil queue : [ NSOperationQueue mainQueue ] usingBlock : ^ ( NSNotification * notif ) {
2016-04-01 14:35:55 +00:00
[ self refreshActivitiesViewDisplay ] ;
2016-08-11 08:38:10 +00:00
} ] ;
[ self refreshActivitiesViewDisplay ] ;
2017-06-01 15:20:08 +00:00
[ self refreshJumpToLastUnreadBannerDisplay ] ;
2016-08-31 13:12:46 +00:00
// Observe missed notifications
2017-04-18 13:55:51 +00:00
mxRoomSummaryDidChangeObserver = [ [ NSNotificationCenter defaultCenter ] addObserverForName : kMXRoomSummaryDidChangeNotification object : nil queue : [ NSOperationQueue mainQueue ] usingBlock : ^ ( NSNotification * notif ) {
2018-07-23 16:14:45 +00:00
MXRoomSummary * roomSummary = notif . object ;
if ( [ roomSummary . roomId isEqualToString : self . roomDataSource . roomId ] )
{
[ self refreshMissedDiscussionsCount : NO ] ;
}
2016-08-31 13:12:46 +00:00
} ] ;
[ self refreshMissedDiscussionsCount : YES ] ;
2020-04-17 19:38:52 +00:00
self . keyboardHeight = MAX ( self . keyboardHeight , 0 ) ;
2014-10-14 15:56:03 +00:00
}
2015-06-16 11:54:12 +00:00
- ( void ) viewDidDisappear : ( BOOL ) animated
{
2014-12-09 09:39:56 +00:00
[ super viewDidDisappear : animated ] ;
2019-05-15 21:24:34 +00:00
// Hide contextual menu if needed
[ self hideContextualMenuAnimated : NO ] ;
2014-12-09 09:39:56 +00:00
// Reset visible room id
2016-08-11 08:38:10 +00:00
[ AppDelegate theDelegate ] . visibleRoomId = nil ;
if ( kAppDelegateNetworkStatusDidChangeNotificationObserver )
2016-04-01 14:35:55 +00:00
{
2016-08-11 08:38:10 +00:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : kAppDelegateNetworkStatusDidChangeNotificationObserver ] ;
kAppDelegateNetworkStatusDidChangeNotificationObserver = nil ;
2016-04-01 14:35:55 +00:00
}
2016-08-31 13:12:46 +00:00
2017-04-18 13:55:51 +00:00
if ( mxRoomSummaryDidChangeObserver )
2016-08-31 13:12:46 +00:00
{
2017-04-18 13:55:51 +00:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : mxRoomSummaryDidChangeObserver ] ;
mxRoomSummaryDidChangeObserver = nil ;
2016-08-31 13:12:46 +00:00
}
2018-06-12 16:21:04 +00:00
if ( mxEventDidDecryptNotificationObserver )
{
[ [ NSNotificationCenter defaultCenter ] removeObserver : mxEventDidDecryptNotificationObserver ] ;
mxEventDidDecryptNotificationObserver = nil ;
}
2014-12-09 09:39:56 +00:00
}
2015-08-26 17:04:30 +00:00
- ( void ) viewDidLayoutSubviews
{
2016-09-12 08:57:44 +00:00
[ super viewDidLayoutSubviews ] ;
2015-08-26 17:04:30 +00:00
UIEdgeInsets contentInset = self . bubblesTableView . contentInset ;
contentInset . bottom = self . bottomLayoutGuide . length ;
self . bubblesTableView . contentInset = contentInset ;
2016-04-18 14:56:44 +00:00
2016-11-16 08:31:58 +00:00
// Check here whether a subview has been added or removed
if ( encryptionInfoView )
{
2020-09-18 11:11:45 +00:00
if ( ! encryptionInfoView . superview )
2016-11-16 08:31:58 +00:00
{
// Reset
encryptionInfoView = nil ;
// Reload the full table to take into account a potential change on a device status .
[ self . bubblesTableView reloadData ] ;
}
}
if ( eventDetailsView )
{
2020-09-18 11:11:45 +00:00
if ( ! eventDetailsView . superview )
2016-11-16 08:31:58 +00:00
{
// Reset
eventDetailsView = nil ;
}
}
2019-02-15 12:45:09 +00:00
2016-06-10 15:15:42 +00:00
// Check whether the preview header is visible
2020-09-18 11:11:45 +00:00
if ( previewHeader )
2016-06-08 16:22:50 +00:00
{
2016-06-10 15:15:42 +00:00
if ( previewHeader . mainHeaderContainer . isHidden )
2016-06-08 16:22:50 +00:00
{
2016-06-10 15:15:42 +00:00
// Check here the main background height to display a correct navigation bar background .
CGRect frame = self . navigationController . navigationBar . frame ;
2016-06-08 16:22:50 +00:00
2016-06-10 15:15:42 +00:00
CGFloat mainHeaderBackgroundHeight = frame . size . height + ( frame . origin . y > 0 ? frame . origin . y : 0 ) ;
2016-06-08 16:22:50 +00:00
2016-06-10 15:15:42 +00:00
if ( previewHeader . mainHeaderBackgroundHeightConstraint . constant ! = mainHeaderBackgroundHeight )
{
previewHeader . mainHeaderBackgroundHeightConstraint . constant = mainHeaderBackgroundHeight ;
// Force the layout of previewHeader to update the position of ' bottomBorderView ' which
// is used to define the actual height of the preview container .
[ previewHeader layoutIfNeeded ] ;
}
2016-06-08 16:22:50 +00:00
}
2019-02-15 12:45:09 +00:00
self . edgesForExtendedLayout = UIRectEdgeAll ;
2016-06-10 15:15:42 +00:00
// Adjust the top constraint of the bubbles table
CGRect frame = previewHeader . bottomBorderView . frame ;
self . previewHeaderContainerHeightConstraint . constant = frame . origin . y + frame . size . height ;
2019-02-15 12:45:09 +00:00
2017-09-27 07:26:34 +00:00
self . bubblesTableViewTopConstraint . constant = self . previewHeaderContainerHeightConstraint . constant - self . bubblesTableView . mxk_adjustedContentInset . top ;
2017-06-01 15:20:08 +00:00
self . jumpToLastUnreadBannerContainerTopConstraint . constant = self . previewHeaderContainerHeightConstraint . constant ;
}
else
{
2019-02-15 12:45:09 +00:00
// In non expanded header mode , the navigation bar is opaque
// The table view must not display behind it
self . edgesForExtendedLayout = UIRectEdgeLeft | UIRectEdgeBottom | UIRectEdgeRight ;
self . jumpToLastUnreadBannerContainerTopConstraint . constant = self . bubblesTableView . mxk_adjustedContentInset . top ; // no expanded
2016-06-08 16:22:50 +00:00
}
2016-08-31 13:12:46 +00:00
[ self refreshMissedDiscussionsCount : YES ] ;
2015-08-26 17:04:30 +00:00
}
2016-02-10 14:08:51 +00:00
- ( void ) viewWillTransitionToSize : ( CGSize ) size withTransitionCoordinator : ( id < UIViewControllerTransitionCoordinator > ) coordinator
{
2016-04-26 07:49:48 +00:00
// Hide the expanded header or the preview in case of iPad and iPhone 6 plus .
// On these devices , the display mode of the splitviewcontroller may change during screen rotation .
// It may correspond to an overlay mode in portrait and a side - by - side mode in landscape .
// This display mode change involves a change at the navigation bar level .
// If we don ' t hide the header , the navigation bar is in a wrong state after rotation . FIXME : Find a way to keep visible the header on rotation .
2016-07-01 06:42:10 +00:00
if ( [ GBDeviceInfo deviceInfo ] . family = = GBDeviceFamilyiPad || [ GBDeviceInfo deviceInfo ] . displayInfo . display >= GBDeviceDisplay5p5Inch )
2016-04-14 00:34:30 +00:00
{
2017-01-24 12:35:06 +00:00
// Hide the preview header ( if any ) before rotating ( It will be restored by ` refreshRoomTitle` call if this is still a room preview ) .
[ self showPreviewHeader : NO ] ;
dispatch_after ( dispatch_time ( DISPATCH_TIME _NOW , ( int64_t ) ( ( coordinator . transitionDuration + 0.5 ) * NSEC_PER _SEC ) ) , dispatch_get _main _queue ( ) , ^ {
2016-04-14 00:34:30 +00:00
2017-01-24 12:35:06 +00:00
// Let [ self refreshRoomTitle ] refresh this title view correctly
[ self refreshRoomTitle ] ;
} ) ;
2016-04-14 00:34:30 +00:00
}
2016-06-08 15:19:00 +00:00
else if ( previewHeader )
{
2016-06-08 16:22:50 +00:00
// Refresh here the preview header according to the coming screen orientation .
2016-06-08 15:19:00 +00:00
2016-06-08 16:22:50 +00:00
// Retrieve the affine transform indicating the amount of rotation being applied to the interface .
2020-08-25 16:48:09 +00:00
// This transform is the identity transform when no rotation is applied .
// Otherwise , it is a transform that applies a 90 degree , -90 degree , or 180 degree rotation .
2016-06-08 16:22:50 +00:00
CGAffineTransform transform = coordinator . targetTransform ;
2016-06-08 15:19:00 +00:00
2016-06-08 16:22:50 +00:00
// Consider here only the transform that applies a + / - 90 degree .
if ( transform . b * transform . c = = -1 )
{
UIInterfaceOrientation currentScreenOrientation = [ [ UIApplication sharedApplication ] statusBarOrientation ] ;
BOOL isLandscapeOriented = YES ;
2016-06-08 15:19:00 +00:00
2016-06-08 16:22:50 +00:00
switch ( currentScreenOrientation )
{
case UIInterfaceOrientationLandscapeRight :
case UIInterfaceOrientationLandscapeLeft :
{
// We leave here landscape orientation
isLandscapeOriented = NO ;
break ;
}
default :
break ;
}
[ self refreshPreviewHeader : isLandscapeOriented ] ;
}
2016-06-08 15:19:00 +00:00
}
2017-01-24 12:35:06 +00:00
else
{
dispatch_after ( dispatch_time ( DISPATCH_TIME _NOW , ( int64_t ) ( ( coordinator . transitionDuration + 0.5 ) * NSEC_PER _SEC ) ) , dispatch_get _main _queue ( ) , ^ {
// Refresh the room title at the end of the transition to take into account the potential changes during the transition .
// For example the display of a preview header is ignored during transition .
[ self refreshRoomTitle ] ;
} ) ;
}
2016-02-10 14:08:51 +00:00
[ super viewWillTransitionToSize : size withTransitionCoordinator : coordinator ] ;
}
2019-09-19 15:21:03 +00:00
# pragma mark - Accessibility
// Handle scrolling when VoiceOver is on because it does not work well if we let the system do :
// VoiceOver loses the focus on the tableview
- ( BOOL ) accessibilityScroll : ( UIAccessibilityScrollDirection ) direction
{
BOOL canScroll = YES ;
// Scroll by one page
CGFloat tableViewHeight = self . bubblesTableView . frame . size . height ;
CGPoint offset = self . bubblesTableView . contentOffset ;
switch ( direction )
{
case UIAccessibilityScrollDirectionUp :
offset . y - = tableViewHeight ;
break ;
case UIAccessibilityScrollDirectionDown :
offset . y + = tableViewHeight ;
break ;
default :
break ;
}
if ( offset . y < 0 && ! [ self . roomDataSource . timeline canPaginate : MXTimelineDirectionBackwards ] )
{
// Can ' t paginate more . Let ' s stick on the first item
UIView * focusedView = [ self firstCellWithAccessibilityDataInCells : self . bubblesTableView . visibleCells . objectEnumerator ] ;
UIAccessibilityPostNotification ( UIAccessibilityLayoutChangedNotification , focusedView ) ;
canScroll = NO ;
}
else if ( offset . y > self . bubblesTableView . contentSize . height - tableViewHeight
&& ! [ self . roomDataSource . timeline canPaginate : MXTimelineDirectionForwards ] )
{
// Can ' t paginate more . Let ' s stick on the last item with accessibility
UIView * focusedView = [ self firstCellWithAccessibilityDataInCells : self . bubblesTableView . visibleCells . reverseObjectEnumerator ] ;
UIAccessibilityPostNotification ( UIAccessibilityLayoutChangedNotification , focusedView ) ;
canScroll = NO ;
}
else
{
// Disable VoiceOver while scrolling
self . bubblesTableView . accessibilityElementsHidden = YES ;
2019-10-28 17:55:55 +00:00
[ self setBubbleTableViewContentOffset : offset animated : NO ] ;
2019-09-19 15:21:03 +00:00
NSEnumerator < UITableViewCell * > * cells ;
if ( direction = = UIAccessibilityScrollDirectionUp )
{
cells = self . bubblesTableView . visibleCells . objectEnumerator ;
}
else
{
cells = self . bubblesTableView . visibleCells . reverseObjectEnumerator ;
}
UIView * cell = [ self firstCellWithAccessibilityDataInCells : cells ] ;
self . bubblesTableView . accessibilityElementsHidden = NO ;
// Force VoiceOver to focus on a visible item
UIAccessibilityPostNotification ( UIAccessibilityLayoutChangedNotification , cell ) ;
}
// If we cannot scroll , let VoiceOver indicates the border
return canScroll ;
}
- ( UIView * ) firstCellWithAccessibilityDataInCells : ( NSEnumerator < UITableViewCell * > * ) cells
{
UIView * view ;
for ( UITableViewCell * cell in cells )
{
if ( ! [ cell isKindOfClass : [ RoomEmptyBubbleCell class ] ] )
{
view = cell ;
break ;
}
}
return view ;
}
2015-05-27 14:51:46 +00:00
# pragma mark - Override MXKRoomViewController
2015-01-20 11:41:27 +00:00
2017-06-01 15:20:08 +00:00
- ( void ) onMatrixSessionChange
{
[ super onMatrixSessionChange ] ;
// Re - enable the read marker display , and disable its update .
self . roomDataSource . showReadMarker = YES ;
self . updateRoomReadMarker = NO ;
}
2020-11-11 21:49:46 +00:00
- ( void ) stopActivityIndicator
{
if ( notificationTaskProfile )
{
// Consider here we have displayed the message corresponding to the notification
[ MXSDKOptions . sharedInstance . profiler stopMeasuringTaskWithProfile : notificationTaskProfile ] ;
notificationTaskProfile = nil ;
}
[ super stopActivityIndicator ] ;
}
2015-06-16 11:54:12 +00:00
- ( void ) displayRoom : ( MXKRoomDataSource * ) dataSource
{
2016-06-10 15:15:42 +00:00
// Remove potential preview Data
if ( roomPreviewData )
{
roomPreviewData = nil ;
[ self removeMatrixSession : self . mainSession ] ;
}
2017-06-06 08:53:17 +00:00
// Enable the read marker display , and disable its update .
dataSource . showReadMarker = YES ;
self . updateRoomReadMarker = NO ;
2015-05-12 12:29:42 +00:00
[ super displayRoom : dataSource ] ;
2015-08-27 09:31:17 +00:00
2016-03-22 16:23:44 +00:00
customizedRoomDataSource = nil ;
2015-12-11 13:01:56 +00:00
2016-03-22 16:23:44 +00:00
if ( self . roomDataSource )
2015-12-11 13:01:56 +00:00
{
2018-10-02 13:30:00 +00:00
[ self listenToServerNotices ] ;
2017-06-09 07:40:22 +00:00
self . eventsAcknowledgementEnabled = YES ;
2016-04-14 00:34:30 +00:00
// Set room title view
[ self refreshRoomTitle ] ;
2016-03-22 16:23:44 +00:00
// Store ref on customized room data source
if ( [ dataSource isKindOfClass : RoomDataSource . class ] )
{
customizedRoomDataSource = ( RoomDataSource * ) dataSource ;
}
2015-12-11 13:01:56 +00:00
}
2016-03-22 16:23:44 +00:00
else
2016-03-11 13:45:06 +00:00
{
2016-03-22 16:23:44 +00:00
self . navigationItem . rightBarButtonItem . enabled = NO ;
2016-03-11 13:45:06 +00:00
}
2016-03-22 16:23:44 +00:00
[ self refreshRoomInputToolbar ] ;
2015-05-12 12:29:42 +00:00
}
2016-04-14 00:34:30 +00:00
- ( void ) onRoomDataSourceReady
{
// Handle here invitation
2018-07-17 16:00:56 +00:00
if ( self . roomDataSource . room . summary . membership = = MXMembershipInvite )
2016-04-14 00:34:30 +00:00
{
self . navigationItem . rightBarButtonItem . enabled = NO ;
// Show preview header
[ self showPreviewHeader : YES ] ;
}
else
{
[ super onRoomDataSourceReady ] ;
}
}
2015-06-16 11:54:12 +00:00
- ( void ) updateViewControllerAppearanceOnRoomDataSourceState
{
2015-04-16 17:08:29 +00:00
[ super updateViewControllerAppearanceOnRoomDataSourceState ] ;
2015-09-11 13:58:08 +00:00
2016-04-14 00:34:30 +00:00
if ( self . isRoomPreview )
{
self . navigationItem . rightBarButtonItem . enabled = NO ;
2016-06-10 15:15:42 +00:00
// Remove input tool bar if any
2016-04-20 16:50:03 +00:00
if ( self . inputToolbarView )
{
[ super setRoomInputToolbarViewClass : nil ] ;
}
2016-04-14 00:34:30 +00:00
2016-04-19 13:28:15 +00:00
if ( previewHeader )
{
previewHeader . mxRoom = self . roomDataSource . room ;
2016-06-08 15:19:00 +00:00
2016-06-10 15:15:42 +00:00
// Force the layout of subviews ( some constraints may have been updated )
2016-11-25 09:50:16 +00:00
[ self forceLayoutRefresh ] ;
2016-04-19 13:28:15 +00:00
}
2016-04-14 00:34:30 +00:00
}
else
{
[ self showPreviewHeader : NO ] ;
self . navigationItem . rightBarButtonItem . enabled = ( self . roomDataSource ! = nil ) ;
self . titleView . editable = NO ;
2016-04-20 16:50:03 +00:00
2016-04-28 17:22:50 +00:00
if ( self . roomDataSource )
2016-04-20 16:50:03 +00:00
{
2016-04-28 17:22:50 +00:00
// Restore tool bar view and room activities view if none
if ( ! self . inputToolbarView )
{
2018-08-27 13:32:37 +00:00
[ self updateRoomInputToolbarViewClassIfNeeded ] ;
2016-04-28 17:22:50 +00:00
[ self refreshRoomInputToolbar ] ;
self . inputToolbarView . hidden = ( self . roomDataSource . state ! = MXKDataSourceStateReady ) ;
}
2016-04-20 16:50:03 +00:00
2016-04-28 17:22:50 +00:00
if ( ! self . activitiesView )
{
// And the extra area
[ self setRoomActivitiesViewClass : RoomActivitiesView . class ] ;
}
2016-04-20 16:50:03 +00:00
}
}
}
2016-09-01 13:28:47 +00:00
- ( void ) leaveRoomOnEvent : ( MXEvent * ) event
{
2017-06-23 13:09:43 +00:00
// Force a simple title view initialised with the current room before leaving actually the room .
[ self setRoomTitleViewClass : SimpleRoomTitleView . class ] ;
self . titleView . editable = NO ;
self . titleView . mxRoom = self . roomDataSource . room ;
// Hide the potential read marker banner .
self . jumpToLastUnreadBannerContainer . hidden = YES ;
2016-09-01 13:28:47 +00:00
[ super leaveRoomOnEvent : event ] ;
}
2018-02-21 15:10:38 +00:00
// Set the input toolbar according to the current display
2018-08-27 13:32:37 +00:00
- ( void ) updateRoomInputToolbarViewClassIfNeeded
2016-04-20 16:50:03 +00:00
{
2018-02-21 15:10:38 +00:00
Class roomInputToolbarViewClass = RoomInputToolbarView . class ;
2019-05-15 21:24:34 +00:00
BOOL shouldDismissContextualMenu = NO ;
2018-02-21 15:10:38 +00:00
// Check the user has enough power to post message
2018-07-20 09:28:02 +00:00
if ( self . roomDataSource . roomState )
2018-02-21 15:10:38 +00:00
{
2018-07-20 09:28:02 +00:00
MXRoomPowerLevels * powerLevels = self . roomDataSource . roomState . powerLevels ;
2018-02-21 15:10:38 +00:00
NSInteger userPowerLevel = [ powerLevels powerLevelOfUserWithUserID : self . mainSession . myUser . userId ] ;
2018-07-27 09:56:51 +00:00
2018-02-21 15:10:38 +00:00
BOOL canSend = ( userPowerLevel >= [ powerLevels minimumPowerLevelForSendingEventAsMessage : kMXEventTypeStringRoomMessage ] ) ;
2018-08-07 16:20:22 +00:00
BOOL isRoomObsolete = self . roomDataSource . roomState . isObsolete ;
2018-08-23 15:03:32 +00:00
BOOL isResourceLimitExceeded = [ self . roomDataSource . mxSession . syncError . errcode isEqualToString : kMXErrCodeStringResourceLimitExceeded ] ;
2018-07-27 09:56:51 +00:00
2018-08-23 15:03:32 +00:00
if ( isRoomObsolete || isResourceLimitExceeded )
2018-07-27 09:56:51 +00:00
{
roomInputToolbarViewClass = nil ;
2019-05-15 21:24:34 +00:00
shouldDismissContextualMenu = YES ;
2018-07-27 09:56:51 +00:00
}
else if ( ! canSend )
2018-02-21 15:10:38 +00:00
{
roomInputToolbarViewClass = DisabledRoomInputToolbarView . class ;
2019-05-15 21:24:34 +00:00
shouldDismissContextualMenu = YES ;
2018-02-21 15:10:38 +00:00
}
}
2016-04-20 16:50:03 +00:00
// Do not show toolbar in case of preview
if ( self . isRoomPreview )
{
roomInputToolbarViewClass = nil ;
2019-05-15 21:24:34 +00:00
shouldDismissContextualMenu = YES ;
}
if ( shouldDismissContextualMenu )
{
[ self hideContextualMenuAnimated : NO ] ;
2016-04-20 16:50:03 +00:00
}
2018-08-27 13:32:37 +00:00
// Change inputToolbarView class only if given class is different from current one
if ( ! self . inputToolbarView || ! [ self . inputToolbarView isMemberOfClass : roomInputToolbarViewClass ] )
{
[ super setRoomInputToolbarViewClass : roomInputToolbarViewClass ] ;
[ self updateInputToolBarViewHeight ] ;
}
2016-04-20 16:50:03 +00:00
}
2018-02-21 15:10:38 +00:00
// Get the height of the current room input toolbar
- ( CGFloat ) inputToolbarHeight
{
CGFloat height = 0 ;
if ( [ self . inputToolbarView isKindOfClass : RoomInputToolbarView . class ] )
{
2019-05-15 21:24:34 +00:00
height = ( ( RoomInputToolbarView * ) self . inputToolbarView ) . mainToolbarHeightConstraint . constant ;
2018-02-21 15:10:38 +00:00
}
else if ( [ self . inputToolbarView isKindOfClass : DisabledRoomInputToolbarView . class ] )
{
height = ( ( DisabledRoomInputToolbarView * ) self . inputToolbarView ) . mainToolbarMinHeightConstraint . constant ;
}
return height ;
}
2016-04-20 16:50:03 +00:00
- ( void ) setRoomActivitiesViewClass : ( Class ) roomActivitiesViewClass
{
2016-06-10 15:15:42 +00:00
// Do not show room activities in case of preview ( FIXME : show it when live events will be supported during peeking )
2016-04-20 16:50:03 +00:00
if ( self . isRoomPreview )
{
roomActivitiesViewClass = nil ;
2016-04-14 00:34:30 +00:00
}
2016-04-20 16:50:03 +00:00
[ super setRoomActivitiesViewClass : roomActivitiesViewClass ] ;
2015-01-22 10:09:14 +00:00
}
2015-06-16 11:54:12 +00:00
- ( BOOL ) isIRCStyleCommand : ( NSString * ) string
{
2015-05-27 14:51:46 +00:00
// Override the default behavior for ` / join` command in order to open automatically the joined room
2018-07-24 10:24:12 +00:00
if ( [ string hasPrefix : kMXKSlashCmdJoinRoom ] )
2015-06-16 11:54:12 +00:00
{
2015-05-27 14:51:46 +00:00
// Join a room
2016-07-01 15:06:20 +00:00
NSString * roomAlias ;
// Sanity check
2018-07-24 10:24:12 +00:00
if ( string . length > kMXKSlashCmdJoinRoom . length )
2016-07-01 15:06:20 +00:00
{
2018-07-24 10:24:12 +00:00
roomAlias = [ string substringFromIndex : kMXKSlashCmdJoinRoom . length + 1 ] ;
2016-07-01 15:06:20 +00:00
// Remove white space from both ends
roomAlias = [ roomAlias stringByTrimmingCharactersInSet : [ NSCharacterSet whitespaceCharacterSet ] ] ;
}
2015-05-27 14:51:46 +00:00
// Check
2015-06-16 11:54:12 +00:00
if ( roomAlias . length )
{
2019-07-08 09:15:44 +00:00
// TODO : / join command does not support via parameters yet
[ self . mainSession joinRoom : roomAlias viaServers : nil success : ^ ( MXRoom * room ) {
2016-06-08 15:19:00 +00:00
// Show the room
2018-07-16 20:30:55 +00:00
[ [ AppDelegate theDelegate ] showRoom : room . roomId andEventId : nil withMatrixSession : self . mainSession ] ;
2016-06-08 15:19:00 +00:00
} failure : ^ ( NSError * error ) {
2017-06-20 09:32:54 +00:00
NSLog ( @ "[RoomVC] Join roomAlias (%@) failed" , roomAlias ) ;
2016-06-08 15:19:00 +00:00
// Alert user
[ [ AppDelegate theDelegate ] showErrorAsAlert : error ] ;
} ] ;
2015-06-16 11:54:12 +00:00
}
else
{
2015-05-27 14:51:46 +00:00
// Display cmd usage in text input as placeholder
self . inputToolbarView . placeholder = @ "Usage: /join <room_alias>" ;
}
return YES ;
}
return [ super isIRCStyleCommand : string ] ;
}
2016-02-24 14:42:22 +00:00
- ( void ) setKeyboardHeight : ( CGFloat ) keyboardHeight
{
[ super setKeyboardHeight : keyboardHeight ] ;
2017-06-08 08:52:03 +00:00
// Make the activity indicator follow the keyboard
// At runtime , this creates a smooth animation
CGPoint activityIndicatorCenter = self . activityIndicator . center ;
activityIndicatorCenter . y = self . view . center . y - keyboardHeight / 2 ;
self . activityIndicator . center = activityIndicatorCenter ;
2016-02-24 14:42:22 +00:00
}
2016-11-16 08:31:58 +00:00
- ( void ) dismissTemporarySubViews
{
[ super dismissTemporarySubViews ] ;
if ( encryptionInfoView )
{
[ encryptionInfoView removeFromSuperview ] ;
encryptionInfoView = nil ;
}
}
2017-06-09 12:20:00 +00:00
- ( void ) setBubbleTableViewDisplayInTransition : ( BOOL ) bubbleTableViewDisplayInTransition
{
if ( self . isBubbleTableViewDisplayInTransition ! = bubbleTableViewDisplayInTransition )
{
[ super setBubbleTableViewDisplayInTransition : bubbleTableViewDisplayInTransition ] ;
2017-06-29 10:03:39 +00:00
// Refresh additional displays when the table is ready .
if ( ! bubbleTableViewDisplayInTransition && ! self . bubblesTableView . isHidden )
{
[ self refreshActivitiesViewDisplay ] ;
[ self checkReadMarkerVisibility ] ;
[ self refreshJumpToLastUnreadBannerDisplay ] ;
}
2017-06-09 12:20:00 +00:00
}
}
2018-07-23 14:56:03 +00:00
- ( void ) sendTextMessage : ( NSString * ) msgTxt
{
2019-04-30 15:07:43 +00:00
if ( self . inputToolBarSendMode = = RoomInputToolbarViewSendModeReply && customizedRoomDataSource . selectedEventId )
2018-07-23 14:56:03 +00:00
{
[ self . roomDataSource sendReplyToEventWithId : customizedRoomDataSource . selectedEventId withTextMessage : msgTxt success : nil failure : ^ ( NSError * error ) {
// Just log the error . The message will be displayed in red in the room history
NSLog ( @ "[MXKRoomViewController] sendTextMessage failed." ) ;
} ] ;
}
2019-06-13 14:38:20 +00:00
else if ( self . inputToolBarSendMode = = RoomInputToolbarViewSendModeEdit && customizedRoomDataSource . selectedEventId )
{
[ self . roomDataSource replaceTextMessageForEventWithId : customizedRoomDataSource . selectedEventId withTextMessage : msgTxt success : nil failure : ^ ( NSError * error ) {
// Just log the error . The message will be displayed in red
NSLog ( @ "[MXKRoomViewController] sendTextMessage failed." ) ;
} ] ;
}
2018-07-23 14:56:03 +00:00
else
{
// Let the datasource send it and manage the local echo
[ self . roomDataSource sendTextMessage : msgTxt success : nil failure : ^ ( NSError * error )
{
// Just log the error . The message will be displayed in red in the room history
NSLog ( @ "[MXKRoomViewController] sendTextMessage failed." ) ;
} ] ;
}
[ self cancelEventSelection ] ;
}
2020-04-08 13:07:24 +00:00
- ( void ) setRoomTitleViewClass : ( Class ) roomTitleViewClass
{
[ super setRoomTitleViewClass : roomTitleViewClass ] ;
[ self updateTitleViewEncryptionDecoration ] ;
}
2015-06-16 11:54:12 +00:00
- ( void ) destroy
{
2017-09-15 11:27:13 +00:00
rightBarButtonItems = nil ;
for ( UIBarButtonItem * barButtonItem in self . navigationItem . rightBarButtonItems )
{
barButtonItem . enabled = NO ;
}
2015-08-27 09:31:17 +00:00
2016-05-03 16:29:54 +00:00
if ( currentAlert )
2015-06-16 11:54:12 +00:00
{
2017-07-14 14:41:25 +00:00
[ currentAlert dismissViewControllerAnimated : NO completion : nil ] ;
2016-05-03 16:29:54 +00:00
currentAlert = nil ;
2015-03-31 16:33:37 +00:00
}
2015-12-11 13:01:56 +00:00
if ( customizedRoomDataSource )
{
customizedRoomDataSource . selectedEventId = nil ;
customizedRoomDataSource = nil ;
}
2016-06-27 13:34:17 +00:00
[ self removeTypingNotificationsListener ] ;
2019-01-11 09:32:56 +00:00
if ( kThemeServiceDidChangeThemeNotificationObserver )
2017-07-21 09:28:17 +00:00
{
2019-01-11 09:32:56 +00:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : kThemeServiceDidChangeThemeNotificationObserver ] ;
kThemeServiceDidChangeThemeNotificationObserver = nil ;
2017-07-21 09:28:17 +00:00
}
2016-06-27 13:34:17 +00:00
if ( kAppDelegateDidTapStatusBarNotificationObserver )
{
[ [ NSNotificationCenter defaultCenter ] removeObserver : kAppDelegateDidTapStatusBarNotificationObserver ] ;
kAppDelegateDidTapStatusBarNotificationObserver = nil ;
}
2016-08-11 08:38:10 +00:00
if ( kAppDelegateNetworkStatusDidChangeNotificationObserver )
{
[ [ NSNotificationCenter defaultCenter ] removeObserver : kAppDelegateNetworkStatusDidChangeNotificationObserver ] ;
kAppDelegateNetworkStatusDidChangeNotificationObserver = nil ;
}
2017-04-18 13:55:51 +00:00
if ( mxRoomSummaryDidChangeObserver )
2016-08-31 13:12:46 +00:00
{
2017-04-18 13:55:51 +00:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : mxRoomSummaryDidChangeObserver ] ;
mxRoomSummaryDidChangeObserver = nil ;
2016-08-31 13:12:46 +00:00
}
2018-06-12 16:21:04 +00:00
if ( mxEventDidDecryptNotificationObserver )
{
[ [ NSNotificationCenter defaultCenter ] removeObserver : mxEventDidDecryptNotificationObserver ] ;
mxEventDidDecryptNotificationObserver = nil ;
}
2017-07-14 14:41:25 +00:00
2016-08-09 10:47:52 +00:00
[ self removeCallNotificationsListeners ] ;
2017-08-09 15:31:15 +00:00
[ self removeWidgetNotificationsListeners ] ;
2018-07-27 09:56:51 +00:00
[ self removeTombstoneEventNotificationsListener ] ;
2018-08-21 16:25:56 +00:00
[ self removeMXSessionStateChangeNotificationsListener ] ;
2018-10-02 13:30:00 +00:00
[ self removeServerNoticesListener ] ;
2017-08-09 15:31:15 +00:00
2020-09-18 11:11:45 +00:00
if ( previewHeader )
2016-04-14 00:34:30 +00:00
{
2016-06-10 15:15:42 +00:00
// Here [ destroy ] is called before [ viewWillDisappear : ]
2017-06-20 09:32:54 +00:00
NSLog ( @ "[RoomVC] destroyed whereas it is still visible" ) ;
2016-06-10 15:15:42 +00:00
2016-04-14 00:34:30 +00:00
[ previewHeader removeFromSuperview ] ;
previewHeader = nil ;
2016-06-10 15:15:42 +00:00
// Hide preview header container to ignore [ self showPreviewHeader : NO ] call ( if any ) .
self . previewHeaderContainer . hidden = YES ;
2016-04-14 00:34:30 +00:00
}
2016-06-10 15:15:42 +00:00
roomPreviewData = nil ;
2016-08-31 18:57:08 +00:00
missedDiscussionsBarButtonCustomView = nil ;
2016-08-31 13:12:46 +00:00
missedDiscussionsBadgeLabelBgView = nil ;
missedDiscussionsBadgeLabel = nil ;
2017-07-14 14:41:25 +00:00
2017-02-14 10:01:38 +00:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : self name : kMXEventDidChangeSentStateNotification object : nil ] ;
2019-06-27 10:25:01 +00:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : self name : kMXEventDidChangeIdentifierNotification object : nil ] ;
2017-07-14 14:41:25 +00:00
2015-04-01 09:06:48 +00:00
[ super destroy ] ;
2015-03-31 16:33:37 +00:00
}
2016-04-18 14:56:44 +00:00
# pragma mark -
2018-01-16 22:49:04 +00:00
- ( void ) setShowMissedDiscussionsBadge : ( BOOL ) showMissedDiscussionsBadge
{
_showMissedDiscussionsBadge = showMissedDiscussionsBadge ;
if ( _showMissedDiscussionsBadge && ! missedDiscussionsBarButtonCustomView )
{
// Prepare missed dicussion badge
missedDiscussionsBarButtonCustomView = [ [ UIView alloc ] initWithFrame : CGRectMake ( 0 , 0 , 0 , 21 ) ] ;
missedDiscussionsBarButtonCustomView . backgroundColor = [ UIColor clearColor ] ;
missedDiscussionsBarButtonCustomView . clipsToBounds = NO ;
NSLayoutConstraint * heightConstraint = [ NSLayoutConstraint constraintWithItem : missedDiscussionsBarButtonCustomView
attribute : NSLayoutAttributeHeight
relatedBy : NSLayoutRelationEqual
toItem : nil
attribute : NSLayoutAttributeNotAnAttribute
multiplier : 1.0
constant : 21 ] ;
missedDiscussionsBadgeLabelBgView = [ [ UIView alloc ] initWithFrame : CGRectMake ( 0 , 0 , 21 , 21 ) ] ;
[ missedDiscussionsBadgeLabelBgView . layer setCornerRadius : 10 ] ;
[ missedDiscussionsBarButtonCustomView addSubview : missedDiscussionsBadgeLabelBgView ] ;
missedDiscussionsBarButtonCustomView . accessibilityIdentifier = @ "RoomVCMissedDiscussionsBarButton" ;
missedDiscussionsBadgeLabel = [ [ UILabel alloc ] initWithFrame : CGRectMake ( 2 , 2 , 17 , 17 ) ] ;
missedDiscussionsBadgeLabel . translatesAutoresizingMaskIntoConstraints = NO ;
[ missedDiscussionsBadgeLabelBgView addSubview : missedDiscussionsBadgeLabel ] ;
NSLayoutConstraint * centerXConstraint = [ NSLayoutConstraint constraintWithItem : missedDiscussionsBadgeLabel
attribute : NSLayoutAttributeCenterX
relatedBy : NSLayoutRelationEqual
toItem : missedDiscussionsBadgeLabelBgView
attribute : NSLayoutAttributeCenterX
multiplier : 1.0
constant : 0 ] ;
NSLayoutConstraint * centerYConstraint = [ NSLayoutConstraint constraintWithItem : missedDiscussionsBadgeLabel
attribute : NSLayoutAttributeCenterY
relatedBy : NSLayoutRelationEqual
toItem : missedDiscussionsBadgeLabelBgView
attribute : NSLayoutAttributeCenterY
multiplier : 1.0
constant : 0 ] ;
[ NSLayoutConstraint activateConstraints : @ [ heightConstraint , centerXConstraint , centerYConstraint ] ] ;
}
else
{
missedDiscussionsBarButtonCustomView = nil ;
missedDiscussionsBadgeLabelBgView = nil ;
missedDiscussionsBadgeLabel = nil ;
}
}
2016-03-22 16:23:44 +00:00
# pragma mark - Internals
2016-11-25 09:50:16 +00:00
- ( void ) forceLayoutRefresh
{
// Sanity check : check whether the table view data source is set .
if ( self . bubblesTableView . dataSource )
{
[ self . view layoutIfNeeded ] ;
}
}
2016-04-14 00:34:30 +00:00
- ( BOOL ) isRoomPreview
{
2016-06-10 15:15:42 +00:00
// Check first whether some preview data are defined .
if ( roomPreviewData )
2016-04-14 00:34:30 +00:00
{
return YES ;
}
2018-07-17 16:00:56 +00:00
if ( self . roomDataSource && self . roomDataSource . state = = MXKDataSourceStateReady && self . roomDataSource . room . summary . membership = = MXMembershipInvite )
2016-04-14 00:34:30 +00:00
{
return YES ;
}
return NO ;
}
2020-01-22 15:27:04 +00:00
- ( BOOL ) isEncryptionEnabled
{
return self . roomDataSource . room . summary . isEncrypted && self . mainSession . crypto ! = nil ;
}
2016-04-14 00:34:30 +00:00
- ( void ) refreshRoomTitle
{
2017-09-15 11:27:13 +00:00
if ( rightBarButtonItems && ! self . navigationItem . rightBarButtonItems )
2017-06-22 08:51:40 +00:00
{
// Restore by default the search bar button .
2017-09-15 11:27:13 +00:00
self . navigationItem . rightBarButtonItems = rightBarButtonItems ;
2017-06-22 08:51:40 +00:00
}
2016-04-14 00:34:30 +00:00
// Set the right room title view
if ( self . isRoomPreview )
{
2018-03-12 15:16:30 +00:00
// Do not show the right buttons
self . navigationItem . rightBarButtonItems = nil ;
2016-04-14 00:34:30 +00:00
[ self showPreviewHeader : YES ] ;
}
else if ( self . roomDataSource )
{
[ self showPreviewHeader : NO ] ;
if ( self . roomDataSource . isLive )
{
2017-09-15 11:27:13 +00:00
// Enable the right buttons ( Search and Integrations )
for ( UIBarButtonItem * barButtonItem in self . navigationItem . rightBarButtonItems )
{
barButtonItem . enabled = YES ;
}
2017-09-27 13:21:38 +00:00
if ( self . navigationItem . rightBarButtonItems . count = = 2 )
2017-09-15 11:27:13 +00:00
{
2017-09-27 13:21:38 +00:00
BOOL matrixAppsEnabled = [ [ NSUserDefaults standardUserDefaults ] boolForKey : @ "matrixApps" ] ;
if ( ! matrixAppsEnabled )
{
// If the setting is disabled , do not show the icon
self . navigationItem . rightBarButtonItems = @ [ self . navigationItem . rightBarButtonItem ] ;
}
2018-05-07 09:15:18 +00:00
else if ( [ self widgetsCount : NO ] )
2017-09-27 13:21:38 +00:00
{
// Show there are widgets by changing the "apps" icon color
2018-05-07 09:15:18 +00:00
// Show it in red only for room widgets , not user ' s widgets
2017-09-27 13:21:38 +00:00
// TODO : Design must be reviewed
UIImage * icon = self . navigationItem . rightBarButtonItems [ 1 ] . image ;
2019-01-18 12:40:09 +00:00
icon = [ MXKTools paintImage : icon withColor : ThemeService . shared . theme . warningColor ] ;
2017-09-27 13:21:38 +00:00
icon = [ icon imageWithRenderingMode : UIImageRenderingModeAlwaysOriginal ] ;
self . navigationItem . rightBarButtonItems [ 1 ] . image = icon ;
2019-09-18 10:47:12 +00:00
self . navigationItem . rightBarButtonItems [ 1 ] . accessibilityLabel = NSLocalizedStringFromTable ( @ "room_accessibility_integrations" , @ "Vector" , nil ) ;
2017-09-27 13:21:38 +00:00
}
2017-09-27 13:40:21 +00:00
else
{
// Reset original icon
2020-07-10 12:20:16 +00:00
self . navigationItem . rightBarButtonItems [ 1 ] . image = [ UIImage imageNamed : @ "integrations_icon" ] ;
2019-09-18 10:47:12 +00:00
self . navigationItem . rightBarButtonItems [ 1 ] . accessibilityLabel = NSLocalizedStringFromTable ( @ "room_accessibility_integrations" , @ "Vector" , nil ) ;
2017-09-27 13:40:21 +00:00
}
2019-09-18 10:47:12 +00:00
self . navigationItem . rightBarButtonItems . firstObject . accessibilityLabel = NSLocalizedStringFromTable ( @ "room_accessibility_search" , @ "Vector" , nil ) ;
2017-09-15 11:27:13 +00:00
}
2017-09-27 13:21:38 +00:00
2016-04-22 22:56:21 +00:00
// Do not change title view class here if the expanded header is visible .
2020-09-18 11:11:45 +00:00
[ self setRoomTitleViewClass : RoomTitleView . class ] ;
( ( RoomTitleView * ) self . titleView ) . tapGestureDelegate = self ;
2016-04-14 00:34:30 +00:00
}
else
{
2017-06-22 08:51:40 +00:00
// Remove the search button temporarily
2017-09-15 11:27:13 +00:00
rightBarButtonItems = self . navigationItem . rightBarButtonItems ;
self . navigationItem . rightBarButtonItems = nil ;
2016-04-14 00:34:30 +00:00
[ self setRoomTitleViewClass : SimpleRoomTitleView . class ] ;
self . titleView . editable = NO ;
}
}
2016-04-18 13:22:57 +00:00
else
{
self . navigationItem . rightBarButtonItem . enabled = NO ;
}
2016-04-14 00:34:30 +00:00
}
2016-03-22 16:23:44 +00:00
- ( void ) refreshRoomInputToolbar
{
2018-02-21 15:10:38 +00:00
MXKImageView * userPictureView ;
2016-03-22 16:23:44 +00:00
// Check whether the input toolbar is ready before updating it .
if ( self . inputToolbarView && [ self . inputToolbarView isKindOfClass : RoomInputToolbarView . class ] )
{
RoomInputToolbarView * roomInputToolbarView = ( RoomInputToolbarView * ) self . inputToolbarView ;
// Check whether the call option is supported
2020-07-30 12:48:35 +00:00
roomInputToolbarView . supportCallOption = BuildSettings . allowVoIPUsage && self . roomDataSource . mxSession . callManager && self . roomDataSource . room . summary . membersCount . joined >= 2 ;
2017-07-14 14:41:25 +00:00
2018-02-21 15:10:38 +00:00
// Get user picture view in input toolbar
userPictureView = roomInputToolbarView . pictureView ;
2017-07-14 14:41:25 +00:00
2017-08-11 12:59:05 +00:00
// Show the hangup button if there is an active call or an active jitsi
// conference call in the current room
2016-08-09 10:24:58 +00:00
MXCall * callInRoom = [ self . roomDataSource . mxSession . callManager callInRoom : self . roomDataSource . roomId ] ;
2017-08-11 12:59:05 +00:00
if ( ( callInRoom && callInRoom . state ! = MXCallStateEnded )
|| [ [ AppDelegate theDelegate ] . jitsiViewController . widget . roomId isEqualToString : self . roomDataSource . roomId ] )
2016-08-09 10:24:58 +00:00
{
roomInputToolbarView . activeCall = YES ;
}
else
{
roomInputToolbarView . activeCall = NO ;
2016-09-02 20:21:02 +00:00
// Hide the call button if there is an active call in another room
roomInputToolbarView . supportCallOption & = ( [ [ AppDelegate theDelegate ] callStatusBarWindow ] = = nil ) ;
2016-08-09 10:24:58 +00:00
}
2016-11-07 10:36:32 +00:00
2020-01-22 15:27:04 +00:00
// Update encryption decoration if needed
[ self updateEncryptionDecorationForRoomInputToolbar : roomInputToolbarView ] ;
2016-03-22 16:23:44 +00:00
}
2018-02-21 15:10:38 +00:00
else if ( self . inputToolbarView && [ self . inputToolbarView isKindOfClass : DisabledRoomInputToolbarView . class ] )
{
DisabledRoomInputToolbarView * roomInputToolbarView = ( DisabledRoomInputToolbarView * ) self . inputToolbarView ;
// Get user picture view in input toolbar
userPictureView = roomInputToolbarView . pictureView ;
// For the moment , there is only one reason to use ` DisabledRoomInputToolbarView`
[ roomInputToolbarView setDisabledReason : NSLocalizedStringFromTable ( @ "room_do_not_have_permission_to_post" , @ "Vector" , nil ) ] ;
}
// Set user picture in input toolbar
if ( userPictureView )
{
UIImage * preview = [ AvatarGenerator generateAvatarForMatrixItem : self . mainSession . myUser . userId withDisplayName : self . mainSession . myUser . displayname ] ;
2018-11-11 14:20:32 +00:00
// Suppose the avatar is stored unencrypted on the Matrix media repository .
2018-02-21 15:10:38 +00:00
userPictureView . enableInMemoryCache = YES ;
2018-11-11 14:20:32 +00:00
[ userPictureView setImageURI : self . mainSession . myUser . avatarUrl
withType : nil
andImageOrientation : UIImageOrientationUp
toFitViewSize : userPictureView . frame . size
withMethod : MXThumbnailingMethodCrop
previewImage : preview
mediaManager : self . mainSession . mediaManager ] ;
2018-02-21 15:10:38 +00:00
[ userPictureView . layer setCornerRadius : userPictureView . frame . size . width / 2 ] ;
userPictureView . clipsToBounds = YES ;
}
2016-03-22 16:23:44 +00:00
}
2019-04-30 15:07:43 +00:00
- ( void ) setInputToolBarSendMode : ( RoomInputToolbarViewSendMode ) sendMode
2018-07-23 14:56:03 +00:00
{
2018-08-06 12:08:28 +00:00
if ( self . inputToolbarView && [ self . inputToolbarView isKindOfClass : [ RoomInputToolbarView class ] ] )
{
RoomInputToolbarView * roomInputToolbarView = ( RoomInputToolbarView * ) self . inputToolbarView ;
2019-04-30 15:07:43 +00:00
roomInputToolbarView . sendMode = sendMode ;
}
}
- ( RoomInputToolbarViewSendMode ) inputToolBarSendMode
{
RoomInputToolbarViewSendMode sendMode = RoomInputToolbarViewSendModeSend ;
if ( self . inputToolbarView && [ self . inputToolbarView isKindOfClass : [ RoomInputToolbarView class ] ] )
{
RoomInputToolbarView * roomInputToolbarView = ( RoomInputToolbarView * ) self . inputToolbarView ;
sendMode = roomInputToolbarView . sendMode ;
2018-08-06 12:08:28 +00:00
}
2019-04-30 15:07:43 +00:00
return sendMode ;
2018-07-23 14:56:03 +00:00
}
2016-04-20 07:52:49 +00:00
- ( void ) onSwipeGesture : ( UISwipeGestureRecognizer * ) swipeGestureRecognizer
{
UIView * view = swipeGestureRecognizer . view ;
2020-09-18 11:11:45 +00:00
if ( view = = self . activitiesView )
2016-09-05 08:40:46 +00:00
{
// Dismiss the keyboard when user swipes down on activities view .
[ self . inputToolbarView dismissKeyboard ] ;
}
2016-04-20 07:52:49 +00:00
}
2018-07-27 09:56:51 +00:00
- ( void ) updateInputToolBarViewHeight
{
// Update the inputToolBar height .
CGFloat height = [ self inputToolbarHeight ] ;
// Disable animation during the update
[ UIView setAnimationsEnabled : NO ] ;
[ self roomInputToolbarView : self . inputToolbarView heightDidChanged : height completion : nil ] ;
[ UIView setAnimationsEnabled : YES ] ;
}
2020-01-22 15:27:04 +00:00
- ( UIImage * ) roomEncryptionBadgeImage
{
UIImage * encryptionIcon ;
if ( self . isEncryptionEnabled )
{
RoomEncryptionTrustLevel roomEncryptionTrustLevel = ( ( RoomDataSource * ) self . roomDataSource ) . encryptionTrustLevel ;
2020-04-08 13:07:24 +00:00
encryptionIcon = [ EncryptionTrustLevelBadgeImageHelper roomBadgeImageFor : roomEncryptionTrustLevel ] ;
2020-01-22 15:27:04 +00:00
}
return encryptionIcon ;
}
- ( void ) updateInputToolbarEncryptionDecoration
{
if ( self . inputToolbarView && [ self . inputToolbarView isKindOfClass : RoomInputToolbarView . class ] )
{
RoomInputToolbarView * roomInputToolbarView = ( RoomInputToolbarView * ) self . inputToolbarView ;
[ self updateEncryptionDecorationForRoomInputToolbar : roomInputToolbarView ] ;
}
}
2020-04-08 13:07:24 +00:00
- ( void ) updateTitleViewEncryptionDecoration
{
if ( ! [ self . titleView isKindOfClass : [ RoomTitleView class ] ] )
{
return ;
}
RoomTitleView * roomTitleView = ( RoomTitleView * ) self . titleView ;
roomTitleView . badgeImageView . image = self . roomEncryptionBadgeImage ;
}
2020-01-22 15:27:04 +00:00
- ( void ) updateEncryptionDecorationForRoomInputToolbar : ( RoomInputToolbarView * ) roomInputToolbarView
{
roomInputToolbarView . isEncryptionEnabled = self . isEncryptionEnabled ;
roomInputToolbarView . encryptedRoomIcon . image = self . roomEncryptionBadgeImage ;
}
2019-05-15 21:24:34 +00:00
- ( void ) handleLongPressFromCell : ( id < MXKCellRendering > ) cell withTappedEvent : ( MXEvent * ) event
{
if ( event && ! customizedRoomDataSource . selectedEventId )
{
2019-06-27 09:41:25 +00:00
[ self showContextualMenuForEvent : event fromSingleTapGesture : NO cell : cell animated : YES ] ;
2019-05-15 21:24:34 +00:00
}
}
2019-07-30 15:18:39 +00:00
- ( void ) showReactionHistoryForEventId : ( NSString * ) eventId animated : ( BOOL ) animated
{
if ( self . reactionHistoryCoordinatorBridgePresenter . isPresenting )
{
return ;
}
ReactionHistoryCoordinatorBridgePresenter * presenter = [ [ ReactionHistoryCoordinatorBridgePresenter alloc ] initWithSession : self . mainSession roomId : self . roomDataSource . roomId eventId : eventId ] ;
presenter . delegate = self ;
[ presenter presentFrom : self animated : animated ] ;
self . reactionHistoryCoordinatorBridgePresenter = presenter ;
}
2019-08-02 15:19:29 +00:00
- ( void ) showCameraControllerAnimated : ( BOOL ) animated
{
CameraPresenter * cameraPresenter = [ CameraPresenter new ] ;
cameraPresenter . delegate = self ;
[ cameraPresenter presentCameraFrom : self with : @ [ MXKUTI . image , MXKUTI . movie ] animated : YES ] ;
self . cameraPresenter = cameraPresenter ;
}
- ( void ) showMediaPickerAnimated : ( BOOL ) animated
{
MediaPickerCoordinatorBridgePresenter * mediaPickerPresenter = [ [ MediaPickerCoordinatorBridgePresenter alloc ] initWithSession : self . mainSession mediaUTIs : @ [ MXKUTI . image , MXKUTI . movie ] allowsMultipleSelection : YES ] ;
mediaPickerPresenter . delegate = self ;
UIView * sourceView ;
RoomInputToolbarView * roomInputToolbarView = [ self inputToolbarViewAsRoomInputToolbarView ] ;
if ( roomInputToolbarView )
{
sourceView = roomInputToolbarView . attachMediaButton ;
}
else
{
sourceView = self . inputToolbarView ;
}
[ mediaPickerPresenter presentFrom : self sourceView : sourceView sourceRect : sourceView . bounds animated : YES ] ;
self . mediaPickerPresenter = mediaPickerPresenter ;
}
2020-09-14 16:44:30 +00:00
- ( void ) showRoomCreationModalWithBubbleData : ( id < MXKRoomBubbleCellDataStoring > ) bubbleData
{
[ self . roomCreationModalCoordinatorBridgePresenter dismissWithAnimated : NO completion : nil ] ;
self . roomCreationModalCoordinatorBridgePresenter = [ [ RoomCreationModalCoordinatorBridgePresenter alloc ] initWithSession : self . mainSession bubbleData : bubbleData roomState : self . roomDataSource . roomState ] ;
self . roomCreationModalCoordinatorBridgePresenter . delegate = self ;
[ self . roomCreationModalCoordinatorBridgePresenter presentFrom : self animated : YES ] ;
}
2020-10-16 11:27:45 +00:00
- ( void ) showMemberDetails : ( MXRoomMember * ) member
{
if ( ! member )
{
return ;
}
RoomMemberDetailsViewController * memberViewController = [ RoomMemberDetailsViewController roomMemberDetailsViewController ] ;
// Set delegate to handle action on member ( start chat , mention )
memberViewController . delegate = self ;
memberViewController . enableMention = ( self . inputToolbarView ! = nil ) ;
memberViewController . enableVoipCall = NO ;
[ memberViewController displayRoomMember : member withMatrixRoom : self . roomDataSource . room ] ;
[ self . navigationController pushViewController : memberViewController animated : YES ] ;
}
2016-04-14 00:34:30 +00:00
# pragma mark - Hide / Show preview header
- ( void ) showPreviewHeader : ( BOOL ) isVisible
{
2017-01-24 12:35:06 +00:00
if ( self . previewHeaderContainer && self . previewHeaderContainer . isHidden = = isVisible )
2016-04-14 00:34:30 +00:00
{
2017-01-24 12:35:06 +00:00
// Check conditions before making the preview room header visible .
// This operation is ignored if a screen rotation is in progress ,
// or if the view controller is not embedded inside a split view controller yet .
if ( isVisible && ( isSizeTransitionInProgress = = YES || ! self . splitViewController ) )
{
2017-06-20 09:32:54 +00:00
NSLog ( @ "[RoomVC] Show preview header ignored" ) ;
2017-01-24 12:35:06 +00:00
return ;
}
2016-04-19 13:28:15 +00:00
if ( isVisible )
2016-04-14 00:34:30 +00:00
{
previewHeader = [ PreviewRoomTitleView roomTitleView ] ;
previewHeader . delegate = self ;
previewHeader . tapGestureDelegate = self ;
previewHeader . translatesAutoresizingMaskIntoConstraints = NO ;
[ self . previewHeaderContainer addSubview : previewHeader ] ;
// Force preview header in full width
NSLayoutConstraint * leftConstraint = [ NSLayoutConstraint constraintWithItem : previewHeader
attribute : NSLayoutAttributeLeading
relatedBy : NSLayoutRelationEqual
toItem : self . previewHeaderContainer
attribute : NSLayoutAttributeLeading
multiplier : 1.0
constant : 0 ] ;
NSLayoutConstraint * rightConstraint = [ NSLayoutConstraint constraintWithItem : previewHeader
attribute : NSLayoutAttributeTrailing
relatedBy : NSLayoutRelationEqual
toItem : self . previewHeaderContainer
attribute : NSLayoutAttributeTrailing
multiplier : 1.0
constant : 0 ] ;
// Vertical constraints are required for iOS > 8
NSLayoutConstraint * topConstraint = [ NSLayoutConstraint constraintWithItem : previewHeader
attribute : NSLayoutAttributeTop
relatedBy : NSLayoutRelationEqual
toItem : self . previewHeaderContainer
attribute : NSLayoutAttributeTop
multiplier : 1.0
constant : 0 ] ;
NSLayoutConstraint * bottomConstraint = [ NSLayoutConstraint constraintWithItem : previewHeader
attribute : NSLayoutAttributeBottom
relatedBy : NSLayoutRelationEqual
toItem : self . previewHeaderContainer
attribute : NSLayoutAttributeBottom
multiplier : 1.0
constant : 0 ] ;
[ NSLayoutConstraint activateConstraints : @ [ leftConstraint , rightConstraint , topConstraint , bottomConstraint ] ] ;
2016-06-10 15:15:42 +00:00
if ( roomPreviewData )
2016-04-14 00:34:30 +00:00
{
2016-06-10 15:15:42 +00:00
previewHeader . roomPreviewData = roomPreviewData ;
2016-04-14 00:34:30 +00:00
}
2016-06-10 15:15:42 +00:00
else if ( self . roomDataSource )
2016-04-14 00:34:30 +00:00
{
2016-06-10 15:15:42 +00:00
previewHeader . mxRoom = self . roomDataSource . room ;
2016-04-14 00:34:30 +00:00
}
2016-04-19 13:28:15 +00:00
2016-06-08 15:19:00 +00:00
self . previewHeaderContainer . hidden = NO ;
2019-01-10 13:47:58 +00:00
2016-06-08 15:19:00 +00:00
// Finalize preview header display according to the screen orientation
2016-06-08 16:56:19 +00:00
[ self refreshPreviewHeader : UIInterfaceOrientationIsLandscape ( [ [ UIApplication sharedApplication ] statusBarOrientation ] ) ] ;
2016-04-19 13:28:15 +00:00
}
else
{
[ previewHeader removeFromSuperview ] ;
previewHeader = nil ;
2016-06-08 15:19:00 +00:00
self . previewHeaderContainer . hidden = YES ;
// Consider the main navigation controller if the current view controller is embedded inside a split view controller .
UINavigationController * mainNavigationController = self . navigationController ;
if ( self . splitViewController . isCollapsed && self . splitViewController . viewControllers . count )
{
mainNavigationController = self . splitViewController . viewControllers . firstObject ;
}
// Set a default title view class without handling tap gesture ( Let [ self refreshRoomTitle ] refresh this view correctly ) .
[ self setRoomTitleViewClass : RoomTitleView . class ] ;
// Remove details icon
RoomTitleView * roomTitleView = ( RoomTitleView * ) self . titleView ;
[ roomTitleView . roomDetailsIconImageView removeFromSuperview ] ;
roomTitleView . roomDetailsIconImageView = nil ;
// Remove the shadow image used to hide the bottom border of the navigation bar when the preview header is displayed
[ mainNavigationController . navigationBar setShadowImage : nil ] ;
[ mainNavigationController . navigationBar setBackgroundImage : nil forBarMetrics : UIBarMetricsDefault ] ;
[ UIView animateWithDuration : 0.3 delay : 0 options : UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseIn
animations : ^ {
2016-06-10 15:15:42 +00:00
self . bubblesTableViewTopConstraint . constant = 0 ;
2017-09-27 07:26:34 +00:00
self . jumpToLastUnreadBannerContainerTopConstraint . constant = self . bubblesTableView . mxk_adjustedContentInset . top ;
2016-06-10 15:15:42 +00:00
2016-06-08 15:19:00 +00:00
// Force to render the view
2016-11-25 09:50:16 +00:00
[ self forceLayoutRefresh ] ;
2016-06-10 15:15:42 +00:00
2016-06-08 15:19:00 +00:00
}
completion : ^ ( BOOL finished ) {
} ] ;
2016-04-14 00:34:30 +00:00
}
2016-06-08 15:19:00 +00:00
}
2020-04-21 13:27:27 +00:00
// Consider the main navigation controller if the current view controller is embedded inside a split view controller .
UINavigationController * mainNavigationController = self . navigationController ;
if ( self . splitViewController . isCollapsed && self . splitViewController . viewControllers . count )
{
mainNavigationController = self . splitViewController . viewControllers . firstObject ;
}
mainNavigationController . navigationBar . translucent = isVisible ;
self . navigationController . navigationBar . translucent = isVisible ;
2016-06-08 15:19:00 +00:00
}
2016-06-08 16:22:50 +00:00
- ( void ) refreshPreviewHeader : ( BOOL ) isLandscapeOriented
2016-06-08 15:19:00 +00:00
{
if ( previewHeader )
{
2018-03-12 14:56:41 +00:00
if ( isLandscapeOriented
2018-03-12 16:07:51 +00:00
&& [ GBDeviceInfo deviceInfo ] . family ! = GBDeviceFamilyiPad )
2016-04-14 00:34:30 +00:00
{
2016-06-08 16:22:50 +00:00
CGRect frame = self . navigationController . navigationBar . frame ;
previewHeader . mainHeaderContainer . hidden = YES ;
previewHeader . mainHeaderBackgroundHeightConstraint . constant = frame . size . height + ( frame . origin . y > 0 ? frame . origin . y : 0 ) ;
[ self setRoomTitleViewClass : RoomTitleView . class ] ;
// We don ' t want to handle tap gesture here
// Remove details icon
RoomTitleView * roomTitleView = ( RoomTitleView * ) self . titleView ;
[ roomTitleView . roomDetailsIconImageView removeFromSuperview ] ;
roomTitleView . roomDetailsIconImageView = nil ;
2016-06-10 15:15:42 +00:00
// Set preview data to provide the room name
roomTitleView . roomPreviewData = roomPreviewData ;
2016-04-14 00:34:30 +00:00
}
2016-06-08 16:22:50 +00:00
else
2016-02-10 09:59:48 +00:00
{
2016-06-08 16:22:50 +00:00
previewHeader . mainHeaderContainer . hidden = NO ;
previewHeader . mainHeaderBackgroundHeightConstraint . constant = previewHeader . mainHeaderContainer . frame . size . height ;
2018-03-12 13:59:59 +00:00
if ( [ previewHeader isKindOfClass : PreviewRoomTitleView . class ] )
{
// In case of preview , update the header height so that we can
// display as much as possible the room topic in this header .
2018-03-12 16:07:51 +00:00
// Note : the header height is handled by the previewHeader . mainHeaderBackgroundHeightConstraint .
2018-03-12 13:59:59 +00:00
PreviewRoomTitleView * previewRoomTitleView = ( PreviewRoomTitleView * ) previewHeader ;
// Compute the height required to display all the room topic
CGSize sizeThatFitsTextView = [ previewRoomTitleView . roomTopic sizeThatFits : CGSizeMake ( previewRoomTitleView . roomTopic . frame . size . width , MAXFLOAT ) ] ;
// Increase the preview header height according to the room topic height
// but limit it in order to let room for room messages at the screen bottom .
// This free space depends on the device .
// On an iphone 5 screen , the room topic height cannot be more than 50 px .
// Then , on larger screen , we can allow it a bit more height but we
// apply a factor to give more priority to the display of more messages .
CGFloat screenHeight = [ [ UIScreen mainScreen ] bounds ] . size . height ;
CGFloat maxRoomTopicHeight = 50 + ( screenHeight - 568 ) / 3 ;
CGFloat additionalHeight = MIN ( maxRoomTopicHeight , sizeThatFitsTextView . height )
- previewRoomTitleView . roomTopic . frame . size . height ;
previewHeader . mainHeaderBackgroundHeightConstraint . constant + = additionalHeight ;
}
2016-06-08 16:22:50 +00:00
[ self setRoomTitleViewClass : RoomAvatarTitleView . class ] ;
// Note the avatar title view does not define tap gesture .
2017-10-20 17:22:37 +00:00
previewHeader . roomAvatar . alpha = 0.0 ;
2016-06-08 16:22:50 +00:00
// Set the avatar provided in preview data
if ( roomPreviewData . roomAvatarUrl )
2016-04-14 11:06:38 +00:00
{
2018-11-11 14:20:32 +00:00
previewHeader . roomAvatarURL = roomPreviewData . roomAvatarUrl ;
2016-04-14 11:06:38 +00:00
}
2016-06-08 16:22:50 +00:00
else if ( roomPreviewData . roomId && roomPreviewData . roomName )
2016-06-06 14:01:31 +00:00
{
2017-10-20 17:22:37 +00:00
previewHeader . roomAvatarPlaceholder = [ AvatarGenerator generateAvatarForMatrixItem : roomPreviewData . roomId withDisplayName : roomPreviewData . roomName ] ;
2016-06-06 14:01:31 +00:00
}
2016-06-08 16:22:50 +00:00
else
2016-06-06 14:01:31 +00:00
{
2018-12-03 16:14:32 +00:00
previewHeader . roomAvatarPlaceholder = [ MXKTools paintImage : [ UIImage imageNamed : @ "placeholder" ]
2019-01-11 10:45:27 +00:00
withColor : ThemeService . shared . theme . tintColor ] ;
2016-06-06 14:01:31 +00:00
}
2016-06-08 15:19:00 +00:00
}
2016-06-08 16:22:50 +00:00
// Force the layout of previewHeader to update the position of ' bottomBorderView ' which is used
// to define the actual height of the preview container .
[ previewHeader layoutIfNeeded ] ;
2016-06-10 15:15:42 +00:00
CGRect frame = previewHeader . bottomBorderView . frame ;
self . previewHeaderContainerHeightConstraint . constant = frame . origin . y + frame . size . height ;
2016-06-08 16:22:50 +00:00
// Consider the main navigation controller if the current view controller is embedded inside a split view controller .
UINavigationController * mainNavigationController = self . navigationController ;
if ( self . splitViewController . isCollapsed && self . splitViewController . viewControllers . count )
{
mainNavigationController = self . splitViewController . viewControllers . firstObject ;
}
// When the preview header is displayed , we hide the bottom border of the navigation bar ( the shadow image ) .
// The default shadow image is nil . When non - nil , this property represents a custom shadow image to show instead
// of the default . For a custom shadow image to be shown , a custom background image must also be set with the
// setBackgroundImage : forBarMetrics : method . If the default background image is used , then the default shadow
// image will be used regardless of the value of this property .
UIImage * shadowImage = [ [ UIImage alloc ] init ] ;
[ mainNavigationController . navigationBar setShadowImage : shadowImage ] ;
[ mainNavigationController . navigationBar setBackgroundImage : shadowImage forBarMetrics : UIBarMetricsDefault ] ;
[ UIView animateWithDuration : 0.3 delay : 0 options : UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseIn
animations : ^ {
2017-09-27 07:26:34 +00:00
self . bubblesTableViewTopConstraint . constant = self . previewHeaderContainerHeightConstraint . constant - self . bubblesTableView . mxk_adjustedContentInset . top ;
2017-06-01 15:20:08 +00:00
self . jumpToLastUnreadBannerContainerTopConstraint . constant = self . previewHeaderContainerHeightConstraint . constant ;
2016-06-10 15:15:42 +00:00
2017-10-20 17:22:37 +00:00
previewHeader . roomAvatar . alpha = 1 ;
2016-06-08 16:22:50 +00:00
// Force to render the view
2016-11-25 09:50:16 +00:00
[ self forceLayoutRefresh ] ;
2016-06-10 15:15:42 +00:00
2016-06-08 16:22:50 +00:00
}
completion : ^ ( BOOL finished ) {
} ] ;
2016-02-10 09:59:48 +00:00
}
}
2016-04-14 00:34:30 +00:00
# pragma mark - Preview
2016-04-15 07:43:56 +00:00
- ( void ) displayRoomPreview : ( RoomPreviewData * ) previewData
2016-04-14 00:34:30 +00:00
{
2016-06-10 15:15:42 +00:00
// Release existing room data source or preview
[ self displayRoom : nil ] ;
2016-04-15 07:43:56 +00:00
if ( previewData )
2016-04-14 00:34:30 +00:00
{
2017-06-09 07:40:22 +00:00
self . eventsAcknowledgementEnabled = NO ;
2016-04-15 07:43:56 +00:00
[ self addMatrixSession : previewData . mxSession ] ;
roomPreviewData = previewData ;
2016-04-14 00:34:30 +00:00
[ self refreshRoomTitle ] ;
2016-06-10 15:15:42 +00:00
if ( roomPreviewData . roomDataSource )
{
[ super displayRoom : roomPreviewData . roomDataSource ] ;
}
2016-04-14 00:34:30 +00:00
}
}
2015-12-07 10:50:13 +00:00
# pragma mark - MXKDataSourceDelegate
- ( Class < MXKCellRendering > ) cellViewClassForCellData : ( MXKCellData * ) cellData
{
Class cellViewClass = nil ;
2020-02-06 17:46:32 +00:00
BOOL showEncryptionBadge = NO ;
2015-12-07 10:50:13 +00:00
// Sanity check
if ( [ cellData conformsToProtocol : @ protocol ( MXKRoomBubbleCellDataStoring ) ] )
{
id < MXKRoomBubbleCellDataStoring > bubbleData = ( id < MXKRoomBubbleCellDataStoring > ) cellData ;
2020-02-06 17:46:32 +00:00
MXKRoomBubbleCellData * roomBubbleCellData ;
if ( [ bubbleData isKindOfClass : MXKRoomBubbleCellData . class ] )
{
roomBubbleCellData = ( MXKRoomBubbleCellData * ) bubbleData ;
showEncryptionBadge = roomBubbleCellData . containsBubbleComponentWithEncryptionBadge ;
}
2017-06-13 15:43:32 +00:00
// Select the suitable table view cell class , by considering first the empty bubble cell .
2017-07-06 15:55:13 +00:00
if ( bubbleData . hasNoDisplay )
2017-06-13 15:43:32 +00:00
{
cellViewClass = RoomEmptyBubbleCell . class ;
}
2018-07-27 16:53:51 +00:00
else if ( bubbleData . tag = = RoomBubbleCellDataTagRoomCreateWithPredecessor )
{
2018-08-07 09:27:37 +00:00
cellViewClass = RoomPredecessorBubbleCell . class ;
2018-07-27 16:53:51 +00:00
}
2020-01-14 19:23:36 +00:00
else if ( bubbleData . tag = = RoomBubbleCellDataTagKeyVerificationRequestIncomingApproval )
2019-12-20 09:43:07 +00:00
{
2020-01-14 19:23:36 +00:00
cellViewClass = bubbleData . isPaginationFirstBubble ? KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell . class : KeyVerificationIncomingRequestApprovalBubbleCell . class ;
2019-12-20 09:43:07 +00:00
}
2020-01-14 19:23:36 +00:00
else if ( bubbleData . tag = = RoomBubbleCellDataTagKeyVerificationRequest )
2019-12-20 09:43:07 +00:00
{
2020-01-14 19:23:36 +00:00
cellViewClass = bubbleData . isPaginationFirstBubble ? KeyVerificationRequestStatusWithPaginationTitleBubbleCell . class : KeyVerificationRequestStatusBubbleCell . class ;
2019-12-20 09:43:07 +00:00
}
2020-01-14 19:23:36 +00:00
else if ( bubbleData . tag = = RoomBubbleCellDataTagKeyVerificationConclusion )
2019-12-20 09:43:07 +00:00
{
2020-01-14 19:23:36 +00:00
cellViewClass = bubbleData . isPaginationFirstBubble ? KeyVerificationConclusionWithPaginationTitleBubbleCell . class : KeyVerificationConclusionBubbleCell . class ;
2019-12-20 09:43:07 +00:00
}
2017-07-05 07:05:23 +00:00
else if ( bubbleData . tag = = RoomBubbleCellDataTagMembership )
2017-07-03 15:57:10 +00:00
{
2017-07-07 15:47:58 +00:00
if ( bubbleData . collapsed )
2017-07-05 10:29:54 +00:00
{
2017-07-07 15:47:58 +00:00
if ( bubbleData . nextCollapsableCellData )
2017-07-07 09:11:42 +00:00
{
2017-07-10 12:30:53 +00:00
cellViewClass = bubbleData . isPaginationFirstBubble ? RoomMembershipCollapsedWithPaginationTitleBubbleCell . class : RoomMembershipCollapsedBubbleCell . class ;
2017-07-07 09:11:42 +00:00
}
else
{
2017-07-07 15:47:58 +00:00
// Use a normal membership cell for a single membership event
2017-07-10 08:25:06 +00:00
cellViewClass = bubbleData . isPaginationFirstBubble ? RoomMembershipWithPaginationTitleBubbleCell . class : RoomMembershipBubbleCell . class ;
2017-07-07 09:11:42 +00:00
}
2017-07-05 10:29:54 +00:00
}
2017-07-07 15:47:58 +00:00
else if ( bubbleData . collapsedAttributedTextMessage )
{
2017-07-17 08:30:46 +00:00
// The cell ( and its series ) is not collapsed but this cell is the first
// of the series . So , use the cell with the "collapse" button .
2017-07-10 13:05:21 +00:00
cellViewClass = bubbleData . isPaginationFirstBubble ? RoomMembershipExpandedWithPaginationTitleBubbleCell . class : RoomMembershipExpandedBubbleCell . class ;
2017-07-07 15:47:58 +00:00
}
else
{
2017-07-10 08:25:06 +00:00
cellViewClass = bubbleData . isPaginationFirstBubble ? RoomMembershipWithPaginationTitleBubbleCell . class : RoomMembershipBubbleCell . class ;
2017-07-07 15:47:58 +00:00
}
2017-07-03 15:57:10 +00:00
}
2020-09-14 11:24:41 +00:00
else if ( bubbleData . tag = = RoomBubbleCellDataTagRoomCreateConfiguration )
{
2020-09-14 12:39:08 +00:00
cellViewClass = bubbleData . isPaginationFirstBubble ? RoomCreationWithPaginationCollapsedBubbleCell . class : RoomCreationCollapsedBubbleCell . class ;
2020-09-14 11:24:41 +00:00
}
2017-06-13 15:43:32 +00:00
else if ( bubbleData . isIncoming )
2015-12-07 10:50:13 +00:00
{
2015-12-08 09:10:59 +00:00
if ( bubbleData . isAttachmentWithThumbnail )
{
2018-04-02 21:59:47 +00:00
// Check whether the provided celldata corresponds to a selected sticker
if ( customizedRoomDataSource . selectedEventId && ( bubbleData . attachment . type = = MXKAttachmentTypeSticker ) && [ bubbleData . attachment . eventId isEqualToString : customizedRoomDataSource . selectedEventId ] )
{
cellViewClass = RoomSelectedStickerBubbleCell . class ;
}
else if ( bubbleData . isPaginationFirstBubble )
2015-12-08 09:10:59 +00:00
{
2020-02-06 17:46:32 +00:00
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell . class : RoomIncomingAttachmentWithPaginationTitleBubbleCell . class ;
2015-12-08 09:10:59 +00:00
}
else if ( bubbleData . shouldHideSenderInformation )
{
2020-02-06 17:46:32 +00:00
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell . class : RoomIncomingAttachmentWithoutSenderInfoBubbleCell . class ;
2015-12-08 09:10:59 +00:00
}
else
{
2020-02-06 17:46:32 +00:00
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedAttachmentBubbleCell . class : RoomIncomingAttachmentBubbleCell . class ;
2015-12-08 09:10:59 +00:00
}
}
else
{
if ( bubbleData . isPaginationFirstBubble )
{
2016-01-21 16:57:02 +00:00
if ( bubbleData . shouldHideSenderName )
{
2020-02-06 17:46:32 +00:00
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell . class : RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell . class ;
2016-01-21 16:57:02 +00:00
}
else
{
2020-02-06 17:46:32 +00:00
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell . class : RoomIncomingTextMsgWithPaginationTitleBubbleCell . class ;
2016-01-21 16:57:02 +00:00
}
2015-12-08 09:10:59 +00:00
}
else if ( bubbleData . shouldHideSenderInformation )
{
2020-02-06 17:46:32 +00:00
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell . class : RoomIncomingTextMsgWithoutSenderInfoBubbleCell . class ;
2015-12-08 09:10:59 +00:00
}
2016-01-21 16:57:02 +00:00
else if ( bubbleData . shouldHideSenderName )
{
2020-02-06 17:46:32 +00:00
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell . class : RoomIncomingTextMsgWithoutSenderNameBubbleCell . class ;
2016-01-21 16:57:02 +00:00
}
2015-12-08 09:10:59 +00:00
else
{
2020-02-06 17:46:32 +00:00
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgBubbleCell . class : RoomIncomingTextMsgBubbleCell . class ;
2015-12-08 09:10:59 +00:00
}
}
2015-12-07 10:50:13 +00:00
}
else
{
2015-12-08 09:10:59 +00:00
// Handle here outgoing bubbles
if ( bubbleData . isAttachmentWithThumbnail )
{
2018-04-02 21:59:47 +00:00
// Check whether the provided celldata corresponds to a selected sticker
if ( customizedRoomDataSource . selectedEventId && ( bubbleData . attachment . type = = MXKAttachmentTypeSticker ) && [ bubbleData . attachment . eventId isEqualToString : customizedRoomDataSource . selectedEventId ] )
{
cellViewClass = RoomSelectedStickerBubbleCell . class ;
}
else if ( bubbleData . isPaginationFirstBubble )
2015-12-08 09:10:59 +00:00
{
2020-02-06 17:46:32 +00:00
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell . class : RoomOutgoingAttachmentWithPaginationTitleBubbleCell . class ;
2015-12-08 09:10:59 +00:00
}
else if ( bubbleData . shouldHideSenderInformation )
{
2020-02-06 17:46:32 +00:00
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell . class : RoomOutgoingAttachmentWithoutSenderInfoBubbleCell . class ;
2015-12-08 09:10:59 +00:00
}
else
{
2020-02-06 17:46:32 +00:00
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedAttachmentBubbleCell . class : RoomOutgoingAttachmentBubbleCell . class ;
2015-12-08 09:10:59 +00:00
}
}
else
{
if ( bubbleData . isPaginationFirstBubble )
{
2016-01-21 16:57:02 +00:00
if ( bubbleData . shouldHideSenderName )
{
2020-02-06 17:46:32 +00:00
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell . class : RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell . class ;
2016-01-21 16:57:02 +00:00
}
else
{
2020-02-06 17:46:32 +00:00
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell . class : RoomOutgoingTextMsgWithPaginationTitleBubbleCell . class ;
2016-01-21 16:57:02 +00:00
}
2015-12-08 09:10:59 +00:00
}
else if ( bubbleData . shouldHideSenderInformation )
{
2020-02-06 17:46:32 +00:00
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell . class : RoomOutgoingTextMsgWithoutSenderInfoBubbleCell . class ;
2015-12-08 09:10:59 +00:00
}
2016-01-21 16:57:02 +00:00
else if ( bubbleData . shouldHideSenderName )
{
2020-02-06 17:46:32 +00:00
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell . class : RoomOutgoingTextMsgWithoutSenderNameBubbleCell . class ;
2016-01-21 16:57:02 +00:00
}
2015-12-08 09:10:59 +00:00
else
{
2020-02-06 17:46:32 +00:00
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgBubbleCell . class : RoomOutgoingTextMsgBubbleCell . class ;
2015-12-08 09:10:59 +00:00
}
}
2015-12-07 10:50:13 +00:00
}
}
return cellViewClass ;
}
2015-03-31 13:09:17 +00:00
# pragma mark - MXKDataSource delegate
2014-10-15 23:34:46 +00:00
2015-06-16 11:54:12 +00:00
- ( void ) dataSource : ( MXKDataSource * ) dataSource didRecognizeAction : ( NSString * ) actionIdentifier inCell : ( id < MXKCellRendering > ) cell userInfo : ( NSDictionary * ) userInfo
{
2015-12-11 13:01:56 +00:00
// Handle here user actions on bubbles for Vector app
if ( customizedRoomDataSource )
2015-06-16 11:54:12 +00:00
{
2019-05-22 14:37:06 +00:00
id < MXKRoomBubbleCellDataStoring > bubbleData ;
if ( [ cell isKindOfClass : [ MXKRoomBubbleTableViewCell class ] ] )
{
MXKRoomBubbleTableViewCell * roomBubbleTableViewCell = ( MXKRoomBubbleTableViewCell * ) cell ;
bubbleData = roomBubbleTableViewCell . bubbleData ;
}
2016-05-17 14:47:55 +00:00
if ( [ actionIdentifier isEqualToString : kMXKRoomBubbleCellTapOnAvatarView ] )
2016-05-03 21:30:46 +00:00
{
2020-10-16 11:27:45 +00:00
MXRoomMember * member = [ self . roomDataSource . roomState . members memberWithUserId : userInfo [ kMXKRoomBubbleCellUserIdKey ] ] ;
[ self showMemberDetails : member ] ;
2016-05-03 21:30:46 +00:00
}
2016-05-17 14:47:55 +00:00
else if ( [ actionIdentifier isEqualToString : kMXKRoomBubbleCellLongPressOnAvatarView ] )
{
// Add the member display name in text input
2018-07-20 09:28:02 +00:00
MXRoomMember * roomMember = [ self . roomDataSource . roomState . members memberWithUserId : userInfo [ kMXKRoomBubbleCellUserIdKey ] ] ;
2016-05-17 14:47:55 +00:00
if ( roomMember )
{
[ self mention : roomMember ] ;
}
}
2016-05-03 21:30:46 +00:00
else if ( [ actionIdentifier isEqualToString : kMXKRoomBubbleCellTapOnMessageTextView ] || [ actionIdentifier isEqualToString : kMXKRoomBubbleCellTapOnContentView ] )
2015-12-11 13:01:56 +00:00
{
// Retrieve the tapped event
MXEvent * tappedEvent = userInfo [ kMXKRoomBubbleCellEventKey ] ;
// Check whether a selection already exist or not
if ( customizedRoomDataSource . selectedEventId )
{
2015-12-18 14:20:57 +00:00
[ self cancelEventSelection ] ;
2015-12-11 13:01:56 +00:00
}
else if ( tappedEvent )
{
2019-02-04 14:49:49 +00:00
if ( tappedEvent . eventType = = MXEventTypeRoomCreate )
{
// Handle tap on RoomPredecessorBubbleCell
MXRoomCreateContent * createContent = [ MXRoomCreateContent modelFromJSON : tappedEvent . content ] ;
NSString * predecessorRoomId = createContent . roomPredecessorInfo . roomId ;
if ( predecessorRoomId )
{
// Show predecessor room
[ [ AppDelegate theDelegate ] showRoom : predecessorRoomId andEventId : nil withMatrixSession : self . mainSession ] ;
}
2020-09-14 11:24:41 +00:00
else
{
// Show contextual menu on single tap if bubble is not collapsed
if ( bubbleData . collapsed )
{
2020-09-14 16:44:30 +00:00
[ self showRoomCreationModalWithBubbleData : bubbleData ] ;
2020-09-14 11:24:41 +00:00
}
else
{
[ self showContextualMenuForEvent : tappedEvent fromSingleTapGesture : YES cell : cell animated : YES ] ;
}
}
2019-02-04 14:49:49 +00:00
}
else
{
2019-05-22 14:37:06 +00:00
// Show contextual menu on single tap if bubble is not collapsed
if ( bubbleData . collapsed )
{
[ self selectEventWithId : tappedEvent . eventId ] ;
}
else
{
2019-06-27 09:41:25 +00:00
[ self showContextualMenuForEvent : tappedEvent fromSingleTapGesture : YES cell : cell animated : YES ] ;
2019-05-22 14:37:06 +00:00
}
2019-02-04 14:49:49 +00:00
}
2015-12-11 13:01:56 +00:00
}
}
else if ( [ actionIdentifier isEqualToString : kMXKRoomBubbleCellTapOnOverlayContainer ] )
{
// Cancel the current event selection
2015-12-18 14:20:57 +00:00
[ self cancelEventSelection ] ;
}
2017-03-09 10:25:36 +00:00
else if ( [ actionIdentifier isEqualToString : kMXKRoomBubbleCellRiotEditButtonPressed ] )
2015-12-18 14:20:57 +00:00
{
[ self dismissKeyboard ] ;
MXEvent * selectedEvent = userInfo [ kMXKRoomBubbleCellEventKey ] ;
2017-07-14 14:41:25 +00:00
2015-12-18 14:20:57 +00:00
if ( selectedEvent )
2015-12-11 13:01:56 +00:00
{
2019-06-27 09:41:25 +00:00
[ self showContextualMenuForEvent : selectedEvent fromSingleTapGesture : YES cell : cell animated : YES ] ;
2016-09-02 07:59:27 +00:00
}
}
2020-01-14 19:23:36 +00:00
else if ( [ actionIdentifier isEqualToString : kMXKRoomBubbleCellKeyVerificationIncomingRequestAcceptPressed ] )
{
NSString * eventId = userInfo [ kMXKRoomBubbleCellEventIdKey ] ;
RoomDataSource * roomDataSource = ( RoomDataSource * ) self . roomDataSource ;
[ roomDataSource acceptVerificationRequestForEventId : eventId success : ^ {
} failure : ^ ( NSError * error ) {
[ [ AppDelegate theDelegate ] showErrorAsAlert : error ] ;
} ] ;
}
else if ( [ actionIdentifier isEqualToString : kMXKRoomBubbleCellKeyVerificationIncomingRequestDeclinePressed ] )
{
NSString * eventId = userInfo [ kMXKRoomBubbleCellEventIdKey ] ;
RoomDataSource * roomDataSource = ( RoomDataSource * ) self . roomDataSource ;
[ roomDataSource declineVerificationRequestForEventId : eventId success : ^ {
} failure : ^ ( NSError * error ) {
[ [ AppDelegate theDelegate ] showErrorAsAlert : error ] ;
} ] ;
}
2018-03-30 10:04:50 +00:00
else if ( [ actionIdentifier isEqualToString : kMXKRoomBubbleCellTapOnAttachmentView ] )
2016-09-02 07:59:27 +00:00
{
2018-03-30 10:04:50 +00:00
if ( ( ( MXKRoomBubbleTableViewCell * ) cell ) . bubbleData . attachment . eventSentState = = MXEventSentStateFailed )
{
// Shortcut : when clicking on an unsent media , show the action sheet to resend it
2019-10-22 09:50:25 +00:00
NSString * eventId = ( ( MXKRoomBubbleTableViewCell * ) cell ) . bubbleData . attachment . eventId ;
MXEvent * selectedEvent = [ self . roomDataSource eventWithEventId : eventId ] ;
if ( selectedEvent )
{
[ self dataSource : dataSource didRecognizeAction : kMXKRoomBubbleCellRiotEditButtonPressed inCell : cell userInfo : @ { kMXKRoomBubbleCellEventKey : selectedEvent } ] ;
}
else
{
NSLog ( @ "[RoomViewController] didRecognizeAction:inCell:userInfo tap on attachment with event state MXEventSentStateFailed. Selected event is nil for event id %@" , eventId ) ;
}
2018-03-30 10:04:50 +00:00
}
else if ( ( ( MXKRoomBubbleTableViewCell * ) cell ) . bubbleData . attachment . type = = MXKAttachmentTypeSticker )
{
// We don ' t open the attachments viewer when the user taps on a sticker .
// We consider this tap like a selection .
// Check whether a selection already exist or not
if ( customizedRoomDataSource . selectedEventId )
{
[ self cancelEventSelection ] ;
}
else
{
// Highlight this event in displayed message
2018-07-23 14:56:03 +00:00
[ self selectEventWithId : ( ( MXKRoomBubbleTableViewCell * ) cell ) . bubbleData . attachment . eventId ] ;
2018-03-30 10:04:50 +00:00
}
}
2018-04-11 09:33:35 +00:00
else
{
// Keep default implementation
[ super dataSource : dataSource didRecognizeAction : actionIdentifier inCell : cell userInfo : userInfo ] ;
}
2016-09-02 07:59:27 +00:00
}
2016-11-16 08:31:58 +00:00
else if ( [ actionIdentifier isEqualToString : kRoomEncryptedDataBubbleCellTapOnEncryptionIcon ] )
{
// Retrieve the tapped event
MXEvent * tappedEvent = userInfo [ kMXKRoomBubbleCellEventKey ] ;
if ( tappedEvent )
{
[ self showEncryptionInformation : tappedEvent ] ;
}
}
2017-06-26 15:33:53 +00:00
else if ( [ actionIdentifier isEqualToString : kMXKRoomBubbleCellTapOnReceiptsContainer ] )
{
MXKReceiptSendersContainer * container = userInfo [ kMXKRoomBubbleCellReceiptsContainerKey ] ;
2017-06-29 15:40:07 +00:00
[ ReadReceiptsViewController openInViewController : self fromContainer : container withSession : self . mainSession ] ;
2017-06-26 15:33:53 +00:00
}
2017-07-07 09:53:49 +00:00
else if ( [ actionIdentifier isEqualToString : kRoomMembershipExpandedBubbleCellTapOnCollapseButton ] )
{
2017-07-17 14:15:49 +00:00
// Reset the selection before collapsing
customizedRoomDataSource . selectedEventId = nil ;
2017-07-07 09:53:49 +00:00
[ self . roomDataSource collapseRoomBubble : ( ( MXKRoomBubbleTableViewCell * ) cell ) . bubbleData collapsed : YES ] ;
}
2019-05-15 21:24:34 +00:00
else if ( [ actionIdentifier isEqualToString : kMXKRoomBubbleCellLongPressOnEvent ] )
{
MXEvent * tappedEvent = userInfo [ kMXKRoomBubbleCellEventKey ] ;
2019-05-22 14:37:06 +00:00
if ( ! bubbleData . collapsed )
{
[ self handleLongPressFromCell : cell withTappedEvent : tappedEvent ] ;
}
2019-05-15 21:24:34 +00:00
}
2019-07-30 15:18:39 +00:00
else if ( [ actionIdentifier isEqualToString : kMXKRoomBubbleCellLongPressOnReactionView ] )
{
NSString * tappedEventId = userInfo [ kMXKRoomBubbleCellEventIdKey ] ;
if ( tappedEventId )
{
[ self showReactionHistoryForEventId : tappedEventId animated : YES ] ;
}
}
2016-09-02 07:59:27 +00:00
else
{
// Keep default implementation for other actions
[ super dataSource : dataSource didRecognizeAction : actionIdentifier inCell : cell userInfo : userInfo ] ;
}
}
else
{
// Keep default implementation for other actions
[ super dataSource : dataSource didRecognizeAction : actionIdentifier inCell : cell userInfo : userInfo ] ;
}
}
2019-06-27 08:23:20 +00:00
// Display the additiontal event actions menu
- ( void ) showAdditionalActionsMenuForEvent : ( MXEvent * ) selectedEvent inCell : ( id < MXKCellRendering > ) cell animated : ( BOOL ) animated
2016-09-02 07:59:27 +00:00
{
MXKRoomBubbleTableViewCell * roomBubbleTableViewCell = ( MXKRoomBubbleTableViewCell * ) cell ;
MXKAttachment * attachment = roomBubbleTableViewCell . bubbleData . attachment ;
2017-07-14 14:41:25 +00:00
2016-09-02 07:59:27 +00:00
if ( currentAlert )
{
2017-07-14 14:41:25 +00:00
[ currentAlert dismissViewControllerAnimated : NO completion : nil ] ;
2016-09-02 07:59:27 +00:00
currentAlert = nil ;
}
2017-07-14 14:41:25 +00:00
2016-09-02 07:59:27 +00:00
__weak __typeof ( self ) weakSelf = self ;
2017-07-14 14:41:25 +00:00
currentAlert = [ UIAlertController alertControllerWithTitle : nil message : nil preferredStyle : UIAlertControllerStyleActionSheet ] ;
2019-06-27 08:23:20 +00:00
// Add actions for a failed event
if ( selectedEvent . sentState = = MXEventSentStateFailed )
2016-09-02 07:59:27 +00:00
{
2019-06-27 08:23:20 +00:00
[ currentAlert addAction : [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "room_event_action_resend" , @ "Vector" , nil )
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
2017-07-14 14:41:25 +00:00
2019-06-27 08:23:20 +00:00
[ self cancelEventSelection ] ;
2017-07-14 14:41:25 +00:00
2019-06-27 08:23:20 +00:00
// Let the datasource resend . It will manage local echo , etc .
[ self . roomDataSource resendEventWithEventId : selectedEvent . eventId success : nil failure : nil ] ;
}
} ] ] ;
[ currentAlert addAction : [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "room_event_action_delete" , @ "Vector" , nil )
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
2017-07-14 14:41:25 +00:00
2019-06-27 08:23:20 +00:00
[ self cancelEventSelection ] ;
2017-07-14 14:41:25 +00:00
2019-06-27 08:23:20 +00:00
[ self . roomDataSource removeEventWithEventId : selectedEvent . eventId ] ;
}
} ] ] ;
2016-09-02 07:59:27 +00:00
}
2017-07-14 14:41:25 +00:00
2016-09-02 07:59:27 +00:00
// Add actions for text message
if ( ! attachment )
{
// Retrieved data related to the selected event
NSArray * components = roomBubbleTableViewCell . bubbleData . bubbleComponents ;
MXKRoomBubbleComponent * selectedComponent ;
for ( selectedComponent in components )
{
if ( [ selectedComponent . event . eventId isEqualToString : selectedEvent . eventId ] )
{
break ;
}
selectedComponent = nil ;
}
2017-07-14 14:41:25 +00:00
2017-10-03 12:57:32 +00:00
2019-06-27 08:23:20 +00:00
// Check status of the selected event
if ( selectedEvent . sentState = = MXEventSentStatePreparing ||
selectedEvent . sentState = = MXEventSentStateEncrypting ||
selectedEvent . sentState = = MXEventSentStateSending )
2016-09-02 07:59:27 +00:00
{
2019-06-27 08:23:20 +00:00
[ currentAlert addAction : [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "room_event_action_cancel_send" , @ "Vector" , nil )
2017-07-14 14:41:25 +00:00
style : UIAlertActionStyleDefault
2019-06-27 08:23:20 +00:00
handler : ^ ( UIAlertAction * action )
{
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
self -> currentAlert = nil ;
// Cancel and remove the outgoing message
[ self . roomDataSource . room cancelSendingOperation : selectedEvent . eventId ] ;
[ self . roomDataSource removeEventWithEventId : selectedEvent . eventId ] ;
[ self cancelEventSelection ] ;
}
} ] ] ;
}
[ currentAlert addAction : [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "room_event_action_quote" , @ "Vector" , nil )
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
2017-07-14 14:41:25 +00:00
2019-06-27 08:23:20 +00:00
[ self cancelEventSelection ] ;
// Quote the message a la Markdown into the input toolbar composer
self . inputToolbarView . textMessage = [ NSString stringWithFormat : @ "%@\n>%@\n\n" , self . inputToolbarView . textMessage , selectedComponent . textMessage ] ;
// And display the keyboard
[ self . inputToolbarView becomeFirstResponder ] ;
}
} ] ] ;
2020-08-03 13:07:39 +00:00
if ( BuildSettings . messageDetailsAllowShare )
2020-07-30 11:42:49 +00:00
{
[ currentAlert addAction : [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "room_event_action_share" , @ "Vector" , nil )
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
[ self cancelEventSelection ] ;
NSArray * activityItems = @ [ selectedComponent . textMessage ] ;
UIActivityViewController * activityViewController = [ [ UIActivityViewController alloc ] initWithActivityItems : activityItems applicationActivities : nil ] ;
if ( activityViewController )
{
activityViewController . modalTransitionStyle = UIModalTransitionStyleCoverVertical ;
activityViewController . popoverPresentationController . sourceView = roomBubbleTableViewCell ;
activityViewController . popoverPresentationController . sourceRect = roomBubbleTableViewCell . bounds ;
[ self presentViewController : activityViewController animated : YES completion : nil ] ;
}
}
} ] ] ;
}
2019-06-27 08:23:20 +00:00
}
else // Add action for attachment
{
2020-08-14 11:55:06 +00:00
if ( BuildSettings . messageDetailsAllowSave )
2016-09-02 07:59:27 +00:00
{
2020-08-14 11:06:22 +00:00
if ( attachment . type = = MXKAttachmentTypeImage || attachment . type = = MXKAttachmentTypeVideo )
{
[ currentAlert addAction : [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "room_event_action_save" , @ "Vector" , nil )
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
[ self cancelEventSelection ] ;
[ self startActivityIndicator ] ;
[ attachment save : ^ {
__strong __typeof ( weakSelf ) self = weakSelf ;
[ self stopActivityIndicator ] ;
} failure : ^ ( NSError * error ) {
__strong __typeof ( weakSelf ) self = weakSelf ;
[ self stopActivityIndicator ] ;
// Alert user
[ [ AppDelegate theDelegate ] showErrorAsAlert : error ] ;
} ] ;
// Start animation in case of download during attachment preparing
[ roomBubbleTableViewCell startProgressUI ] ;
}
} ] ] ;
}
2016-09-02 07:59:27 +00:00
}
2019-06-27 08:23:20 +00:00
// Check status of the selected event
if ( selectedEvent . sentState = = MXEventSentStatePreparing ||
selectedEvent . sentState = = MXEventSentStateEncrypting ||
selectedEvent . sentState = = MXEventSentStateUploading ||
selectedEvent . sentState = = MXEventSentStateSending )
{
// Upload id is stored in attachment url ( nasty trick )
NSString * uploadId = roomBubbleTableViewCell . bubbleData . attachment . contentURL ;
if ( [ MXMediaManager existingUploaderWithId : uploadId ] )
2016-09-02 07:59:27 +00:00
{
2019-06-27 08:23:20 +00:00
[ currentAlert addAction : [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "room_event_action_cancel_send" , @ "Vector" , nil )
2017-07-14 14:41:25 +00:00
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
2019-06-27 08:23:20 +00:00
// Get again the loader
MXMediaLoader * loader = [ MXMediaManager existingUploaderWithId : uploadId ] ;
if ( loader )
{
[ loader cancel ] ;
}
// Hide the progress animation
roomBubbleTableViewCell . progressView . hidden = YES ;
2017-07-14 14:41:25 +00:00
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
2019-06-27 08:23:20 +00:00
self -> currentAlert = nil ;
2017-07-14 14:41:25 +00:00
2019-06-27 08:23:20 +00:00
// Remove the outgoing message and its related cached file .
[ [ NSFileManager defaultManager ] removeItemAtPath : roomBubbleTableViewCell . bubbleData . attachment . cacheFilePath error : nil ] ;
[ [ NSFileManager defaultManager ] removeItemAtPath : roomBubbleTableViewCell . bubbleData . attachment . thumbnailCachePath error : nil ] ;
2017-07-14 14:41:25 +00:00
2019-06-27 08:23:20 +00:00
// Cancel and remove the outgoing message
[ self . roomDataSource . room cancelSendingOperation : selectedEvent . eventId ] ;
[ self . roomDataSource removeEventWithEventId : selectedEvent . eventId ] ;
2017-07-14 14:41:25 +00:00
2019-06-27 08:23:20 +00:00
[ self cancelEventSelection ] ;
2017-07-14 14:41:25 +00:00
}
} ] ] ;
}
2016-09-02 07:59:27 +00:00
}
2019-06-27 08:23:20 +00:00
if ( attachment . type ! = MXKAttachmentTypeSticker )
2016-09-02 07:59:27 +00:00
{
2020-08-03 13:07:39 +00:00
if ( BuildSettings . messageDetailsAllowShare )
2020-07-30 11:42:49 +00:00
{
[ currentAlert addAction : [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "room_event_action_share" , @ "Vector" , nil )
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
[ self cancelEventSelection ] ;
[ attachment prepareShare : ^ ( NSURL * fileURL ) {
__strong __typeof ( weakSelf ) self = weakSelf ;
self -> documentInteractionController = [ UIDocumentInteractionController interactionControllerWithURL : fileURL ] ;
[ self -> documentInteractionController setDelegate : self ] ;
self -> currentSharedAttachment = attachment ;
if ( ! [ self -> documentInteractionController presentOptionsMenuFromRect : self . view . frame inView : self . view animated : YES ] )
{
self -> documentInteractionController = nil ;
[ attachment onShareEnded ] ;
self -> currentSharedAttachment = nil ;
}
} failure : ^ ( NSError * error ) {
// Alert user
[ [ AppDelegate theDelegate ] showErrorAsAlert : error ] ;
} ] ;
// Start animation in case of download during attachment preparing
[ roomBubbleTableViewCell startProgressUI ] ;
}
} ] ] ;
}
2016-09-02 07:59:27 +00:00
}
}
2017-07-14 14:41:25 +00:00
2016-09-02 07:59:27 +00:00
// Check status of the selected event
2016-12-16 17:24:24 +00:00
if ( selectedEvent . sentState = = MXEventSentStateSent )
2016-09-02 07:59:27 +00:00
{
// Check whether download is in progress
2019-06-27 08:23:20 +00:00
if ( selectedEvent . isMediaAttachment )
2016-09-02 07:59:27 +00:00
{
2018-11-10 13:28:08 +00:00
NSString * downloadId = roomBubbleTableViewCell . bubbleData . attachment . downloadId ;
if ( [ MXMediaManager existingDownloaderWithIdentifier : downloadId ] )
2016-09-02 07:59:27 +00:00
{
2017-07-14 14:41:25 +00:00
[ currentAlert addAction : [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "room_event_action_cancel_download" , @ "Vector" , nil )
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
[ self cancelEventSelection ] ;
// Get again the loader
2018-11-10 13:28:08 +00:00
MXMediaLoader * loader = [ MXMediaManager existingDownloaderWithIdentifier : downloadId ] ;
2017-07-14 14:41:25 +00:00
if ( loader )
{
[ loader cancel ] ;
}
// Hide the progress animation
roomBubbleTableViewCell . progressView . hidden = YES ;
}
} ] ] ;
2016-09-02 07:59:27 +00:00
}
}
2017-07-14 14:41:25 +00:00
2019-06-27 08:23:20 +00:00
// Do not allow to redact the event that enabled encryption ( m . room . encryption )
// because it breaks everything
if ( selectedEvent . eventType ! = MXEventTypeRoomEncryption )
2016-09-02 07:59:27 +00:00
{
2019-06-27 08:23:20 +00:00
[ currentAlert addAction : [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "room_event_action_redact" , @ "Vector" , nil )
2017-07-14 14:41:25 +00:00
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
[ self cancelEventSelection ] ;
2019-06-27 08:23:20 +00:00
[ self startActivityIndicator ] ;
2017-07-14 14:41:25 +00:00
2019-06-27 08:23:20 +00:00
[ self . roomDataSource . room redactEvent : selectedEvent . eventId reason : nil success : ^ {
__strong __typeof ( weakSelf ) self = weakSelf ;
[ self stopActivityIndicator ] ;
} failure : ^ ( NSError * error ) {
__strong __typeof ( weakSelf ) self = weakSelf ;
[ self stopActivityIndicator ] ;
NSLog ( @ "[RoomVC] Redact event (%@) failed" , selectedEvent . eventId ) ;
// Alert user
[ [ AppDelegate theDelegate ] showErrorAsAlert : error ] ;
} ] ;
2017-07-14 14:41:25 +00:00
}
} ] ] ;
2016-09-02 07:59:27 +00:00
}
2019-06-27 08:23:20 +00:00
2020-08-03 13:07:39 +00:00
if ( BuildSettings . messageDetailsAllowPermalink )
2020-07-30 11:40:04 +00:00
{
[ currentAlert addAction : [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "room_event_action_permalink" , @ "Vector" , nil )
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
[ self cancelEventSelection ] ;
// Create a matrix . to permalink that is common to all matrix clients
NSString * permalink = [ MXTools permalinkToEvent : selectedEvent . eventId inRoom : selectedEvent . roomId ] ;
if ( permalink )
{
2020-10-09 12:20:54 +00:00
MXKPasteboardManager . shared . pasteboard . string = permalink ;
2020-07-30 11:40:04 +00:00
}
else
{
NSLog ( @ "[RoomViewController] Contextual menu permalink action failed. Permalink is nil room id/event id: %@/%@" , selectedEvent . roomId , selectedEvent . eventId ) ;
}
}
} ] ] ;
}
2017-07-14 14:41:25 +00:00
2019-07-30 15:18:39 +00:00
// Add reaction history if event contains reactions
if ( roomBubbleTableViewCell . bubbleData . reactions [ selectedEvent . eventId ] . aggregatedReactionsWithNonZeroCount )
{
[ currentAlert addAction : [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "room_event_action_reaction_history" , @ "Vector" , nil )
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
[ self cancelEventSelection ] ;
// Show reaction history
[ self showReactionHistoryForEventId : selectedEvent . eventId animated : YES ] ;
} ] ] ;
}
2020-08-03 13:07:39 +00:00
if ( BuildSettings . messageDetailsAllowViewSource )
2018-08-08 14:14:56 +00:00
{
2020-07-30 11:37:24 +00:00
[ currentAlert addAction : [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "room_event_action_view_source" , @ "Vector" , nil )
2018-08-08 14:14:56 +00:00
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
2020-07-30 11:37:24 +00:00
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
[ self cancelEventSelection ] ;
// Display event details
[ self showEventDetails : selectedEvent ] ;
}
} ] ] ;
2018-08-08 14:14:56 +00:00
2020-07-30 11:37:24 +00:00
// Add "View Decrypted Source" for e2ee event we can decrypt
if ( selectedEvent . isEncrypted && selectedEvent . clearEvent )
{
[ currentAlert addAction : [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "room_event_action_view_decrypted_source" , @ "Vector" , nil )
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
[ self cancelEventSelection ] ;
// Display clear event details
[ self showEventDetails : selectedEvent . clearEvent ] ;
}
} ] ] ;
}
2018-08-08 14:14:56 +00:00
}
2017-07-14 14:41:25 +00:00
2020-11-30 13:12:47 +00:00
if ( ! [ selectedEvent . sender isEqualToString : self . mainSession . myUser . userId ] )
{
[ currentAlert addAction : [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "room_event_action_report" , @ "Vector" , nil )
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
2020-07-30 15:12:17 +00:00
2020-11-30 13:12:47 +00:00
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
2020-07-30 11:38:36 +00:00
2020-11-30 13:12:47 +00:00
[ self cancelEventSelection ] ;
// Prompt user to enter a description of the problem content .
self -> currentAlert = [ UIAlertController alertControllerWithTitle : NSLocalizedStringFromTable ( @ "room_event_action_report_prompt_reason" , @ "Vector" , nil ) message : nil preferredStyle : UIAlertControllerStyleAlert ] ;
[ self -> currentAlert addTextFieldWithConfigurationHandler : ^ ( UITextField * textField ) {
textField . secureTextEntry = NO ;
textField . placeholder = nil ;
textField . keyboardType = UIKeyboardTypeDefault ;
} ] ;
[ self -> currentAlert addAction : [ UIAlertAction actionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "ok" ] style : UIAlertActionStyleDefault handler : ^ ( UIAlertAction * action ) {
2020-07-30 11:38:36 +00:00
2020-11-30 13:12:47 +00:00
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
NSString * text = [ self -> currentAlert textFields ] . firstObject . text ;
self -> currentAlert = nil ;
2020-07-30 11:38:36 +00:00
2020-11-30 13:12:47 +00:00
[ self startActivityIndicator ] ;
2020-07-30 15:12:17 +00:00
2020-11-30 13:12:47 +00:00
[ self . roomDataSource . room reportEvent : selectedEvent . eventId score : -100 reason : text success : ^ {
2020-07-30 11:38:36 +00:00
2020-11-30 13:12:47 +00:00
__strong __typeof ( weakSelf ) self = weakSelf ;
[ self stopActivityIndicator ] ;
// Prompt user to ignore content from this user
self -> currentAlert = [ UIAlertController alertControllerWithTitle : NSLocalizedStringFromTable ( @ "room_event_action_report_prompt_ignore_user" , @ "Vector" , nil ) message : nil preferredStyle : UIAlertControllerStyleAlert ] ;
[ self -> currentAlert addAction : [ UIAlertAction actionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "yes" ] style : UIAlertActionStyleDefault handler : ^ ( UIAlertAction * action ) {
2020-07-30 15:12:17 +00:00
2020-11-30 13:12:47 +00:00
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
self -> currentAlert = nil ;
2020-07-30 15:12:17 +00:00
2020-11-30 13:12:47 +00:00
[ self startActivityIndicator ] ;
2020-07-30 15:12:17 +00:00
2020-11-30 13:12:47 +00:00
// Add the user to the blacklist : ignored users
[ self . mainSession ignoreUsers : @ [ selectedEvent . sender ] success : ^ {
__strong __typeof ( weakSelf ) self = weakSelf ;
[ self stopActivityIndicator ] ;
} failure : ^ ( NSError * error ) {
__strong __typeof ( weakSelf ) self = weakSelf ;
[ self stopActivityIndicator ] ;
NSLog ( @ "[RoomVC] Ignore user (%@) failed" , selectedEvent . sender ) ;
// Alert user
[ [ AppDelegate theDelegate ] showErrorAsAlert : error ] ;
} ] ;
}
} ] ] ;
2020-07-30 11:38:36 +00:00
2020-11-30 13:12:47 +00:00
[ self -> currentAlert addAction : [ UIAlertAction actionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "no" ] style : UIAlertActionStyleDefault handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
self -> currentAlert = nil ;
}
} ] ] ;
2020-07-30 11:38:36 +00:00
2020-11-30 13:12:47 +00:00
[ self presentViewController : self -> currentAlert animated : YES completion : nil ] ;
2020-07-30 11:38:36 +00:00
2020-11-30 13:12:47 +00:00
} failure : ^ ( NSError * error ) {
__strong __typeof ( weakSelf ) self = weakSelf ;
[ self stopActivityIndicator ] ;
NSLog ( @ "[RoomVC] Report event (%@) failed" , selectedEvent . eventId ) ;
// Alert user
[ [ AppDelegate theDelegate ] showErrorAsAlert : error ] ;
} ] ;
}
} ] ] ;
2020-07-30 11:38:36 +00:00
2020-11-30 13:12:47 +00:00
[ self -> currentAlert addAction : [ UIAlertAction actionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "cancel" ] style : UIAlertActionStyleCancel handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
self -> currentAlert = nil ;
}
} ] ] ;
2020-07-30 15:12:17 +00:00
2020-11-30 13:12:47 +00:00
[ self presentViewController : self -> currentAlert animated : YES completion : nil ] ;
}
} ] ] ;
}
2020-07-30 11:38:36 +00:00
2019-06-27 08:23:20 +00:00
if ( self . roomDataSource . room . summary . isEncrypted )
2016-11-16 08:31:58 +00:00
{
2017-07-14 14:41:25 +00:00
[ currentAlert addAction : [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "room_event_action_view_encryption" , @ "Vector" , nil )
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
[ self cancelEventSelection ] ;
// Display encryption details
[ self showEncryptionInformation : selectedEvent ] ;
}
} ] ] ;
2015-12-11 13:01:56 +00:00
}
2017-07-14 14:41:25 +00:00
}
[ currentAlert addAction : [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "cancel" , @ "Vector" , nil )
2018-01-19 02:43:28 +00:00
style : UIAlertActionStyleCancel
2017-07-14 14:41:25 +00:00
handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
2019-05-15 21:24:34 +00:00
[ self hideContextualMenuAnimated : YES ] ;
2017-07-14 14:41:25 +00:00
}
} ] ] ;
2016-09-02 07:59:27 +00:00
// Do not display empty action sheet
2017-07-14 14:41:25 +00:00
if ( currentAlert . actions . count > 1 )
2016-09-02 07:59:27 +00:00
{
2019-07-25 14:46:35 +00:00
NSInteger bubbleComponentIndex = [ roomBubbleTableViewCell . bubbleData bubbleComponentIndexForEventId : selectedEvent . eventId ] ;
2019-06-27 08:23:20 +00:00
2019-07-25 14:46:35 +00:00
CGRect sourceRect = [ roomBubbleTableViewCell componentFrameInContentViewForIndex : bubbleComponentIndex ] ;
2019-06-27 08:23:20 +00:00
2017-07-14 14:41:25 +00:00
[ currentAlert mxk_setAccessibilityIdentifier : @ "RoomVCEventMenuAlert" ] ;
[ currentAlert popoverPresentationController ] . sourceView = roomBubbleTableViewCell ;
2019-06-27 08:23:20 +00:00
[ currentAlert popoverPresentationController ] . sourceRect = sourceRect ;
[ self presentViewController : currentAlert animated : animated completion : nil ] ;
2016-09-02 07:59:27 +00:00
}
2015-06-16 11:54:12 +00:00
else
{
2016-09-02 07:59:27 +00:00
currentAlert = nil ;
2015-03-31 13:09:17 +00:00
}
}
2016-04-18 15:53:30 +00:00
- ( BOOL ) dataSource : ( MXKDataSource * ) dataSource shouldDoAction : ( NSString * ) actionIdentifier inCell : ( id < MXKCellRendering > ) cell userInfo : ( NSDictionary * ) userInfo defaultValue : ( BOOL ) defaultValue
{
BOOL shouldDoAction = defaultValue ;
2017-07-14 14:41:25 +00:00
2016-04-18 15:53:30 +00:00
if ( [ actionIdentifier isEqualToString : kMXKRoomBubbleCellShouldInteractWithURL ] )
{
// Try to catch universal link supported by the app
NSURL * url = userInfo [ kMXKRoomBubbleCellUrl ] ;
2019-03-04 17:29:13 +00:00
// Retrieve the type of interaction expected with the URL ( See UITextItemInteraction )
NSNumber * urlItemInteractionValue = userInfo [ kMXKRoomBubbleCellUrlItemInteraction ] ;
2016-09-29 15:50:20 +00:00
2019-12-03 18:58:51 +00:00
RoomMessageURLType roomMessageURLType = RoomMessageURLTypeUnknown ;
if ( url )
{
roomMessageURLType = [ self . roomMessageURLParser parseURL : url ] ;
}
2016-09-29 15:50:20 +00:00
// When a link refers to a room alias / id , a user id or an event id , the non - ASCII characters ( like ' # ' in room alias ) has been escaped
// to be able to convert it into a legal URL string .
2019-02-04 20:15:47 +00:00
NSString * absoluteURLString = [ url . absoluteString stringByRemovingPercentEncoding ] ;
2017-07-14 14:41:25 +00:00
2016-04-29 14:19:13 +00:00
// If the link can be open it by the app , let it do
if ( [ Tools isUniversalLink : url ] )
2016-04-18 15:53:30 +00:00
{
2016-04-29 14:19:13 +00:00
shouldDoAction = NO ;
2017-07-14 14:41:25 +00:00
2016-04-29 14:19:13 +00:00
// iOS Patch : fix vector . im urls before using it
2016-05-03 09:05:47 +00:00
NSURL * fixedURL = [ Tools fixURLWithSeveralHashKeys : url ] ;
2017-07-14 14:41:25 +00:00
2016-05-03 09:05:47 +00:00
[ [ AppDelegate theDelegate ] handleUniversalLinkFragment : fixedURL . fragment ] ;
2016-04-18 15:53:30 +00:00
}
2016-08-26 14:20:20 +00:00
// Open a detail screen about the clicked user
2016-09-29 15:50:20 +00:00
else if ( [ MXTools isMatrixUserIdentifier : absoluteURLString ] )
2016-08-26 14:00:32 +00:00
{
shouldDoAction = NO ;
2017-07-14 14:41:25 +00:00
2016-09-29 15:50:20 +00:00
NSString * userId = absoluteURLString ;
2017-07-14 14:41:25 +00:00
2018-07-20 09:28:02 +00:00
MXRoomMember * member = [ self . roomDataSource . roomState . members memberWithUserId : userId ] ;
2016-08-26 14:00:32 +00:00
if ( member )
{
// Use the room member detail VC for room members
2020-10-16 11:27:45 +00:00
[ self showMemberDetails : member ] ;
2016-08-26 14:00:32 +00:00
}
else
{
// Use the contact detail VC for other users
MXUser * user = [ self . roomDataSource . room . mxSession userWithUserId : userId ] ;
if ( user )
{
selectedContact = [ [ MXKContact alloc ] initMatrixContactWithDisplayName : ( ( user . displayname . length > 0 ) ? user . displayname : user . userId ) andMatrixID : user . userId ] ;
}
else
{
selectedContact = [ [ MXKContact alloc ] initMatrixContactWithDisplayName : userId andMatrixID : userId ] ;
}
[ self performSegueWithIdentifier : @ "showContactDetails" sender : self ] ;
}
}
2016-08-26 14:16:49 +00:00
// Open the clicked room
2016-09-29 15:50:20 +00:00
else if ( [ MXTools isMatrixRoomIdentifier : absoluteURLString ] || [ MXTools isMatrixRoomAlias : absoluteURLString ] )
2016-08-26 14:16:49 +00:00
{
shouldDoAction = NO ;
2017-07-14 14:41:25 +00:00
2016-09-29 15:50:20 +00:00
NSString * roomIdOrAlias = absoluteURLString ;
2017-07-14 14:41:25 +00:00
2016-08-26 14:16:49 +00:00
// Open the room or preview it
2019-02-18 14:31:55 +00:00
NSString * fragment = [ NSString stringWithFormat : @ "/room/%@" , [ MXTools encodeURIComponent : roomIdOrAlias ] ] ;
2016-08-26 14:16:49 +00:00
[ [ AppDelegate theDelegate ] handleUniversalLinkFragment : fragment ] ;
}
2017-12-31 15:24:47 +00:00
// Preview the clicked group
else if ( [ MXTools isMatrixGroupIdentifier : absoluteURLString ] )
{
shouldDoAction = NO ;
// Open the group or preview it
2019-02-18 14:31:55 +00:00
NSString * fragment = [ NSString stringWithFormat : @ "/group/%@" , [ MXTools encodeURIComponent : absoluteURLString ] ] ;
2017-12-31 15:24:47 +00:00
[ [ AppDelegate theDelegate ] handleUniversalLinkFragment : fragment ] ;
}
2019-06-12 14:06:27 +00:00
else if ( [ absoluteURLString hasPrefix : EventFormatterOnReRequestKeysLinkAction ] )
2018-06-08 15:21:52 +00:00
{
2019-06-12 14:06:27 +00:00
NSArray < NSString * > * arguments = [ absoluteURLString componentsSeparatedByString : EventFormatterLinkActionSeparator ] ;
2018-06-08 15:21:52 +00:00
if ( arguments . count > 1 )
{
NSString * eventId = arguments [ 1 ] ;
MXEvent * event = [ self . roomDataSource eventWithEventId : eventId ] ;
if ( event )
{
2018-06-12 14:28:26 +00:00
[ self reRequestKeysAndShowExplanationAlert : event ] ;
2018-06-08 15:21:52 +00:00
}
}
}
2019-06-12 14:06:27 +00:00
else if ( [ absoluteURLString hasPrefix : EventFormatterEditedEventLinkAction ] )
{
NSArray < NSString * > * arguments = [ absoluteURLString componentsSeparatedByString : EventFormatterLinkActionSeparator ] ;
if ( arguments . count > 1 )
{
NSString * eventId = arguments [ 1 ] ;
2019-07-04 17:24:15 +00:00
[ self showEditHistoryForEventId : eventId animated : YES ] ;
2019-06-12 14:06:27 +00:00
}
shouldDoAction = NO ;
}
2019-03-04 17:29:13 +00:00
else if ( url && urlItemInteractionValue )
{
// Fallback case for external links
switch ( urlItemInteractionValue . integerValue ) {
2019-05-15 21:24:34 +00:00
case UITextItemInteractionInvokeDefaultAction :
2019-12-03 18:58:51 +00:00
{
switch ( roomMessageURLType ) {
case RoomMessageURLTypeAppleDataDetector :
// Keep the default OS behavior on single tap when UITextView data detector detect a known type .
shouldDoAction = YES ;
break ;
case RoomMessageURLTypeDummy :
// Do nothing for dummy links
shouldDoAction = NO ;
break ;
default :
2020-07-03 11:23:44 +00:00
{
MXEvent * tappedEvent = userInfo [ kMXKRoomBubbleCellEventKey ] ;
NSString * format = tappedEvent . content [ @ "format" ] ;
NSString * formattedBody = tappedEvent . content [ @ "formatted_body" ] ;
// if an html formatted body exists
if ( [ format isEqualToString : kMXRoomMessageFormatHTML ] && formattedBody )
{
2020-07-14 12:39:04 +00:00
NSURL * visibleURL = [ formattedBodyParser getVisibleURLForURL : url inFormattedBody : formattedBody ] ;
2020-07-03 11:23:44 +00:00
if ( visibleURL && ! [ url isEqual : visibleURL ] )
{
// urls are different , show confirmation alert
NSString * formatStr = NSLocalizedStringFromTable ( @ "external_link_confirmation_message" , @ "Vector" , nil ) ;
UIAlertController * alert = [ UIAlertController alertControllerWithTitle : NSLocalizedStringFromTable ( @ "external_link_confirmation_title" , @ "Vector" , nil ) message : [ NSString stringWithFormat : formatStr , visibleURL . absoluteString , url . absoluteString ] preferredStyle : UIAlertControllerStyleAlert ] ;
UIAlertAction * continueAction = [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "continue" , @ "Vector" , nil ) style : UIAlertActionStyleDefault handler : ^ ( UIAlertAction * _Nonnull action ) {
// Try to open the link
[ [ UIApplication sharedApplication ] vc_open : url completionHandler : ^ ( BOOL success ) {
if ( ! success )
{
[ self showUnableToOpenLinkErrorAlert ] ;
}
} ] ;
} ] ;
2020-07-14 08:27:46 +00:00
UIAlertAction * cancelAction = [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "cancel" , @ "Vector" , nil ) style : UIAlertActionStyleCancel handler : nil ] ;
2020-07-03 11:23:44 +00:00
[ alert addAction : continueAction ] ;
[ alert addAction : cancelAction ] ;
2020-07-14 08:28:17 +00:00
[ self presentViewController : alert animated : YES completion : nil ] ;
2020-07-03 11:23:44 +00:00
return NO ;
}
}
2019-12-03 18:58:51 +00:00
// Try to open the link
[ [ UIApplication sharedApplication ] vc_open : url completionHandler : ^ ( BOOL success ) {
if ( ! success )
{
[ self showUnableToOpenLinkErrorAlert ] ;
}
} ] ;
shouldDoAction = NO ;
break ;
2020-07-03 11:23:44 +00:00
}
2019-12-03 18:58:51 +00:00
}
2019-03-04 17:29:13 +00:00
}
break ;
2019-05-15 21:24:34 +00:00
case UITextItemInteractionPresentActions :
{
2019-12-03 18:58:51 +00:00
// Retrieve the tapped event
MXEvent * tappedEvent = userInfo [ kMXKRoomBubbleCellEventKey ] ;
if ( tappedEvent )
{
// Long press on link , present room contextual menu .
[ self showContextualMenuForEvent : tappedEvent fromSingleTapGesture : NO cell : cell animated : YES ] ;
}
2019-05-15 21:24:34 +00:00
shouldDoAction = NO ;
}
2019-03-04 17:29:13 +00:00
break ;
2019-05-15 21:24:34 +00:00
case UITextItemInteractionPreview :
2019-03-04 17:29:13 +00:00
// Force touch on link , let MXKRoomBubbleTableViewCell UITextView use default peek and pop behavior .
break ;
default :
break ;
}
}
else
{
[ self showUnableToOpenLinkErrorAlert ] ;
}
2016-04-18 15:53:30 +00:00
}
2017-07-14 14:41:25 +00:00
2016-04-18 15:53:30 +00:00
return shouldDoAction ;
}
2018-07-23 14:56:03 +00:00
- ( void ) selectEventWithId : ( NSString * ) eventId
{
2019-06-13 14:38:20 +00:00
[ self selectEventWithId : eventId inputToolBarSendMode : RoomInputToolbarViewSendModeSend showTimestamp : YES ] ;
2019-05-15 21:24:34 +00:00
}
2019-04-30 15:07:43 +00:00
2019-06-13 14:38:20 +00:00
- ( void ) selectEventWithId : ( NSString * ) eventId inputToolBarSendMode : ( RoomInputToolbarViewSendMode ) inputToolBarSendMode showTimestamp : ( BOOL ) showTimestamp
2019-05-15 21:24:34 +00:00
{
2019-06-13 14:38:20 +00:00
[ self setInputToolBarSendMode : inputToolBarSendMode ] ;
2019-05-15 21:24:34 +00:00
customizedRoomDataSource . showBubbleDateTimeOnSelection = showTimestamp ;
2018-07-23 14:56:03 +00:00
customizedRoomDataSource . selectedEventId = eventId ;
2019-05-15 21:24:34 +00:00
// Force table refresh
[ self dataSource : self . roomDataSource didCellChange : nil ] ;
2018-07-23 14:56:03 +00:00
}
2015-12-18 14:20:57 +00:00
- ( void ) cancelEventSelection
{
2019-04-30 15:07:43 +00:00
[ self setInputToolBarSendMode : RoomInputToolbarViewSendModeSend ] ;
2018-07-23 14:56:03 +00:00
2016-05-03 16:29:54 +00:00
if ( currentAlert )
2015-12-18 14:20:57 +00:00
{
2017-07-14 14:41:25 +00:00
[ currentAlert dismissViewControllerAnimated : NO completion : nil ] ;
2016-05-03 16:29:54 +00:00
currentAlert = nil ;
2015-12-18 14:20:57 +00:00
}
2017-07-14 14:41:25 +00:00
2019-05-15 21:24:34 +00:00
customizedRoomDataSource . showBubbleDateTimeOnSelection = YES ;
2015-12-18 14:20:57 +00:00
customizedRoomDataSource . selectedEventId = nil ;
2017-07-14 14:41:25 +00:00
2019-06-13 14:38:20 +00:00
[ self restoreTextMessageBeforeEditing ] ;
2016-03-09 17:29:39 +00:00
// Force table refresh
[ self dataSource : self . roomDataSource didCellChange : nil ] ;
2015-12-18 14:20:57 +00:00
}
2019-03-04 17:29:13 +00:00
- ( void ) showUnableToOpenLinkErrorAlert
{
[ [ AppDelegate theDelegate ] showAlertWithTitle : [ NSBundle mxk_localizedStringForKey : @ "error" ]
message : NSLocalizedStringFromTable ( @ "room_message_unable_open_link_error_message" , @ "Vector" , nil ) ] ;
}
2019-06-13 14:38:20 +00:00
- ( void ) editEventContentWithId : ( NSString * ) eventId
{
MXEvent * event = [ self . roomDataSource eventWithEventId : eventId ] ;
RoomInputToolbarView * roomInputToolbarView = [ self inputToolbarViewAsRoomInputToolbarView ] ;
if ( roomInputToolbarView )
{
self . textMessageBeforeEditing = roomInputToolbarView . textMessage ;
2019-07-03 09:13:53 +00:00
roomInputToolbarView . textMessage = [ self . roomDataSource editableTextMessageForEvent : event ] ;
2019-06-13 14:38:20 +00:00
}
[ self selectEventWithId : eventId inputToolBarSendMode : RoomInputToolbarViewSendModeEdit showTimestamp : YES ] ;
}
- ( void ) restoreTextMessageBeforeEditing
{
RoomInputToolbarView * roomInputToolbarView = [ self inputToolbarViewAsRoomInputToolbarView ] ;
if ( self . textMessageBeforeEditing )
{
roomInputToolbarView . textMessage = self . textMessageBeforeEditing ;
}
self . textMessageBeforeEditing = nil ;
}
- ( RoomInputToolbarView * ) inputToolbarViewAsRoomInputToolbarView
{
RoomInputToolbarView * roomInputToolbarView ;
if ( self . inputToolbarView && [ self . inputToolbarView isKindOfClass : [ RoomInputToolbarView class ] ] )
{
roomInputToolbarView = ( RoomInputToolbarView * ) self . inputToolbarView ;
}
return roomInputToolbarView ;
}
2020-01-22 15:27:04 +00:00
# pragma mark - RoomDataSourceDelegate
- ( void ) roomDataSource : ( RoomDataSource * ) roomDataSource didUpdateEncryptionTrustLevel : ( RoomEncryptionTrustLevel ) roomEncryptionTrustLevel
{
[ self updateInputToolbarEncryptionDecoration ] ;
2020-04-08 13:07:24 +00:00
[ self updateTitleViewEncryptionDecoration ] ;
2020-01-22 15:27:04 +00:00
}
2014-12-23 17:51:49 +00:00
# pragma mark - Segues
2015-06-16 11:54:12 +00:00
- ( void ) prepareForSegue : ( UIStoryboardSegue * ) segue sender : ( id ) sender
{
2015-06-19 22:45:27 +00:00
// Keep ref on destinationViewController
[ super prepareForSegue : segue sender : sender ] ;
2017-07-14 14:41:25 +00:00
2015-06-19 22:45:27 +00:00
id pushedViewController = [ segue destinationViewController ] ;
2017-07-14 14:41:25 +00:00
2020-10-15 11:18:13 +00:00
if ( [ [ segue identifier ] isEqualToString : @ "showRoomSearch" ] )
2015-12-31 09:12:45 +00:00
{
// Dismiss keyboard
[ self dismissKeyboard ] ;
2017-07-14 14:41:25 +00:00
2015-12-31 09:12:45 +00:00
RoomSearchViewController * roomSearchViewController = ( RoomSearchViewController * ) pushedViewController ;
2016-10-05 07:36:52 +00:00
// Add the current data source to be able to search messages .
roomSearchViewController . roomDataSource = self . roomDataSource ;
2015-12-31 09:12:45 +00:00
}
2016-08-26 14:00:32 +00:00
else if ( [ [ segue identifier ] isEqualToString : @ "showContactDetails" ] )
{
if ( selectedContact )
{
ContactDetailsViewController * contactDetailsViewController = segue . destinationViewController ;
2016-11-10 14:21:28 +00:00
contactDetailsViewController . enableVoipCall = NO ;
2016-08-26 14:00:32 +00:00
contactDetailsViewController . contact = selectedContact ;
2017-07-14 14:41:25 +00:00
2016-08-26 14:00:32 +00:00
selectedContact = nil ;
}
}
2017-02-14 17:21:46 +00:00
else if ( [ [ segue identifier ] isEqualToString : @ "showUnknownDevices" ] )
{
if ( unknownDevices )
{
UsersDevicesViewController * usersDevicesViewController = ( UsersDevicesViewController * ) segue . destinationViewController . childViewControllers . firstObject ;
2017-03-15 14:54:50 +00:00
[ usersDevicesViewController displayUsersDevices : unknownDevices andMatrixSession : self . roomDataSource . mxSession onComplete : nil ] ;
2017-07-14 14:41:25 +00:00
2017-02-14 17:21:46 +00:00
unknownDevices = nil ;
}
}
2017-07-14 14:41:25 +00:00
2015-11-20 16:07:45 +00:00
// Hide back button title
2015-11-26 15:47:24 +00:00
self . navigationItem . backBarButtonItem = [ [ UIBarButtonItem alloc ] initWithTitle : @ "" style : UIBarButtonItemStylePlain target : nil action : nil ] ;
2014-12-23 17:51:49 +00:00
}
2018-05-24 13:31:27 +00:00
# pragma mark - RoomInputToolbarViewDelegate
- ( void ) roomInputToolbarViewPresentStickerPicker : ( MXKRoomInputToolbarView * ) toolbarView
{
// Search for the sticker picker widget in the user account
Widget * widget = [ [ WidgetManager sharedManager ] userWidgets : self . roomDataSource . mxSession ofTypes : @ [ kWidgetTypeStickerPicker ] ] . firstObject ;
if ( widget )
{
// Display the widget
[ widget widgetUrl : ^ ( NSString * _Nonnull widgetUrl ) {
StickerPickerViewController * stickerPickerVC = [ [ StickerPickerViewController alloc ] initWithUrl : widgetUrl forWidget : widget ] ;
stickerPickerVC . roomDataSource = self . roomDataSource ;
[ self . navigationController pushViewController : stickerPickerVC animated : YES ] ;
} failure : ^ ( NSError * _Nonnull error ) {
NSLog ( @ "[RoomVC] Cannot display widget %@" , widget ) ;
[ [ AppDelegate theDelegate ] showErrorAsAlert : error ] ;
} ] ;
}
else
{
// The Sticker picker widget is not installed yet . Propose the user to install it
2018-05-24 16:40:19 +00:00
__weak typeof ( self ) weakSelf = self ;
[ currentAlert dismissViewControllerAnimated : NO completion : nil ] ;
NSString * alertMessage = [ NSString stringWithFormat : @ "%@\n%@" ,
NSLocalizedStringFromTable ( @ "widget_sticker_picker_no_stickerpacks_alert" , @ "Vector" , nil ) ,
NSLocalizedStringFromTable ( @ "widget_sticker_picker_no_stickerpacks_alert_add_now" , @ "Vector" , nil )
] ;
currentAlert = [ UIAlertController alertControllerWithTitle : nil message : alertMessage preferredStyle : UIAlertControllerStyleAlert ] ;
[ currentAlert addAction : [ UIAlertAction actionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "no" ]
style : UIAlertActionStyleCancel
handler : ^ ( UIAlertAction * action )
{
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
self -> currentAlert = nil ;
}
} ] ] ;
[ currentAlert addAction : [ UIAlertAction actionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "yes" ]
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action )
{
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
self -> currentAlert = nil ;
// Show the sticker picker settings screen
IntegrationManagerViewController * modularVC = [ [ IntegrationManagerViewController alloc ]
initForMXSession : self . roomDataSource . mxSession
inRoom : self . roomDataSource . roomId
screen : [ IntegrationManagerViewController screenForWidget : kWidgetTypeStickerPicker ]
widgetId : nil ] ;
[ self presentViewController : modularVC animated : NO completion : nil ] ;
}
} ] ] ;
2018-05-30 09:54:35 +00:00
[ currentAlert mxk_setAccessibilityIdentifier : @ "RoomVCStickerPickerAlert" ] ;
2018-05-24 16:40:19 +00:00
[ self presentViewController : currentAlert animated : YES completion : nil ] ;
2018-05-24 13:31:27 +00:00
}
}
2015-08-18 14:24:48 +00:00
# pragma mark - MXKRoomInputToolbarViewDelegate
2016-03-09 17:42:25 +00:00
- ( void ) roomInputToolbarView : ( MXKRoomInputToolbarView * ) toolbarView isTyping : ( BOOL ) typing
{
[ super roomInputToolbarView : toolbarView isTyping : typing ] ;
2018-07-23 14:56:03 +00:00
2016-03-09 17:42:25 +00:00
// Cancel potential selected event ( to leave edition mode )
2018-07-23 14:56:03 +00:00
NSString * selectedEventId = customizedRoomDataSource . selectedEventId ;
if ( typing && selectedEventId && ! [ self . roomDataSource canReplyToEventWithId : selectedEventId ] )
2016-03-09 17:42:25 +00:00
{
[ self cancelEventSelection ] ;
}
}
2015-08-18 14:24:48 +00:00
- ( void ) roomInputToolbarView : ( MXKRoomInputToolbarView * ) toolbarView placeCallWithVideo : ( BOOL ) video
{
2017-08-18 07:02:54 +00:00
__weak __typeof ( self ) weakSelf = self ;
2019-01-07 23:24:11 +00:00
NSString * appDisplayName = [ [ NSBundle mainBundle ] infoDictionary ] [ @ "CFBundleDisplayName" ] ;
2017-08-11 15:02:45 +00:00
2017-08-18 07:02:54 +00:00
// Check app permissions first
[ MXKTools checkAccessForCall : video
manualChangeMessageForAudio : [ NSString stringWithFormat : [ NSBundle mxk_localizedStringForKey : @ "microphone_access_not_granted_for_call" ] , appDisplayName ]
manualChangeMessageForVideo : [ NSString stringWithFormat : [ NSBundle mxk_localizedStringForKey : @ "camera_access_not_granted_for_call" ] , appDisplayName ]
showPopUpInViewController : self completionHandler : ^ ( BOOL granted ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
2017-08-11 15:02:45 +00:00
if ( granted )
{
2017-08-18 07:02:54 +00:00
[ self roomInputToolbarView : toolbarView placeCallWithVideo2 : video ] ;
2017-08-11 15:02:45 +00:00
}
else
{
NSLog ( @ "RoomViewController: Warning: The application does not have the perssion to place the call" ) ;
}
2017-08-18 07:02:54 +00:00
}
} ] ;
}
- ( void ) roomInputToolbarView : ( MXKRoomInputToolbarView * ) toolbarView placeCallWithVideo2 : ( BOOL ) video
{
__weak __typeof ( self ) weakSelf = self ;
// If there is already a jitsi widget , join it
Widget * jitsiWidget = [ customizedRoomDataSource jitsiWidget ] ;
if ( jitsiWidget )
{
[ [ AppDelegate theDelegate ] displayJitsiViewControllerWithWidget : jitsiWidget andVideo : video ] ;
2017-08-11 15:02:45 +00:00
}
2017-08-18 07:02:54 +00:00
// If enabled , create the conf using jitsi widget and open it directly
2018-07-02 12:51:47 +00:00
else if ( RiotSettings . shared . createConferenceCallsWithJitsi
2018-07-17 15:28:32 +00:00
&& self . roomDataSource . room . summary . membersCount . joined > 2 )
2017-08-17 16:59:02 +00:00
{
2017-08-18 07:02:54 +00:00
[ self startActivityIndicator ] ;
2017-08-17 16:59:02 +00:00
[ [ WidgetManager sharedManager ] createJitsiWidgetInRoom : self . roomDataSource . room
withVideo : video
2017-08-18 07:02:54 +00:00
success : ^ ( Widget * jitsiWidget )
{
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
[ self stopActivityIndicator ] ;
[ [ AppDelegate theDelegate ] displayJitsiViewControllerWithWidget : jitsiWidget andVideo : video ] ;
}
}
failure : ^ ( NSError * error )
{
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
[ self stopActivityIndicator ] ;
[ self showJitsiErrorAsAlert : error ] ;
}
} ] ;
}
// Classic conference call is not supported in encrypted rooms
2018-07-17 15:28:32 +00:00
else if ( self . roomDataSource . room . summary . isEncrypted && self . roomDataSource . room . summary . membersCount . joined > 2 )
2016-11-16 14:35:46 +00:00
{
2017-07-14 14:41:25 +00:00
[ currentAlert dismissViewControllerAnimated : NO completion : nil ] ;
2017-08-18 07:02:54 +00:00
2017-07-14 14:41:25 +00:00
currentAlert = [ UIAlertController alertControllerWithTitle : [ NSBundle mxk_localizedStringForKey : @ "room_no_conference_call_in_encrypted_rooms" ] message : nil preferredStyle : UIAlertControllerStyleAlert ] ;
2017-08-18 07:02:54 +00:00
2017-07-14 14:41:25 +00:00
[ currentAlert addAction : [ UIAlertAction actionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "ok" ]
style : UIAlertActionStyleDefault
2017-08-18 07:02:54 +00:00
handler : ^ ( UIAlertAction * action )
{
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
self -> currentAlert = nil ;
}
} ] ] ;
2017-07-14 14:41:25 +00:00
[ currentAlert mxk_setAccessibilityIdentifier : @ "RoomVCCallAlert" ] ;
[ self presentViewController : currentAlert animated : YES completion : nil ] ;
2016-11-16 14:35:46 +00:00
}
2017-08-18 07:02:54 +00:00
2016-08-19 07:59:31 +00:00
// In case of conference call , check that the user has enough power level
2018-07-17 15:28:32 +00:00
else if ( self . roomDataSource . room . summary . membersCount . joined > 2 &&
2018-07-20 09:28:02 +00:00
! [ MXCallManager canPlaceConferenceCallInRoom : self . roomDataSource . room roomState : self . roomDataSource . roomState ] )
2016-08-19 07:57:35 +00:00
{
2017-07-14 14:41:25 +00:00
[ currentAlert dismissViewControllerAnimated : NO completion : nil ] ;
2017-08-18 07:02:54 +00:00
2017-07-14 14:41:25 +00:00
currentAlert = [ UIAlertController alertControllerWithTitle : [ NSBundle mxk_localizedStringForKey : @ "room_no_power_to_create_conference_call" ] message : nil preferredStyle : UIAlertControllerStyleAlert ] ;
2017-08-18 07:02:54 +00:00
2017-07-14 14:41:25 +00:00
[ currentAlert addAction : [ UIAlertAction actionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "ok" ]
style : UIAlertActionStyleDefault
2017-08-18 07:02:54 +00:00
handler : ^ ( UIAlertAction * action )
{
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
self -> currentAlert = nil ;
}
} ] ] ;
2017-07-14 14:41:25 +00:00
[ currentAlert mxk_setAccessibilityIdentifier : @ "RoomVCCallAlert" ] ;
[ self presentViewController : currentAlert animated : YES completion : nil ] ;
2016-08-19 07:57:35 +00:00
}
2017-08-18 07:02:54 +00:00
// Classic 1 : 1 or group call can be done
2016-08-19 07:57:35 +00:00
else
{
2017-08-18 07:02:54 +00:00
[ self . roomDataSource . room placeCallWithVideo : video success : nil failure : nil ] ;
2016-08-19 07:57:35 +00:00
}
2015-08-18 14:24:48 +00:00
}
2016-08-09 10:24:58 +00:00
- ( void ) roomInputToolbarViewHangupCall : ( MXKRoomInputToolbarView * ) toolbarView
{
MXCall * callInRoom = [ self . roomDataSource . mxSession . callManager callInRoom : self . roomDataSource . roomId ] ;
if ( callInRoom )
{
2016-08-09 10:47:52 +00:00
[ callInRoom hangup ] ;
2016-08-09 10:24:58 +00:00
}
2017-08-11 12:59:05 +00:00
else if ( [ [ AppDelegate theDelegate ] . jitsiViewController . widget . roomId isEqualToString : self . roomDataSource . roomId ] )
{
[ [ AppDelegate theDelegate ] . jitsiViewController hangup ] ;
}
[ self refreshActivitiesViewDisplay ] ;
[ self refreshRoomInputToolbar ] ;
2016-08-09 10:24:58 +00:00
}
2016-01-13 09:06:18 +00:00
- ( void ) roomInputToolbarView : ( MXKRoomInputToolbarView * ) toolbarView heightDidChanged : ( CGFloat ) height completion : ( void ( ^ ) ( BOOL finished ) ) completion
{
if ( self . roomInputToolbarContainerHeightConstraint . constant ! = height )
{
// Hide temporarily the placeholder to prevent its distorsion during height animation
if ( ! savedInputToolbarPlaceholder )
{
savedInputToolbarPlaceholder = toolbarView . placeholder . length ? toolbarView . placeholder : @ "" ;
}
toolbarView . placeholder = nil ;
[ super roomInputToolbarView : toolbarView heightDidChanged : height completion : ^ ( BOOL finished ) {
if ( completion )
{
completion ( finished ) ;
}
2017-07-14 14:41:25 +00:00
2017-05-19 12:46:30 +00:00
// Consider here the saved placeholder only if no new placeholder has been defined during the height animation .
2016-01-13 09:06:18 +00:00
if ( ! toolbarView . placeholder )
{
// Restore the placeholder if any
toolbarView . placeholder = savedInputToolbarPlaceholder . length ? savedInputToolbarPlaceholder : nil ;
}
2017-05-19 12:46:30 +00:00
savedInputToolbarPlaceholder = nil ;
2016-01-13 09:06:18 +00:00
} ] ;
}
}
2019-07-11 17:17:55 +00:00
- ( void ) roomInputToolbarViewDidTapFileUpload : ( MXKRoomInputToolbarView * ) toolbarView
{
MXKDocumentPickerPresenter * documentPickerPresenter = [ MXKDocumentPickerPresenter new ] ;
documentPickerPresenter . delegate = self ;
NSArray < MXKUTI * > * allowedUTIs = @ [ MXKUTI . data ] ;
[ documentPickerPresenter presentDocumentPickerWith : allowedUTIs from : self animated : YES completion : nil ] ;
self . documentPickerPresenter = documentPickerPresenter ;
}
2019-08-02 15:19:29 +00:00
- ( void ) roomInputToolbarViewDidTapCamera : ( MXKRoomInputToolbarView * ) toolbarView
{
[ self showCameraControllerAnimated : YES ] ;
}
- ( void ) roomInputToolbarViewDidTapMediaLibrary : ( MXKRoomInputToolbarView * ) toolbarView
{
[ self showMediaPickerAnimated : YES ] ;
}
2016-05-26 16:30:41 +00:00
# pragma mark - RoomParticipantsViewControllerDelegate
- ( void ) roomParticipantsViewController : ( RoomParticipantsViewController * ) roomParticipantsViewController mention : ( MXRoomMember * ) member
{
[ self mention : member ] ;
}
# pragma mark - MXKRoomMemberDetailsViewControllerDelegate
- ( void ) roomMemberDetailsViewController : ( MXKRoomMemberDetailsViewController * ) roomMemberDetailsViewController startChatWithMemberId : ( NSString * ) matrixId completion : ( void ( ^ ) ( void ) ) completion
{
2016-11-02 13:32:02 +00:00
[ [ AppDelegate theDelegate ] createDirectChatWithUserId : matrixId completion : completion ] ;
2016-05-26 16:30:41 +00:00
}
- ( void ) roomMemberDetailsViewController : ( MXKRoomMemberDetailsViewController * ) roomMemberDetailsViewController mention : ( MXRoomMember * ) member
{
[ self mention : member ] ;
}
2015-05-12 12:10:37 +00:00
# pragma mark - Action
2015-06-16 11:54:12 +00:00
- ( IBAction ) onButtonPressed : ( id ) sender
{
2017-09-15 11:27:13 +00:00
// Search button
2015-08-19 08:45:45 +00:00
if ( sender = = self . navigationItem . rightBarButtonItem )
2015-06-16 11:54:12 +00:00
{
2015-12-31 09:12:45 +00:00
[ self performSegueWithIdentifier : @ "showRoomSearch" sender : self ] ;
2015-05-12 12:10:37 +00:00
}
2017-09-15 11:27:13 +00:00
// Matrix Apps button
else if ( self . navigationItem . rightBarButtonItems . count = = 2 && sender = = self . navigationItem . rightBarButtonItems [ 1 ] )
{
2018-05-24 14:02:51 +00:00
if ( [ self widgetsCount : NO ] )
2017-09-18 14:22:59 +00:00
{
2017-09-27 13:21:38 +00:00
WidgetPickerViewController * widgetPicker = [ [ WidgetPickerViewController alloc ] initForMXSession : self . roomDataSource . mxSession
inRoom : self . roomDataSource . roomId ] ;
[ widgetPicker showInViewController : self ] ;
}
else
2017-09-18 14:22:59 +00:00
{
2017-09-27 13:21:38 +00:00
// No widgets -> Directly show the integration manager
2017-09-18 14:22:59 +00:00
IntegrationManagerViewController * modularVC = [ [ IntegrationManagerViewController alloc ] initForMXSession : self . roomDataSource . mxSession
inRoom : self . roomDataSource . roomId
screen : kIntegrationManagerMainScreen
widgetId : nil ] ;
[ self presentViewController : modularVC animated : NO completion : nil ] ;
}
2017-09-15 11:27:13 +00:00
}
2017-06-01 15:20:08 +00:00
else if ( sender = = self . jumpToLastUnreadButton )
{
2017-06-15 09:15:47 +00:00
// Dismiss potential keyboard .
[ self dismissKeyboard ] ;
2018-07-20 09:28:02 +00:00
2017-06-06 08:53:17 +00:00
// Jump to the last unread event by using a temporary room data source initialized with the last unread event id .
2018-07-24 14:30:48 +00:00
MXWeakify ( self ) ;
2018-07-20 09:28:02 +00:00
[ RoomDataSource loadRoomDataSourceWithRoomId : self . roomDataSource . roomId initialEventId : self . roomDataSource . room . accountData . readMarkerEventId andMatrixSession : self . mainSession onComplete : ^ ( id roomDataSource ) {
2018-07-24 14:30:48 +00:00
MXStrongifyAndReturnIfNil ( self ) ;
2018-07-20 09:28:02 +00:00
[ roomDataSource finalizeInitialization ] ;
// Center the bubbles table content on the bottom of the read marker event in order to display correctly the read marker view .
self . centerBubblesTableViewContentOnTheInitialEventBottom = YES ;
[ self displayRoom : roomDataSource ] ;
// Give the data source ownership to the room view controller .
self . hasRoomDataSourceOwnership = YES ;
} ] ;
2017-06-01 15:20:08 +00:00
}
else if ( sender = = self . resetReadMarkerButton )
{
// Move the read marker to the current read receipt position .
[ self . roomDataSource . room forgetReadMarker ] ;
2017-06-13 15:43:32 +00:00
// Hide the banner
self . jumpToLastUnreadBannerContainer . hidden = YES ;
2017-06-01 15:20:08 +00:00
}
2015-05-12 12:10:37 +00:00
}
2016-04-19 13:28:15 +00:00
# pragma mark - UITableViewDelegate
2015-09-08 15:53:53 +00:00
2017-06-01 15:20:08 +00:00
- ( void ) tableView : ( UITableView * ) tableView willDisplayCell : ( UITableViewCell * ) cell forRowAtIndexPath : ( NSIndexPath * ) indexPath
{
2019-01-11 10:45:27 +00:00
cell . backgroundColor = ThemeService . shared . theme . backgroundColor ;
2017-08-11 14:56:09 +00:00
// Update the selected background view
2019-01-11 10:45:27 +00:00
if ( ThemeService . shared . theme . selectedBackgroundColor )
2017-08-11 14:56:09 +00:00
{
cell . selectedBackgroundView = [ [ UIView alloc ] init ] ;
2019-01-11 10:45:27 +00:00
cell . selectedBackgroundView . backgroundColor = ThemeService . shared . theme . selectedBackgroundColor ;
2017-08-11 14:56:09 +00:00
}
else
{
if ( tableView . style = = UITableViewStylePlain )
{
cell . selectedBackgroundView = nil ;
}
else
{
cell . selectedBackgroundView . backgroundColor = nil ;
}
}
2017-06-01 15:20:08 +00:00
if ( [ cell isKindOfClass : MXKRoomBubbleTableViewCell . class ] )
{
MXKRoomBubbleTableViewCell * roomBubbleTableViewCell = ( MXKRoomBubbleTableViewCell * ) cell ;
if ( roomBubbleTableViewCell . readMarkerView )
{
readMarkerTableViewCell = roomBubbleTableViewCell ;
[ self checkReadMarkerVisibility ] ;
}
}
}
- ( void ) tableView : ( UITableView * ) tableView didEndDisplayingCell : ( UITableViewCell * ) cell forRowAtIndexPath : ( NSIndexPath * ) indexPath
{
if ( cell = = readMarkerTableViewCell )
{
readMarkerTableViewCell = nil ;
}
[ super tableView : tableView didEndDisplayingCell : cell forRowAtIndexPath : indexPath ] ;
}
2015-09-08 15:53:53 +00:00
- ( void ) tableView : ( UITableView * ) tableView didSelectRowAtIndexPath : ( NSIndexPath * ) indexPath
{
[ super tableView : tableView didSelectRowAtIndexPath : indexPath ] ;
}
2016-04-20 12:09:14 +00:00
# pragma mark -
2017-06-01 15:20:08 +00:00
- ( void ) scrollViewDidScroll : ( UIScrollView * ) scrollView
{
[ super scrollViewDidScroll : scrollView ] ;
[ self checkReadMarkerVisibility ] ;
2017-06-19 08:32:22 +00:00
// Switch back to the live mode when the user scrolls to the bottom of the non live timeline .
2017-06-22 09:09:17 +00:00
if ( ! self . roomDataSource . isLive && ! [ self isRoomPreview ] )
2017-06-19 08:32:22 +00:00
{
2017-09-27 07:26:34 +00:00
CGFloat contentBottomPosY = self . bubblesTableView . contentOffset . y + self . bubblesTableView . frame . size . height - self . bubblesTableView . mxk_adjustedContentInset . bottom ;
2017-06-19 08:32:22 +00:00
if ( contentBottomPosY >= self . bubblesTableView . contentSize . height && ! [ self . roomDataSource . timeline canPaginate : MXTimelineDirectionForwards ] )
{
[ self goBackToLive ] ;
}
}
2017-06-01 15:20:08 +00:00
}
2016-04-20 12:09:14 +00:00
- ( void ) scrollViewWillBeginDragging : ( UIScrollView * ) scrollView
{
if ( [ MXKRoomViewController instancesRespondToSelector : @ selector ( scrollViewWillBeginDragging : ) ] )
{
[ super scrollViewWillBeginDragging : scrollView ] ;
}
}
- ( void ) scrollViewDidEndDragging : ( UIScrollView * ) scrollView willDecelerate : ( BOOL ) decelerate
{
if ( [ MXKRoomViewController instancesRespondToSelector : @ selector ( scrollViewDidEndDragging : willDecelerate : ) ] )
{
[ super scrollViewDidEndDragging : scrollView willDecelerate : decelerate ] ;
}
if ( decelerate = = NO )
{
// Handle swipe on expanded header
[ self onScrollViewDidEndScrolling : scrollView ] ;
2016-08-29 16:34:00 +00:00
[ self refreshActivitiesViewDisplay ] ;
2017-06-01 15:20:08 +00:00
[ self refreshJumpToLastUnreadBannerDisplay ] ;
2016-04-20 12:09:14 +00:00
}
else
{
// Dispatch async the expanded header handling in order to let the deceleration go first .
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2017-07-14 14:41:25 +00:00
2016-04-20 12:09:14 +00:00
// Handle swipe on expanded header
[ self onScrollViewDidEndScrolling : scrollView ] ;
} ) ;
}
}
2016-08-29 16:34:00 +00:00
- ( void ) scrollViewDidEndDecelerating : ( UIScrollView * ) scrollView
{
if ( [ MXKRoomViewController instancesRespondToSelector : @ selector ( scrollViewDidEndDecelerating : ) ] )
{
[ super scrollViewDidEndDecelerating : scrollView ] ;
}
[ self refreshActivitiesViewDisplay ] ;
2017-06-01 15:20:08 +00:00
[ self refreshJumpToLastUnreadBannerDisplay ] ;
2016-08-29 16:34:00 +00:00
}
- ( void ) scrollViewDidEndScrollingAnimation : ( UIScrollView * ) scrollView
{
if ( [ MXKRoomViewController instancesRespondToSelector : @ selector ( scrollViewDidEndScrollingAnimation : ) ] )
{
[ super scrollViewDidEndScrollingAnimation : scrollView ] ;
}
[ self refreshActivitiesViewDisplay ] ;
2017-06-01 15:20:08 +00:00
[ self refreshJumpToLastUnreadBannerDisplay ] ;
2016-08-29 16:34:00 +00:00
}
2016-04-20 12:09:14 +00:00
- ( void ) onScrollViewDidEndScrolling : ( UIScrollView * ) scrollView
{
2020-09-18 11:11:45 +00:00
2016-04-20 12:09:14 +00:00
}
2016-01-13 18:03:27 +00:00
# pragma mark - MXKRoomTitleViewDelegate
2015-11-20 13:12:37 +00:00
- ( BOOL ) roomTitleViewShouldBeginEditing : ( MXKRoomTitleView * ) titleView
{
2016-01-13 18:03:27 +00:00
// Disable room name edition
2015-11-20 13:12:37 +00:00
return NO ;
}
2016-02-10 09:59:48 +00:00
# pragma mark - RoomTitleViewTapGestureDelegate
- ( void ) roomTitleView : ( RoomTitleView * ) titleView recognizeTapGesture : ( UITapGestureRecognizer * ) tapGestureRecognizer
{
2016-08-22 09:54:26 +00:00
UIView * tappedView = tapGestureRecognizer . view ;
2016-02-10 09:59:48 +00:00
2016-08-22 09:54:26 +00:00
if ( tappedView = = titleView . titleMask )
2016-02-10 09:59:48 +00:00
{
2020-09-18 15:57:40 +00:00
self . roomInfoCoordinatorBridgePresenter = [ [ RoomInfoCoordinatorBridgePresenter alloc ] initWithSession : self . roomDataSource . mxSession room : self . roomDataSource . room ] ;
self . roomInfoCoordinatorBridgePresenter . delegate = self ;
[ self . roomInfoCoordinatorBridgePresenter presentFrom : self animated : YES ] ;
2017-06-20 09:32:54 +00:00
}
2016-08-22 09:54:26 +00:00
else if ( tappedView = = previewHeader . rightButton )
2016-04-14 00:34:30 +00:00
{
2016-07-12 20:40:35 +00:00
// ' Join ' button has been pressed
2016-04-15 12:25:57 +00:00
if ( roomPreviewData )
2016-04-14 00:34:30 +00:00
{
2016-06-10 15:15:42 +00:00
// Attempt to join the room ( keep reference on the potential eventId , the preview data will be removed automatically in case of success ) .
NSString * eventId = roomPreviewData . eventId ;
2016-06-14 20:25:10 +00:00
// We promote here join by room alias instead of room id when an alias is available .
NSString * roomIdOrAlias = roomPreviewData . roomId ;
2020-02-10 16:54:52 +00:00
if ( roomPreviewData . roomCanonicalAlias . length )
{
roomIdOrAlias = roomPreviewData . roomCanonicalAlias ;
}
else if ( roomPreviewData . roomAliases . count )
2016-06-14 20:25:10 +00:00
{
2016-09-05 12:53:09 +00:00
roomIdOrAlias = roomPreviewData . roomAliases . firstObject ;
2016-06-14 20:25:10 +00:00
}
2016-04-15 12:25:57 +00:00
// Note in case of simple link to a room the signUrl param is nil
2019-07-08 09:15:44 +00:00
[ self joinRoomWithRoomIdOrAlias : roomIdOrAlias viaServers : roomPreviewData . viaServers andSignUrl : roomPreviewData . emailInvitation . signUrl completion : ^ ( BOOL succeed ) {
2017-07-14 14:41:25 +00:00
2016-04-14 15:44:12 +00:00
if ( succeed )
{
2016-04-26 15:10:06 +00:00
// If an event was specified , replace the datasource by a non live datasource showing the event
if ( eventId )
{
2018-07-24 14:30:48 +00:00
MXWeakify ( self ) ;
2018-07-20 09:28:02 +00:00
[ RoomDataSource loadRoomDataSourceWithRoomId : self . roomDataSource . roomId initialEventId : eventId andMatrixSession : self . mainSession onComplete : ^ ( id roomDataSource ) {
2018-07-24 14:30:48 +00:00
MXStrongifyAndReturnIfNil ( self ) ;
2018-07-20 09:28:02 +00:00
[ roomDataSource finalizeInitialization ] ;
( ( RoomDataSource * ) roomDataSource ) . markTimelineInitialEvent = YES ;
[ self displayRoom : roomDataSource ] ;
self . hasRoomDataSourceOwnership = YES ;
} ] ;
2016-04-26 15:10:06 +00:00
}
2016-06-10 15:15:42 +00:00
else
{
// Enable back the text input
[ self setRoomInputToolbarViewClass : RoomInputToolbarView . class ] ;
2018-07-27 09:56:51 +00:00
[ self updateInputToolBarViewHeight ] ;
2016-06-10 15:15:42 +00:00
// And the extra area
[ self setRoomActivitiesViewClass : RoomActivitiesView . class ] ;
[ self refreshRoomTitle ] ;
[ self refreshRoomInputToolbar ] ;
}
2016-04-14 15:44:12 +00:00
}
2017-07-14 14:41:25 +00:00
2016-04-14 15:44:12 +00:00
} ] ;
2016-04-14 00:34:30 +00:00
}
else
{
[ self joinRoom : ^ ( BOOL succeed ) {
if ( succeed )
{
[ self refreshRoomTitle ] ;
}
} ] ;
}
}
2016-08-22 09:54:26 +00:00
else if ( tappedView = = previewHeader . leftButton )
2016-04-14 00:34:30 +00:00
{
2016-07-12 20:40:35 +00:00
// ' Decline ' button has been pressed
2016-04-15 07:43:56 +00:00
if ( roomPreviewData )
2016-04-14 00:34:30 +00:00
{
2016-04-14 15:44:12 +00:00
// Decline this invitation = leave this page
[ [ AppDelegate theDelegate ] restoreInitialDisplay : ^ { } ] ;
2016-04-14 00:34:30 +00:00
}
else
{
[ self startActivityIndicator ] ;
[ self . roomDataSource . room leave : ^ {
[ self stopActivityIndicator ] ;
// We remove the current view controller .
// Pop to homes view controller
[ [ AppDelegate theDelegate ] restoreInitialDisplay : ^ { } ] ;
} failure : ^ ( NSError * error ) {
[ self stopActivityIndicator ] ;
2018-07-16 20:30:55 +00:00
NSLog ( @ "[RoomVC] Failed to reject an invited room (%@) failed" , self . roomDataSource . room . roomId ) ;
2016-04-14 00:34:30 +00:00
} ] ;
}
}
2016-02-10 09:59:48 +00:00
}
2016-02-25 12:54:53 +00:00
# pragma mark - Typing management
2015-11-18 08:38:55 +00:00
- ( void ) removeTypingNotificationsListener
{
if ( self . roomDataSource )
{
// Remove the previous live listener
if ( typingNotifListener )
{
2018-07-19 07:47:34 +00:00
MXWeakify ( self ) ;
[ self . roomDataSource . room liveTimeline : ^ ( MXEventTimeline * liveTimeline ) {
MXStrongifyAndReturnIfNil ( self ) ;
[ liveTimeline removeListener : self -> typingNotifListener ] ;
self -> typingNotifListener = nil ;
} ] ;
2015-11-18 08:38:55 +00:00
}
}
2016-06-27 13:34:17 +00:00
currentTypingUsers = nil ;
2015-11-18 08:38:55 +00:00
}
- ( void ) listenTypingNotifications
{
if ( self . roomDataSource )
{
// Add typing notification listener
2018-07-19 07:47:34 +00:00
MXWeakify ( self ) ;
2018-07-23 16:14:45 +00:00
self -> typingNotifListener = [ self . roomDataSource . room listenToEventsOfTypes : @ [ kMXEventTypeStringTypingNotification ] onEvent : ^ ( MXEvent * event , MXTimelineDirection direction , MXRoomState * roomState ) {
2018-07-19 07:47:34 +00:00
MXStrongifyAndReturnIfNil ( self ) ;
2018-07-23 16:14:45 +00:00
// Handle only live events
if ( direction = = MXTimelineDirectionForwards )
{
// Retrieve typing users list
NSMutableArray * typingUsers = [ NSMutableArray arrayWithArray : self . roomDataSource . room . typingUsers ] ;
// Remove typing info for the current user
NSUInteger index = [ typingUsers indexOfObject : self . mainSession . myUser . userId ] ;
if ( index ! = NSNotFound )
2015-11-25 15:53:52 +00:00
{
2018-07-23 16:14:45 +00:00
[ typingUsers removeObjectAtIndex : index ] ;
}
2018-07-19 07:47:34 +00:00
2018-07-23 16:14:45 +00:00
// Ignore this notification if both arrays are empty
if ( self -> currentTypingUsers . count || typingUsers . count )
{
self -> currentTypingUsers = typingUsers ;
[ self refreshActivitiesViewDisplay ] ;
2015-11-25 15:53:52 +00:00
}
2018-07-23 16:14:45 +00:00
}
2015-11-25 15:53:52 +00:00
} ] ;
2018-07-23 16:14:45 +00:00
2016-03-14 10:34:01 +00:00
// Retrieve the current typing users list
NSMutableArray * typingUsers = [ NSMutableArray arrayWithArray : self . roomDataSource . room . typingUsers ] ;
// Remove typing info for the current user
NSUInteger index = [ typingUsers indexOfObject : self . mainSession . myUser . userId ] ;
if ( index ! = NSNotFound )
{
[ typingUsers removeObjectAtIndex : index ] ;
}
currentTypingUsers = typingUsers ;
2016-02-25 12:54:53 +00:00
[ self refreshActivitiesViewDisplay ] ;
2015-11-18 08:38:55 +00:00
}
}
2016-02-25 12:54:53 +00:00
- ( void ) refreshTypingNotification
2015-11-18 08:38:55 +00:00
{
2016-08-29 16:34:00 +00:00
if ( [ self . activitiesView isKindOfClass : RoomActivitiesView . class ] )
2015-11-25 15:53:52 +00:00
{
2016-02-25 12:54:53 +00:00
// Prepare here typing notification
NSString * text = nil ;
NSUInteger count = currentTypingUsers . count ;
2015-11-18 08:38:55 +00:00
2016-02-25 12:54:53 +00:00
// get the room member names
NSMutableArray * names = [ [ NSMutableArray alloc ] init ] ;
2015-11-18 08:38:55 +00:00
2016-02-25 12:54:53 +00:00
// keeps the only the first two users
for ( int i = 0 ; i < MIN ( count , 2 ) ; i + + )
2015-11-18 08:38:55 +00:00
{
2019-01-07 23:24:11 +00:00
NSString * name = currentTypingUsers [ i ] ;
2016-02-25 12:54:53 +00:00
2018-07-20 09:28:02 +00:00
MXRoomMember * member = [ self . roomDataSource . roomState . members memberWithUserId : name ] ;
2016-02-25 12:54:53 +00:00
if ( member && member . displayname . length )
{
name = member . displayname ;
}
// sanity check
if ( name )
{
[ names addObject : name ] ;
}
2015-11-18 08:38:55 +00:00
}
2016-02-25 12:54:53 +00:00
if ( 0 = = names . count )
2015-12-03 10:38:37 +00:00
{
2016-02-25 12:54:53 +00:00
// something to do ?
2015-12-03 10:38:37 +00:00
}
2016-02-25 12:54:53 +00:00
else if ( 1 = = names . count )
{
2019-01-07 23:24:11 +00:00
text = [ NSString stringWithFormat : NSLocalizedStringFromTable ( @ "room_one_user_is_typing" , @ "Vector" , nil ) , names [ 0 ] ] ;
2016-02-25 12:54:53 +00:00
}
else if ( 2 = = names . count )
{
2019-01-07 23:24:11 +00:00
text = [ NSString stringWithFormat : NSLocalizedStringFromTable ( @ "room_two_users_are_typing" , @ "Vector" , nil ) , names [ 0 ] , names [ 1 ] ] ;
2016-02-25 12:54:53 +00:00
}
else
{
2019-01-07 23:24:11 +00:00
text = [ NSString stringWithFormat : NSLocalizedStringFromTable ( @ "room_many_users_are_typing" , @ "Vector" , nil ) , names [ 0 ] , names [ 1 ] ] ;
2016-02-25 12:54:53 +00:00
}
[ ( ( RoomActivitiesView * ) self . activitiesView ) displayTypingNotification : text ] ;
2015-11-18 08:38:55 +00:00
}
2016-02-25 12:54:53 +00:00
}
2016-08-09 10:47:52 +00:00
# pragma mark - Call notifications management
- ( void ) removeCallNotificationsListeners
{
if ( kMXCallStateDidChangeObserver )
{
[ [ NSNotificationCenter defaultCenter ] removeObserver : kMXCallStateDidChangeObserver ] ;
kMXCallStateDidChangeObserver = nil ;
}
if ( kMXCallManagerConferenceStartedObserver )
{
[ [ NSNotificationCenter defaultCenter ] removeObserver : kMXCallManagerConferenceStartedObserver ] ;
kMXCallManagerConferenceStartedObserver = nil ;
}
if ( kMXCallManagerConferenceFinishedObserver )
{
[ [ NSNotificationCenter defaultCenter ] removeObserver : kMXCallManagerConferenceFinishedObserver ] ;
kMXCallManagerConferenceFinishedObserver = nil ;
}
}
- ( void ) listenCallNotifications
{
kMXCallStateDidChangeObserver = [ [ NSNotificationCenter defaultCenter ] addObserverForName : kMXCallStateDidChange object : nil queue : [ NSOperationQueue mainQueue ] usingBlock : ^ ( NSNotification * notif ) {
2017-07-14 14:41:25 +00:00
2016-08-09 10:47:52 +00:00
MXCall * call = notif . object ;
if ( [ call . room . roomId isEqualToString : customizedRoomDataSource . roomId ] )
{
[ self refreshActivitiesViewDisplay ] ;
[ self refreshRoomInputToolbar ] ;
}
} ] ;
kMXCallManagerConferenceStartedObserver = [ [ NSNotificationCenter defaultCenter ] addObserverForName : kMXCallManagerConferenceStarted object : nil queue : [ NSOperationQueue mainQueue ] usingBlock : ^ ( NSNotification * notif ) {
2017-07-14 14:41:25 +00:00
2016-08-09 10:47:52 +00:00
NSString * roomId = notif . object ;
if ( [ roomId isEqualToString : customizedRoomDataSource . roomId ] )
{
[ self refreshActivitiesViewDisplay ] ;
}
} ] ;
kMXCallManagerConferenceFinishedObserver = [ [ NSNotificationCenter defaultCenter ] addObserverForName : kMXCallManagerConferenceFinished object : nil queue : [ NSOperationQueue mainQueue ] usingBlock : ^ ( NSNotification * notif ) {
2017-07-14 14:41:25 +00:00
2016-08-09 10:47:52 +00:00
NSString * roomId = notif . object ;
if ( [ roomId isEqualToString : customizedRoomDataSource . roomId ] )
{
[ self refreshActivitiesViewDisplay ] ;
[ self refreshRoomInputToolbar ] ;
}
} ] ;
}
2018-10-02 13:30:00 +00:00
# pragma mark - Server notices management
- ( void ) removeServerNoticesListener
{
if ( serverNotices )
{
[ serverNotices close ] ;
serverNotices = nil ;
}
}
- ( void ) listenToServerNotices
{
if ( ! serverNotices )
{
serverNotices = [ [ MXServerNotices alloc ] initWithMatrixSession : self . roomDataSource . mxSession ] ;
serverNotices . delegate = self ;
}
}
- ( void ) serverNoticesDidChangeState : ( MXServerNotices * ) serverNotices
{
[ self refreshActivitiesViewDisplay ] ;
}
2017-08-09 15:31:15 +00:00
# pragma mark - Widget notifications management
- ( void ) removeWidgetNotificationsListeners
{
if ( kMXKWidgetManagerDidUpdateWidgetObserver )
{
[ [ NSNotificationCenter defaultCenter ] removeObserver : kMXKWidgetManagerDidUpdateWidgetObserver ] ;
kMXKWidgetManagerDidUpdateWidgetObserver = nil ;
}
}
- ( void ) listenWidgetNotifications
{
2017-08-18 07:49:14 +00:00
kMXKWidgetManagerDidUpdateWidgetObserver = [ [ NSNotificationCenter defaultCenter ] addObserverForName : kWidgetManagerDidUpdateWidgetNotification object : nil queue : [ NSOperationQueue mainQueue ] usingBlock : ^ ( NSNotification * notif ) {
2017-08-09 15:31:15 +00:00
Widget * widget = notif . object ;
if ( widget . mxSession = = self . roomDataSource . mxSession
&& [ widget . roomId isEqualToString : customizedRoomDataSource . roomId ] )
{
// Jitsi conference widget existence is shown in the bottom bar
// Update the bar
[ self refreshActivitiesViewDisplay ] ;
[ self refreshRoomInputToolbar ] ;
2017-09-27 13:40:21 +00:00
[ self refreshRoomTitle ] ;
2017-08-09 15:31:15 +00:00
}
} ] ;
}
2017-08-17 16:59:02 +00:00
- ( void ) showJitsiErrorAsAlert : ( NSError * ) error
{
// Customise the error for permission issues
if ( [ error . domain isEqualToString : WidgetManagerErrorDomain ] && error . code = = WidgetManagerErrorCodeNotEnoughPower )
{
error = [ NSError errorWithDomain : error . domain
code : error . code
userInfo : @ {
NSLocalizedDescriptionKey : NSLocalizedStringFromTable ( @ "room_conference_call_no_power" , @ "Vector" , nil )
} ] ;
}
// Alert user
[ [ AppDelegate theDelegate ] showErrorAsAlert : error ] ;
}
2018-05-07 09:15:18 +00:00
- ( NSUInteger ) widgetsCount : ( BOOL ) includeUserWidgets
2017-09-27 13:21:38 +00:00
{
2020-04-24 07:04:49 +00:00
NSUInteger widgetsCount = [ [ WidgetManager sharedManager ] widgetsNotOfTypes : @ [ kWidgetTypeJitsiV1 , kWidgetTypeJitsiV2 ]
2018-07-20 09:28:02 +00:00
inRoom : self . roomDataSource . room
withRoomState : self . roomDataSource . roomState ] . count ;
2018-05-07 09:15:18 +00:00
if ( includeUserWidgets )
{
2018-05-24 17:01:19 +00:00
widgetsCount + = [ [ WidgetManager sharedManager ] userWidgets : self . roomDataSource . room . mxSession ] . count ;
2018-05-07 09:15:18 +00:00
}
return widgetsCount ;
2017-09-27 13:21:38 +00:00
}
2016-02-25 12:54:53 +00:00
# pragma mark - Unreachable Network Handling
- ( void ) refreshActivitiesViewDisplay
{
2016-08-29 16:34:00 +00:00
if ( [ self . activitiesView isKindOfClass : RoomActivitiesView . class ] )
2015-11-18 08:38:55 +00:00
{
2016-08-08 16:40:22 +00:00
RoomActivitiesView * roomActivitiesView = ( RoomActivitiesView * ) self . activitiesView ;
2017-08-17 16:59:02 +00:00
2016-09-05 08:40:46 +00:00
// Reset gesture recognizers
while ( roomActivitiesView . gestureRecognizers . count )
{
[ roomActivitiesView removeGestureRecognizer : roomActivitiesView . gestureRecognizers [ 0 ] ] ;
}
2017-08-04 11:47:50 +00:00
Widget * jitsiWidget = [ customizedRoomDataSource jitsiWidget ] ;
2018-08-21 16:25:56 +00:00
if ( [ self . roomDataSource . mxSession . syncError . errcode isEqualToString : kMXErrCodeStringResourceLimitExceeded ] )
{
2020-09-01 10:20:44 +00:00
[ roomActivitiesView showResourceLimitExceededError : self . roomDataSource . mxSession . syncError . userInfo onAdminContactTapped : ^ ( NSURL * adminContactURL ) {
[ [ UIApplication sharedApplication ] vc_open : adminContactURL completionHandler : ^ ( BOOL success ) {
if ( ! success )
{
NSLog ( @ "[RoomVC] refreshActivitiesViewDisplay: adminContact(%@) cannot be opened" , adminContactURL ) ;
}
} ] ;
2018-08-21 16:25:56 +00:00
} ] ;
}
else if ( [ AppDelegate theDelegate ] . isOffline )
2016-02-25 12:54:53 +00:00
{
2016-08-08 16:40:22 +00:00
[ roomActivitiesView displayNetworkErrorNotification : NSLocalizedStringFromTable ( @ "room_offline_notification" , @ "Vector" , nil ) ] ;
}
2018-08-07 16:06:58 +00:00
else if ( customizedRoomDataSource . roomState . isObsolete )
2018-07-27 09:56:51 +00:00
{
2019-07-10 12:44:48 +00:00
MXWeakify ( self ) ;
2018-07-27 09:56:51 +00:00
[ roomActivitiesView displayRoomReplacementWithRoomLinkTappedHandler : ^ {
2019-07-10 12:44:48 +00:00
MXStrongifyAndReturnIfNil ( self ) ;
MXEvent * stoneTombEvent = [ self -> customizedRoomDataSource . roomState stateEventsWithType : kMXEventTypeStringRoomTombStone ] . lastObject ;
NSString * replacementRoomId = self -> customizedRoomDataSource . roomState . tombStoneContent . replacementRoomId ;
if ( [ self . roomDataSource . mxSession roomWithRoomId : replacementRoomId ] )
{
// Open the room if it is already joined
[ [ AppDelegate theDelegate ] showRoom : replacementRoomId andEventId : nil withMatrixSession : self . roomDataSource . mxSession ] ;
}
else
{
// Else auto join it via the server that sent the event
NSLog ( @ "[RoomVC] Auto join an upgraded room: %@ -> %@. Sender: %@" , self -> customizedRoomDataSource . roomState . roomId ,
replacementRoomId , stoneTombEvent . sender ) ;
NSString * viaSenderServer = [ MXTools serverNameInMatrixIdentifier : stoneTombEvent . sender ] ;
if ( viaSenderServer )
{
[ self startActivityIndicator ] ;
[ self . roomDataSource . mxSession joinRoom : replacementRoomId viaServers : @ [ viaSenderServer ] success : ^ ( MXRoom * room ) {
[ self stopActivityIndicator ] ;
[ [ AppDelegate theDelegate ] showRoom : replacementRoomId andEventId : nil withMatrixSession : self . roomDataSource . mxSession ] ;
} failure : ^ ( NSError * error ) {
[ self stopActivityIndicator ] ;
NSLog ( @ "[RoomVC] Failed to join an upgraded room. Error: %@" ,
error ) ;
[ [ AppDelegate theDelegate ] showErrorAsAlert : error ] ;
} ] ;
}
}
2018-07-27 09:56:51 +00:00
} ] ;
}
2018-07-20 09:28:02 +00:00
else if ( customizedRoomDataSource . roomState . isOngoingConferenceCall )
2016-08-08 16:40:22 +00:00
{
2016-08-09 11:00:37 +00:00
// Show the "Ongoing conference call" banner only if the user is not in the conference
MXCall * callInRoom = [ self . roomDataSource . mxSession . callManager callInRoom : self . roomDataSource . roomId ] ;
if ( callInRoom && callInRoom . state ! = MXCallStateEnded )
{
if ( [ self checkUnsentMessages ] = = NO )
{
[ self refreshTypingNotification ] ;
}
}
else
{
2016-09-06 11:30:06 +00:00
[ roomActivitiesView displayOngoingConferenceCall : ^ ( BOOL video ) {
2017-07-14 14:41:25 +00:00
2017-06-20 09:32:54 +00:00
NSLog ( @ "[RoomVC] onOngoingConferenceCallPressed" ) ;
2017-07-14 14:41:25 +00:00
2016-09-06 11:30:06 +00:00
// Make sure there is not yet a call
if ( ! [ customizedRoomDataSource . mxSession . callManager callInRoom : customizedRoomDataSource . roomId ] )
{
[ customizedRoomDataSource . room placeCallWithVideo : video success : nil failure : nil ] ;
}
2017-08-17 10:17:07 +00:00
} onClosePressed : nil ] ;
2016-08-09 11:00:37 +00:00
}
2016-02-25 12:54:53 +00:00
}
2017-08-04 11:47:50 +00:00
else if ( jitsiWidget )
{
2017-08-11 12:18:10 +00:00
// The room has an active jitsi widget
// Show it in the banner if the user is not already in
2020-08-31 17:38:06 +00:00
LegacyAppDelegate * appDelegate = [ AppDelegate theDelegate ] ;
2017-08-11 12:18:10 +00:00
if ( [ appDelegate . jitsiViewController . widget . widgetId isEqualToString : jitsiWidget . widgetId ] )
{
if ( [ self checkUnsentMessages ] = = NO )
2017-08-04 11:47:50 +00:00
{
2017-08-11 12:18:10 +00:00
[ self refreshTypingNotification ] ;
2017-08-04 11:47:50 +00:00
}
2017-08-11 12:18:10 +00:00
}
else
{
[ roomActivitiesView displayOngoingConferenceCall : ^ ( BOOL video ) {
NSLog ( @ "[RoomVC] onOngoingConferenceCallPressed (jitsi)" ) ;
2017-08-18 07:02:54 +00:00
__weak __typeof ( self ) weakSelf = self ;
2019-01-07 23:24:11 +00:00
NSString * appDisplayName = [ [ NSBundle mainBundle ] infoDictionary ] [ @ "CFBundleDisplayName" ] ;
2017-08-18 07:02:54 +00:00
// Check app permissions first
[ MXKTools checkAccessForCall : video
manualChangeMessageForAudio : [ NSString stringWithFormat : [ NSBundle mxk_localizedStringForKey : @ "microphone_access_not_granted_for_call" ] , appDisplayName ]
manualChangeMessageForVideo : [ NSString stringWithFormat : [ NSBundle mxk_localizedStringForKey : @ "camera_access_not_granted_for_call" ] , appDisplayName ]
showPopUpInViewController : self completionHandler : ^ ( BOOL granted ) {
if ( weakSelf )
{
if ( granted )
{
// Present the Jitsi view controller
[ appDelegate displayJitsiViewControllerWithWidget : jitsiWidget andVideo : video ] ;
}
else
{
NSLog ( @ "[RoomVC] onOngoingConferenceCallPressed: Warning: The application does not have the perssion to join the call" ) ;
}
}
} ] ;
2017-08-17 10:17:07 +00:00
} onClosePressed : ^ {
[ self startActivityIndicator ] ;
// Close the widget
__weak __typeof ( self ) weakSelf = self ;
[ [ WidgetManager sharedManager ] closeWidget : jitsiWidget . widgetId inRoom : self . roomDataSource . room success : ^ {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
[ self stopActivityIndicator ] ;
2017-08-18 07:49:14 +00:00
// The banner will automatically leave thanks to kWidgetManagerDidUpdateWidgetNotification
2017-08-17 10:17:07 +00:00
}
} failure : ^ ( NSError * error ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
2017-08-17 16:59:02 +00:00
[ self showJitsiErrorAsAlert : error ] ;
2017-08-17 10:17:07 +00:00
[ self stopActivityIndicator ] ;
}
} ] ;
2017-08-11 12:18:10 +00:00
} ] ;
}
2017-08-04 11:47:50 +00:00
}
2016-02-25 12:54:53 +00:00
else if ( [ self checkUnsentMessages ] = = NO )
{
2017-06-06 08:53:17 +00:00
// Show "scroll to bottom" icon when the most recent message is not visible ,
// or when the timelime is not live ( this icon is used to go back to live ) .
2017-06-29 10:03:39 +00:00
// Note : we check if ` currentEventIdAtTableBottom` is set to know whether the table has been rendered at least once .
if ( ! self . roomDataSource . isLive || ( currentEventIdAtTableBottom && [ self isBubblesTableScrollViewAtTheBottom ] = = NO ) )
2016-08-29 16:34:00 +00:00
{
// Retrieve the unread messages count
2017-03-22 14:45:22 +00:00
NSUInteger unreadCount = self . roomDataSource . room . summary . localUnreadEventCount ;
2016-08-29 16:34:00 +00:00
if ( unreadCount = = 0 )
{
// Refresh the typing notification here
// We will keep visible this notification ( if any ) beside the "scroll to bottom" icon .
[ self refreshTypingNotification ] ;
}
[ roomActivitiesView displayScrollToBottomIcon : unreadCount onIconTapGesture : ^ {
2017-06-19 08:32:22 +00:00
[ self goBackToLive ] ;
2016-08-29 16:34:00 +00:00
} ] ;
}
2018-10-02 13:30:00 +00:00
else if ( serverNotices . usageLimit && serverNotices . usageLimit . isServerNoticeUsageLimit )
{
2020-09-01 10:20:44 +00:00
[ roomActivitiesView showResourceUsageLimitNotice : serverNotices . usageLimit onAdminContactTapped : ^ ( NSURL * adminContactURL ) {
[ [ UIApplication sharedApplication ] vc_open : adminContactURL completionHandler : ^ ( BOOL success ) {
if ( ! success )
{
NSLog ( @ "[RoomVC] refreshActivitiesViewDisplay: adminContact(%@) cannot be opened" , adminContactURL ) ;
}
} ] ;
2018-10-02 13:30:00 +00:00
} ] ;
}
2016-08-29 16:34:00 +00:00
else
{
[ self refreshTypingNotification ] ;
}
2016-02-25 12:54:53 +00:00
}
2016-09-05 08:40:46 +00:00
// Recognize swipe downward to dismiss keyboard if any
UISwipeGestureRecognizer * swipe = [ [ UISwipeGestureRecognizer alloc ] initWithTarget : self action : @ selector ( onSwipeGesture : ) ] ;
[ swipe setNumberOfTouchesRequired : 1 ] ;
[ swipe setDirection : UISwipeGestureRecognizerDirectionDown ] ;
[ roomActivitiesView addGestureRecognizer : swipe ] ;
2015-11-18 08:38:55 +00:00
}
2016-02-25 12:54:53 +00:00
}
2017-06-19 08:32:22 +00:00
- ( void ) goBackToLive
{
if ( self . roomDataSource . isLive )
{
// Enable the read marker display , and disable its update ( in order to not mark as read all the new messages by default ) .
self . roomDataSource . showReadMarker = YES ;
self . updateRoomReadMarker = NO ;
[ self scrollBubblesTableViewToBottomAnimated : YES ] ;
}
else
{
// Switch back to the room live timeline managed by MXKRoomDataSourceManager
MXKRoomDataSourceManager * roomDataSourceManager = [ MXKRoomDataSourceManager sharedManagerForMatrixSession : self . mainSession ] ;
2018-07-20 09:28:02 +00:00
2018-07-24 14:30:48 +00:00
MXWeakify ( self ) ;
2018-07-20 09:28:02 +00:00
[ roomDataSourceManager roomDataSourceForRoom : self . roomDataSource . roomId create : YES onComplete : ^ ( MXKRoomDataSource * roomDataSource ) {
2018-07-24 14:30:48 +00:00
MXStrongifyAndReturnIfNil ( self ) ;
2018-07-20 09:28:02 +00:00
// Scroll to bottom the bubble history on the display refresh .
2018-07-24 14:30:48 +00:00
self -> shouldScrollToBottomOnTableRefresh = YES ;
2018-07-20 09:28:02 +00:00
[ self displayRoom : roomDataSource ] ;
// The room view controller do not have here the data source ownership .
self . hasRoomDataSourceOwnership = NO ;
[ self refreshActivitiesViewDisplay ] ;
[ self refreshJumpToLastUnreadBannerDisplay ] ;
if ( self . saveProgressTextInput )
{
// Restore the potential message partially typed before jump to last unread messages .
self . inputToolbarView . textMessage = roomDataSource . partialTextMessage ;
}
} ] ;
2017-06-19 08:32:22 +00:00
}
}
2016-08-31 13:12:46 +00:00
# pragma mark - Missed discussions handling
- ( void ) refreshMissedDiscussionsCount : ( BOOL ) force
{
2016-08-31 18:57:08 +00:00
// Ignore this action when no room is displayed
2017-03-21 13:20:47 +00:00
if ( ! self . roomDataSource || ! missedDiscussionsBarButtonCustomView )
2016-08-31 18:57:08 +00:00
{
return ;
}
2016-09-05 12:00:56 +00:00
NSUInteger highlightCount = 0 ;
2017-04-18 13:55:51 +00:00
NSUInteger missedCount = [ [ AppDelegate theDelegate ] . masterTabBarController missedDiscussionsCount ] ;
// Compute the missed notifications count of the current room by considering its notification mode in Riot .
NSUInteger roomNotificationCount = self . roomDataSource . room . summary . notificationCount ;
if ( self . roomDataSource . room . isMentionsOnly )
{
// Only the highlighted missed messages must be considered here .
roomNotificationCount = self . roomDataSource . room . summary . highlightCount ;
}
// Remove the current room from the missed discussion counter .
if ( missedCount && roomNotificationCount )
2016-08-31 13:47:34 +00:00
{
2016-09-05 12:00:56 +00:00
missedCount - - ;
}
if ( missedCount )
{
// Compute the missed highlight count
2017-04-18 13:55:51 +00:00
highlightCount = [ [ AppDelegate theDelegate ] . masterTabBarController missedHighlightDiscussionsCount ] ;
if ( highlightCount && self . roomDataSource . room . summary . highlightCount )
2016-09-05 12:00:56 +00:00
{
// Remove the current room from the missed highlight counter
highlightCount - - ;
}
2016-08-31 13:47:34 +00:00
}
2016-09-05 12:00:56 +00:00
if ( force || missedDiscussionsCount ! = missedCount || missedHighlightCount ! = highlightCount )
2016-08-31 13:12:46 +00:00
{
2016-09-05 12:00:56 +00:00
missedDiscussionsCount = missedCount ;
missedHighlightCount = highlightCount ;
2016-08-31 13:12:46 +00:00
NSMutableArray * leftBarButtonItems = [ NSMutableArray arrayWithArray : self . navigationItem . leftBarButtonItems ] ;
2016-09-05 12:00:56 +00:00
if ( missedCount )
2016-08-31 13:12:46 +00:00
{
2016-08-31 18:57:08 +00:00
// Refresh missed discussions count label
2016-09-05 12:00:56 +00:00
if ( missedCount > 99 )
2016-08-31 13:12:46 +00:00
{
missedDiscussionsBadgeLabel . text = @ "99+" ;
}
else
{
2016-09-05 12:00:56 +00:00
missedDiscussionsBadgeLabel . text = [ NSString stringWithFormat : @ "%tu" , missedCount ] ;
2016-08-31 13:12:46 +00:00
}
[ missedDiscussionsBadgeLabel sizeToFit ] ;
2016-08-31 18:57:08 +00:00
// Update the label background view frame
2016-08-31 13:12:46 +00:00
CGRect frame = missedDiscussionsBadgeLabelBgView . frame ;
2016-08-31 18:57:08 +00:00
frame . size . width = round ( missedDiscussionsBadgeLabel . frame . size . width + 18 ) ;
2017-09-27 20:41:51 +00:00
if ( [ GBDeviceInfo deviceInfo ] . osVersion . major < 11 )
2016-08-31 13:12:46 +00:00
{
2017-09-27 20:41:51 +00:00
// Consider the main navigation controller if the current view controller is embedded inside a split view controller .
UINavigationController * mainNavigationController = self . navigationController ;
if ( self . splitViewController . isCollapsed && self . splitViewController . viewControllers . count )
{
mainNavigationController = self . splitViewController . viewControllers . firstObject ;
}
UINavigationItem * backItem = mainNavigationController . navigationBar . backItem ;
UIBarButtonItem * backButton = backItem . backBarButtonItem ;
if ( backButton && ! backButton . title . length )
{
// Shift the badge on the left to be close the back icon
frame . origin . x = ( [ GBDeviceInfo deviceInfo ] . displayInfo . display > GBDeviceDisplay4Inch ? -35 : -25 ) ;
}
else
{
frame . origin . x = 0 ;
}
2016-08-31 13:12:46 +00:00
}
2017-09-27 20:41:51 +00:00
2016-08-31 18:57:08 +00:00
// Caution : set label background view frame only in case of changes to prevent from looping on ' viewDidLayoutSubviews ' .
if ( ! CGRectEqualToRect ( missedDiscussionsBadgeLabelBgView . frame , frame ) )
{
missedDiscussionsBadgeLabelBgView . frame = frame ;
}
2016-08-31 13:12:46 +00:00
2016-09-05 12:00:56 +00:00
// Set the right background color
if ( highlightCount )
{
2019-02-12 18:06:22 +00:00
missedDiscussionsBadgeLabelBgView . backgroundColor = ThemeService . shared . theme . noticeColor ;
2016-09-05 12:00:56 +00:00
}
else
{
2019-02-12 18:06:22 +00:00
missedDiscussionsBadgeLabelBgView . backgroundColor = ThemeService . shared . theme . noticeSecondaryColor ;
2016-09-05 12:00:56 +00:00
}
2016-08-31 18:57:08 +00:00
if ( ! missedDiscussionsButton || [ leftBarButtonItems indexOfObject : missedDiscussionsButton ] = = NSNotFound )
{
missedDiscussionsButton = [ [ UIBarButtonItem alloc ] initWithCustomView : missedDiscussionsBarButtonCustomView ] ;
// Add it in left bar items
[ leftBarButtonItems addObject : missedDiscussionsButton ] ;
}
}
else if ( missedDiscussionsButton )
{
[ leftBarButtonItems removeObject : missedDiscussionsButton ] ;
missedDiscussionsButton = nil ;
2016-08-31 13:12:46 +00:00
}
self . navigationItem . leftBarButtonItems = leftBarButtonItems ;
}
}
2016-02-25 12:54:53 +00:00
# pragma mark - Unsent Messages Handling
- ( BOOL ) checkUnsentMessages
{
BOOL hasUnsent = NO ;
2017-02-14 10:01:38 +00:00
BOOL hasUnsentDueToUnknownDevices = NO ;
2017-07-14 14:41:25 +00:00
2016-08-29 16:34:00 +00:00
if ( [ self . activitiesView isKindOfClass : RoomActivitiesView . class ] )
2015-11-18 08:38:55 +00:00
{
2017-02-14 10:01:38 +00:00
NSArray < MXEvent * > * outgoingMsgs = self . roomDataSource . room . outgoingMessages ;
2016-02-25 12:54:53 +00:00
for ( MXEvent * event in outgoingMsgs )
{
2016-12-05 15:32:21 +00:00
if ( event . sentState = = MXEventSentStateFailed )
2016-02-25 12:54:53 +00:00
{
hasUnsent = YES ;
2017-07-14 14:41:25 +00:00
2017-02-14 10:01:38 +00:00
// Check if the error is due to unknown devices
if ( [ event . sentError . domain isEqualToString : MXEncryptingErrorDomain ]
2017-07-14 14:41:25 +00:00
&& event . sentError . code = = MXEncryptingErrorUnknownDeviceCode )
2017-02-14 10:01:38 +00:00
{
hasUnsentDueToUnknownDevices = YES ;
break ;
}
2016-02-25 12:54:53 +00:00
}
}
if ( hasUnsent )
{
2017-02-14 10:01:38 +00:00
NSString * notification = hasUnsentDueToUnknownDevices ?
2017-07-14 14:41:25 +00:00
NSLocalizedStringFromTable ( @ "room_unsent_messages_unknown_devices_notification" , @ "Vector" , nil ) :
NSLocalizedStringFromTable ( @ "room_unsent_messages_notification" , @ "Vector" , nil ) ;
2016-03-07 12:56:52 +00:00
RoomActivitiesView * roomActivitiesView = ( RoomActivitiesView * ) self . activitiesView ;
2017-02-14 10:01:38 +00:00
[ roomActivitiesView displayUnsentMessagesNotification : notification withResendLink : ^ {
2016-02-25 12:54:53 +00:00
2016-03-07 12:56:52 +00:00
[ self resendAllUnsentMessages ] ;
2016-02-25 12:54:53 +00:00
2017-03-07 15:46:56 +00:00
} andCancelLink : ^ {
2017-07-14 14:41:25 +00:00
2017-03-07 15:46:56 +00:00
[ self cancelAllUnsentMessages ] ;
2017-07-14 14:41:25 +00:00
2016-03-07 12:56:52 +00:00
} andIconTapGesture : ^ {
2017-07-14 14:41:25 +00:00
2016-05-03 16:29:54 +00:00
if ( currentAlert )
2016-02-25 12:54:53 +00:00
{
2017-07-14 14:41:25 +00:00
[ currentAlert dismissViewControllerAnimated : NO completion : nil ] ;
2016-03-07 12:56:52 +00:00
}
__weak __typeof ( self ) weakSelf = self ;
2017-07-14 14:41:25 +00:00
currentAlert = [ UIAlertController alertControllerWithTitle : nil message : nil preferredStyle : UIAlertControllerStyleActionSheet ] ;
2016-03-07 12:56:52 +00:00
2017-07-14 14:41:25 +00:00
[ currentAlert addAction : [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "room_resend_unsent_messages" , @ "Vector" , nil )
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
[ self resendAllUnsentMessages ] ;
self -> currentAlert = nil ;
}
} ] ] ;
2016-03-07 12:56:52 +00:00
2017-07-14 14:41:25 +00:00
[ currentAlert addAction : [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "room_delete_unsent_messages" , @ "Vector" , nil )
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
[ self cancelAllUnsentMessages ] ;
self -> currentAlert = nil ;
}
} ] ] ;
2016-03-07 12:56:52 +00:00
2017-07-14 14:41:25 +00:00
[ currentAlert addAction : [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "cancel" , @ "Vector" , nil )
2018-01-19 02:43:28 +00:00
style : UIAlertActionStyleCancel
2017-07-14 14:41:25 +00:00
handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
self -> currentAlert = nil ;
}
} ] ] ;
2016-03-07 12:56:52 +00:00
2017-07-14 14:41:25 +00:00
[ currentAlert mxk_setAccessibilityIdentifier : @ "RoomVCUnsentMessagesMenuAlert" ] ;
[ currentAlert popoverPresentationController ] . sourceView = roomActivitiesView ;
[ currentAlert popoverPresentationController ] . sourceRect = roomActivitiesView . bounds ;
[ self presentViewController : currentAlert animated : YES completion : nil ] ;
2016-02-25 12:54:53 +00:00
} ] ;
}
2015-11-18 08:38:55 +00:00
}
2016-02-25 12:54:53 +00:00
return hasUnsent ;
}
2017-02-14 10:01:38 +00:00
- ( void ) eventDidChangeSentState : ( NSNotification * ) notif
{
// We are only interested by event that has just failed in their encryption
// because of unknown devices in the room
MXEvent * event = notif . object ;
if ( event . sentState = = MXEventSentStateFailed &&
[ event . roomId isEqualToString : self . roomDataSource . roomId ]
&& [ event . sentError . domain isEqualToString : MXEncryptingErrorDomain ]
&& event . sentError . code = = MXEncryptingErrorUnknownDeviceCode
2017-02-15 10:36:22 +00:00
&& ! unknownDevices ) // Show the alert once in case of resending several events
2017-02-14 10:01:38 +00:00
{
__weak __typeof ( self ) weakSelf = self ;
2017-07-14 14:41:25 +00:00
2017-02-14 10:01:38 +00:00
[ self dismissTemporarySubViews ] ;
2017-07-14 14:41:25 +00:00
2017-02-15 10:36:22 +00:00
// List all unknown devices
unknownDevices = [ [ MXUsersDevicesMap alloc ] init ] ;
2017-07-14 14:41:25 +00:00
2017-02-15 10:36:22 +00:00
NSArray < MXEvent * > * outgoingMsgs = self . roomDataSource . room . outgoingMessages ;
for ( MXEvent * event in outgoingMsgs )
{
if ( event . sentState = = MXEventSentStateFailed
&& [ event . sentError . domain isEqualToString : MXEncryptingErrorDomain ]
&& event . sentError . code = = MXEncryptingErrorUnknownDeviceCode )
{
MXUsersDevicesMap < MXDeviceInfo * > * eventUnknownDevices = event . sentError . userInfo [ MXEncryptingErrorUnknownDeviceDevicesKey ] ;
2017-07-14 14:41:25 +00:00
2017-02-15 10:36:22 +00:00
[ unknownDevices addEntriesFromMap : eventUnknownDevices ] ;
}
}
2017-07-14 14:41:25 +00:00
currentAlert = [ UIAlertController alertControllerWithTitle : [ NSBundle mxk_localizedStringForKey : @ "unknown_devices_alert_title" ]
message : [ NSBundle mxk_localizedStringForKey : @ "unknown_devices_alert" ]
preferredStyle : UIAlertControllerStyleAlert ] ;
[ currentAlert addAction : [ UIAlertAction actionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "unknown_devices_verify" ]
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
self -> currentAlert = nil ;
[ self performSegueWithIdentifier : @ "showUnknownDevices" sender : self ] ;
}
} ] ] ;
[ currentAlert addAction : [ UIAlertAction actionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "unknown_devices_send_anyway" ]
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
self -> currentAlert = nil ;
// Acknowledge the existence of all devices
[ self startActivityIndicator ] ;
[ self . mainSession . crypto setDevicesKnown : self -> unknownDevices complete : ^ {
self -> unknownDevices = nil ;
[ self stopActivityIndicator ] ;
// And resend pending messages
[ self resendAllUnsentMessages ] ;
} ] ;
}
} ] ] ;
[ currentAlert mxk_setAccessibilityIdentifier : @ "RoomVCUnknownDevicesAlert" ] ;
[ self presentViewController : currentAlert animated : YES completion : nil ] ;
2017-02-14 10:01:38 +00:00
}
}
2019-06-27 10:25:01 +00:00
- ( void ) eventDidChangeIdentifier : ( NSNotification * ) notif
{
MXEvent * event = notif . object ;
NSString * previousId = notif . userInfo [ kMXEventIdentifierKey ] ;
if ( [ customizedRoomDataSource . selectedEventId isEqualToString : previousId ] )
{
NSLog ( @ "[RoomVC] eventDidChangeIdentifier: Update selectedEventId" ) ;
customizedRoomDataSource . selectedEventId = event . eventId ;
}
}
2017-03-08 08:13:48 +00:00
2016-03-07 12:56:52 +00:00
- ( void ) resendAllUnsentMessages
{
// List unsent event ids
NSArray * outgoingMsgs = self . roomDataSource . room . outgoingMessages ;
NSMutableArray * failedEventIds = [ NSMutableArray arrayWithCapacity : outgoingMsgs . count ] ;
for ( MXEvent * event in outgoingMsgs )
{
2016-12-05 15:32:21 +00:00
if ( event . sentState = = MXEventSentStateFailed )
2016-03-07 12:56:52 +00:00
{
[ failedEventIds addObject : event . eventId ] ;
}
}
// Launch iterative operation
[ self resendFailedEvent : 0 inArray : failedEventIds ] ;
}
2016-02-25 12:54:53 +00:00
- ( void ) resendFailedEvent : ( NSUInteger ) index inArray : ( NSArray * ) failedEventIds
{
if ( index < failedEventIds . count )
2015-11-24 12:34:08 +00:00
{
2016-02-25 12:54:53 +00:00
NSString * failedEventId = failedEventIds [ index ] ;
NSUInteger nextIndex = index + 1 ;
// Let the datasource resend . It will manage local echo , etc .
[ self . roomDataSource resendEventWithEventId : failedEventId success : ^ ( NSString * eventId ) {
[ self resendFailedEvent : nextIndex inArray : failedEventIds ] ;
} failure : ^ ( NSError * error ) {
[ self resendFailedEvent : nextIndex inArray : failedEventIds ] ;
} ] ;
return ;
2015-11-24 12:34:08 +00:00
}
2016-02-25 12:54:53 +00:00
// Refresh activities view
[ self refreshActivitiesViewDisplay ] ;
2015-11-18 08:38:55 +00:00
}
2017-03-07 15:46:56 +00:00
- ( void ) cancelAllUnsentMessages
{
// Remove unsent event ids
for ( NSUInteger index = 0 ; index < self . roomDataSource . room . outgoingMessages . count ; )
{
MXEvent * event = self . roomDataSource . room . outgoingMessages [ index ] ;
if ( event . sentState = = MXEventSentStateFailed )
{
[ self . roomDataSource removeEventWithEventId : event . eventId ] ;
}
else
{
index + + ;
}
}
}
2016-11-16 08:31:58 +00:00
# pragma mark - Encryption Information view
- ( void ) showEncryptionInformation : ( MXEvent * ) event
{
[ self dismissKeyboard ] ;
2017-07-14 14:41:25 +00:00
2016-11-16 08:31:58 +00:00
// Remove potential existing subviews
[ self dismissTemporarySubViews ] ;
encryptionInfoView = [ [ EncryptionInfoView alloc ] initWithEvent : event andMatrixSession : self . roomDataSource . mxSession ] ;
2016-11-28 15:26:59 +00:00
// Add shadow on added view
2016-11-16 08:31:58 +00:00
encryptionInfoView . layer . cornerRadius = 5 ;
encryptionInfoView . layer . shadowOffset = CGSizeMake ( 0 , 1 ) ;
encryptionInfoView . layer . shadowOpacity = 0.5 f ;
// Add the view and define edge constraints
[ self . view addSubview : encryptionInfoView ] ;
[ self . view addConstraint : [ NSLayoutConstraint constraintWithItem : encryptionInfoView
attribute : NSLayoutAttributeTop
relatedBy : NSLayoutRelationEqual
toItem : self . topLayoutGuide
attribute : NSLayoutAttributeBottom
multiplier : 1.0 f
constant : 10.0 f ] ] ;
[ self . view addConstraint : [ NSLayoutConstraint constraintWithItem : encryptionInfoView
attribute : NSLayoutAttributeBottom
relatedBy : NSLayoutRelationEqual
toItem : self . bottomLayoutGuide
attribute : NSLayoutAttributeTop
multiplier : 1.0 f
constant : -10.0 f ] ] ;
[ self . view addConstraint : [ NSLayoutConstraint constraintWithItem : self . view
attribute : NSLayoutAttributeLeading
relatedBy : NSLayoutRelationEqual
toItem : encryptionInfoView
attribute : NSLayoutAttributeLeading
multiplier : 1.0 f
constant : -10.0 f ] ] ;
[ self . view addConstraint : [ NSLayoutConstraint constraintWithItem : self . view
attribute : NSLayoutAttributeTrailing
relatedBy : NSLayoutRelationEqual
toItem : encryptionInfoView
attribute : NSLayoutAttributeTrailing
multiplier : 1.0 f
constant : 10.0 f ] ] ;
[ self . view setNeedsUpdateConstraints ] ;
}
2017-06-01 15:20:08 +00:00
# pragma mark - Read marker handling
- ( void ) checkReadMarkerVisibility
{
2017-06-09 12:20:00 +00:00
if ( readMarkerTableViewCell && isAppeared && ! self . isBubbleTableViewDisplayInTransition )
2017-06-01 15:20:08 +00:00
{
// Check whether the read marker is visible
2017-09-27 07:26:34 +00:00
CGFloat contentTopPosY = self . bubblesTableView . contentOffset . y + self . bubblesTableView . mxk_adjustedContentInset . top ;
2017-06-07 11:07:44 +00:00
CGFloat readMarkerViewPosY = readMarkerTableViewCell . frame . origin . y + readMarkerTableViewCell . readMarkerView . frame . origin . y ;
if ( contentTopPosY <= readMarkerViewPosY )
2017-06-01 15:20:08 +00:00
{
2017-06-07 11:07:44 +00:00
// Compute the max vertical position visible according to contentOffset
2017-09-27 07:26:34 +00:00
CGFloat contentBottomPosY = self . bubblesTableView . contentOffset . y + self . bubblesTableView . frame . size . height - self . bubblesTableView . mxk_adjustedContentInset . bottom ;
2017-06-07 11:07:44 +00:00
if ( readMarkerViewPosY <= contentBottomPosY )
{
// Launch animation
[ self animateReadMarkerView ] ;
// Disable the read marker display when it has been rendered once .
self . roomDataSource . showReadMarker = NO ;
[ self refreshJumpToLastUnreadBannerDisplay ] ;
// Update the read marker position according the events acknowledgement in this view controller .
self . updateRoomReadMarker = YES ;
2017-06-09 12:20:00 +00:00
if ( self . roomDataSource . isLive )
{
// Move the read marker to the current read receipt position .
[ self . roomDataSource . room forgetReadMarker ] ;
}
2017-06-07 11:07:44 +00:00
}
2017-06-01 15:20:08 +00:00
}
}
}
- ( void ) animateReadMarkerView
{
// Check whether the cell with the read marker is known and if the marker is not animated yet .
if ( readMarkerTableViewCell && readMarkerTableViewCell . readMarkerView . isHidden )
{
RoomBubbleCellData * cellData = ( RoomBubbleCellData * ) readMarkerTableViewCell . bubbleData ;
// Do not display the marker if this is the last message .
if ( cellData . containsLastMessage && readMarkerTableViewCell . readMarkerView . tag = = cellData . mostRecentComponentIndex )
{
readMarkerTableViewCell . readMarkerView . hidden = YES ;
readMarkerTableViewCell = nil ;
}
else
{
readMarkerTableViewCell . readMarkerView . hidden = NO ;
// Animate the layout to hide the read marker
dispatch_after ( dispatch_time ( DISPATCH_TIME _NOW , ( int64_t ) ( 0.5 * NSEC_PER _SEC ) ) , dispatch_get _main _queue ( ) , ^ {
[ UIView animateWithDuration : 1.5 delay : 0 options : UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseIn
animations : ^ {
readMarkerTableViewCell . readMarkerViewLeadingConstraint . constant = readMarkerTableViewCell . readMarkerViewTrailingConstraint . constant = readMarkerTableViewCell . bubbleOverlayContainer . frame . size . width / 2 ;
readMarkerTableViewCell . readMarkerView . alpha = 0 ;
// Force to render the view
[ readMarkerTableViewCell . bubbleOverlayContainer layoutIfNeeded ] ;
}
completion : ^ ( BOOL finished ) {
readMarkerTableViewCell . readMarkerView . hidden = YES ;
readMarkerTableViewCell . readMarkerView . alpha = 1 ;
readMarkerTableViewCell = nil ;
} ] ;
} ) ;
}
}
}
- ( void ) refreshJumpToLastUnreadBannerDisplay
{
2017-06-14 21:09:56 +00:00
// This banner is only displayed when the room timeline is in live ( and no peeking ) .
2017-06-06 08:53:17 +00:00
// Check whether the read marker exists and has not been rendered yet .
2017-06-14 21:09:56 +00:00
if ( self . roomDataSource . isLive && ! self . roomDataSource . isPeeking && self . roomDataSource . showReadMarker && self . roomDataSource . room . accountData . readMarkerEventId )
2017-06-01 15:20:08 +00:00
{
UITableViewCell * cell = [ self . bubblesTableView visibleCells ] . firstObject ;
if ( [ cell isKindOfClass : MXKRoomBubbleTableViewCell . class ] )
{
MXKRoomBubbleTableViewCell * roomBubbleTableViewCell = ( MXKRoomBubbleTableViewCell * ) cell ;
// Check whether the read marker is inside the first displayed cell .
if ( roomBubbleTableViewCell . readMarkerView )
{
2017-06-09 12:20:00 +00:00
// The read marker display is still enabled ( see roomDataSource . showReadMarker flag ) ,
// this means the read marker was not been visible yet .
// We show the banner if the marker is located in the top hidden part of the cell .
2017-09-27 07:26:34 +00:00
CGFloat contentTopPosY = self . bubblesTableView . contentOffset . y + self . bubblesTableView . mxk_adjustedContentInset . top ;
2017-06-09 12:20:00 +00:00
CGFloat readMarkerViewPosY = roomBubbleTableViewCell . frame . origin . y + roomBubbleTableViewCell . readMarkerView . frame . origin . y ;
self . jumpToLastUnreadBannerContainer . hidden = ( contentTopPosY < readMarkerViewPosY ) ;
2017-06-01 15:20:08 +00:00
}
else
{
2017-06-13 15:46:13 +00:00
// Check whether the read marker event is anterior to the first event displayed in the first rendered cell .
MXKRoomBubbleComponent * component = roomBubbleTableViewCell . bubbleData . bubbleComponents . firstObject ;
MXEvent * firstDisplayedEvent = component . event ;
2017-06-01 15:20:08 +00:00
MXEvent * currentReadMarkerEvent = [ self . roomDataSource . mxSession . store eventWithEventId : self . roomDataSource . room . accountData . readMarkerEventId inRoom : self . roomDataSource . roomId ] ;
2017-06-13 15:46:13 +00:00
if ( ! currentReadMarkerEvent || ( currentReadMarkerEvent . originServerTs < firstDisplayedEvent . originServerTs ) )
2017-06-01 15:20:08 +00:00
{
self . jumpToLastUnreadBannerContainer . hidden = NO ;
}
else
{
self . jumpToLastUnreadBannerContainer . hidden = YES ;
}
}
}
}
else
{
self . jumpToLastUnreadBannerContainer . hidden = YES ;
2017-06-08 08:30:29 +00:00
2017-06-14 21:09:56 +00:00
// Initialize the read marker if it does not exist yet , only in case of live timeline .
if ( ! self . roomDataSource . room . accountData . readMarkerEventId && self . roomDataSource . isLive && ! self . roomDataSource . isPeeking )
2017-06-08 08:30:29 +00:00
{
// Move the read marker to the current read receipt position by default .
[ self . roomDataSource . room forgetReadMarker ] ;
}
2017-06-01 15:20:08 +00:00
}
}
2017-06-20 09:32:54 +00:00
# pragma mark - ContactsTableViewControllerDelegate
- ( void ) contactsTableViewController : ( ContactsTableViewController * ) contactsTableViewController didSelectContact : ( MXKContact * ) contact
{
__weak typeof ( self ) weakSelf = self ;
if ( currentAlert )
{
2017-07-14 14:41:25 +00:00
[ currentAlert dismissViewControllerAnimated : NO completion : nil ] ;
2017-06-20 09:32:54 +00:00
currentAlert = nil ;
}
// Invite ?
NSString * promptMsg = [ NSString stringWithFormat : NSLocalizedStringFromTable ( @ "room_participants_invite_prompt_msg" , @ "Vector" , nil ) , contact . displayName ] ;
2017-07-14 14:41:25 +00:00
currentAlert = [ UIAlertController alertControllerWithTitle : NSLocalizedStringFromTable ( @ "room_participants_invite_prompt_title" , @ "Vector" , nil )
message : promptMsg
preferredStyle : UIAlertControllerStyleAlert ] ;
[ currentAlert addAction : [ UIAlertAction actionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "cancel" ]
2018-01-19 02:43:28 +00:00
style : UIAlertActionStyleCancel
2017-07-14 14:41:25 +00:00
handler : ^ ( UIAlertAction * action ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
self -> currentAlert = nil ;
}
} ] ] ;
[ currentAlert addAction : [ UIAlertAction actionWithTitle : NSLocalizedStringFromTable ( @ "invite" , @ "Vector" , nil )
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action ) {
// Sanity check
if ( ! weakSelf )
{
return ;
}
typeof ( self ) self = weakSelf ;
self -> currentAlert = nil ;
MXSession * session = self . roomDataSource . mxSession ;
NSString * roomId = self . roomDataSource . roomId ;
MXRoom * room = [ session roomWithRoomId : roomId ] ;
NSArray * identifiers = contact . matrixIdentifiers ;
NSString * participantId ;
if ( identifiers . count )
{
participantId = identifiers . firstObject ;
// Invite this user if a room is defined
[ room inviteUser : participantId success : ^ {
// Refresh display by removing the contacts picker
[ contactsTableViewController withdrawViewControllerAnimated : YES completion : nil ] ;
} failure : ^ ( NSError * error ) {
NSLog ( @ "[RoomVC] Invite %@ failed" , participantId ) ;
// Alert user
[ [ AppDelegate theDelegate ] showErrorAsAlert : error ] ;
} ] ;
}
else
{
if ( contact . emailAddresses . count )
{
// This is a local contact , consider the first email by default .
// TODO : Prompt the user to select the right email .
MXKEmail * email = contact . emailAddresses . firstObject ;
participantId = email . emailAddress ;
}
else
{
// This is the text filled by the user .
participantId = contact . displayName ;
}
// Is it an email or a Matrix user ID ?
if ( [ MXTools isEmailAddress : participantId ] )
{
[ room inviteUserByEmail : participantId success : ^ {
// Refresh display by removing the contacts picker
[ contactsTableViewController withdrawViewControllerAnimated : YES completion : nil ] ;
} failure : ^ ( NSError * error ) {
NSLog ( @ "[RoomVC] Invite be email %@ failed" , participantId ) ;
// Alert user
2019-09-17 08:06:10 +00:00
if ( [ error . domain isEqualToString : kMXRestClientErrorDomain ]
&& error . code = = MXRestClientErrorMissingIdentityServer )
{
2019-09-17 08:56:46 +00:00
NSString * message = [ NSBundle mxk_localizedStringForKey : @ "error_invite_3pid_with_no_identity_server" ] ;
2019-09-17 08:06:10 +00:00
[ [ AppDelegate theDelegate ] showAlertWithTitle : message message : nil ] ;
}
else
{
[ [ AppDelegate theDelegate ] showErrorAsAlert : error ] ;
}
2017-07-14 14:41:25 +00:00
} ] ;
}
else // if ( [ MXTools isMatrixUserIdentifier : participantId ] )
{
[ room inviteUser : participantId success : ^ {
// Refresh display by removing the contacts picker
[ contactsTableViewController withdrawViewControllerAnimated : YES completion : nil ] ;
} failure : ^ ( NSError * error ) {
NSLog ( @ "[RoomVC] Invite %@ failed" , participantId ) ;
// Alert user
[ [ AppDelegate theDelegate ] showErrorAsAlert : error ] ;
} ] ;
}
}
} ] ] ;
[ currentAlert mxk_setAccessibilityIdentifier : @ "RoomVCInviteAlert" ] ;
[ self presentViewController : currentAlert animated : YES completion : nil ] ;
2017-06-20 09:32:54 +00:00
}
2018-06-08 15:21:52 +00:00
# pragma mark - Re - request encryption keys
2018-06-12 14:28:26 +00:00
- ( void ) reRequestKeysAndShowExplanationAlert : ( MXEvent * ) event
2018-06-08 15:21:52 +00:00
{
2018-06-12 14:28:26 +00:00
MXWeakify ( self ) ;
__block UIAlertController * alert ;
// Make the re - request
[ self . mainSession . crypto reRequestRoomKeyForEvent : event ] ;
// Observe kMXEventDidDecryptNotification to remove automatically the dialog
// if the user has shared the keys from another device
2018-06-12 16:21:04 +00:00
mxEventDidDecryptNotificationObserver = [ [ NSNotificationCenter defaultCenter ] addObserverForName : kMXEventDidDecryptNotification object : nil queue : [ NSOperationQueue mainQueue ] usingBlock : ^ ( NSNotification * notif ) {
MXStrongifyAndReturnIfNil ( self ) ;
2018-06-12 14:28:26 +00:00
MXEvent * decryptedEvent = notif . object ;
if ( [ decryptedEvent . eventId isEqualToString : event . eventId ] )
{
2018-06-12 16:21:04 +00:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : self -> mxEventDidDecryptNotificationObserver ] ;
self -> mxEventDidDecryptNotificationObserver = nil ;
2018-06-12 14:28:26 +00:00
if ( self -> currentAlert = = alert )
{
[ self -> currentAlert dismissViewControllerAnimated : YES completion : nil ] ;
self -> currentAlert = nil ;
}
}
} ] ;
// Show the explanation dialog
alert = [ UIAlertController alertControllerWithTitle : NSLocalizedStringFromTable ( @ "rerequest_keys_alert_title" , @ "Vector" , nil )
2018-06-08 15:21:52 +00:00
message : NSLocalizedStringFromTable ( @ "rerequest_keys_alert_message" , @ "Vector" , nil )
preferredStyle : UIAlertControllerStyleAlert ] ;
2018-06-12 14:28:26 +00:00
currentAlert = alert ;
2018-06-08 15:21:52 +00:00
2018-06-12 14:28:26 +00:00
[ alert addAction : [ UIAlertAction actionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "ok" ]
2018-06-08 15:21:52 +00:00
style : UIAlertActionStyleDefault
handler : ^ ( UIAlertAction * action )
{
MXStrongifyAndReturnIfNil ( self ) ;
2018-06-12 16:21:04 +00:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : self -> mxEventDidDecryptNotificationObserver ] ;
self -> mxEventDidDecryptNotificationObserver = nil ;
2018-06-08 15:21:52 +00:00
self -> currentAlert = nil ;
} ] ] ;
[ self presentViewController : currentAlert animated : YES completion : nil ] ;
}
2018-07-27 09:56:51 +00:00
# pragma mark Tombstone event
- ( void ) listenTombstoneEventNotifications
{
// Room is already obsolete do not listen to tombstone event
2018-08-07 16:20:22 +00:00
if ( self . roomDataSource . roomState . isObsolete )
2018-07-27 09:56:51 +00:00
{
return ;
}
MXWeakify ( self ) ;
2018-08-07 16:20:22 +00:00
tombstoneEventNotificationsListener = [ self . roomDataSource . room listenToEventsOfTypes : @ [ kMXEventTypeStringRoomTombStone ] onEvent : ^ ( MXEvent * event , MXTimelineDirection direction , MXRoomState * roomState ) {
2018-07-27 09:56:51 +00:00
MXStrongifyAndReturnIfNil ( self ) ;
// Update activitiesView with room replacement information
[ self refreshActivitiesViewDisplay ] ;
// Hide inputToolbarView
2018-08-27 13:32:37 +00:00
[ self updateRoomInputToolbarViewClassIfNeeded ] ;
2018-07-27 09:56:51 +00:00
} ] ;
}
- ( void ) removeTombstoneEventNotificationsListener
{
if ( self . roomDataSource )
{
// Remove the previous live listener
if ( tombstoneEventNotificationsListener )
{
2018-08-07 16:20:22 +00:00
[ self . roomDataSource . room removeListener : tombstoneEventNotificationsListener ] ;
2018-07-27 09:56:51 +00:00
tombstoneEventNotificationsListener = nil ;
}
}
}
2018-08-21 16:25:56 +00:00
# pragma mark MXSession state change
- ( void ) listenMXSessionStateChangeNotifications
{
kMXSessionStateDidChangeObserver = [ [ NSNotificationCenter defaultCenter ] addObserverForName : kMXSessionStateDidChangeNotification object : self . roomDataSource . mxSession queue : [ NSOperationQueue mainQueue ] usingBlock : ^ ( NSNotification * notif ) {
if ( self . roomDataSource . mxSession . state = = MXSessionStateSyncError
|| self . roomDataSource . mxSession . state = = MXSessionStateRunning )
{
[ self refreshActivitiesViewDisplay ] ;
2018-08-23 15:03:32 +00:00
// update inputToolbarView
2018-08-27 13:32:37 +00:00
[ self updateRoomInputToolbarViewClassIfNeeded ] ;
2018-08-21 16:25:56 +00:00
}
} ] ;
}
- ( void ) removeMXSessionStateChangeNotificationsListener
{
if ( kMXSessionStateDidChangeObserver )
{
[ [ NSNotificationCenter defaultCenter ] removeObserver : kMXSessionStateDidChangeObserver ] ;
kMXSessionStateDidChangeObserver = nil ;
}
}
2019-05-15 21:24:34 +00:00
# pragma mark - Contextual Menu
- ( NSArray < RoomContextualMenuItem * > * ) contextualMenuItemsForEvent : ( MXEvent * ) event andCell : ( id < MXKCellRendering > ) cell
{
NSString * eventId = event . eventId ;
MXKRoomBubbleTableViewCell * roomBubbleTableViewCell = ( MXKRoomBubbleTableViewCell * ) cell ;
MXKAttachment * attachment = roomBubbleTableViewCell . bubbleData . attachment ;
MXWeakify ( self ) ;
// Copy action
2020-01-14 19:23:36 +00:00
BOOL isCopyActionEnabled = ! attachment || attachment . type ! = MXKAttachmentTypeSticker ;
2020-10-13 22:01:29 +00:00
if ( attachment && ! BuildSettings . messageDetailsAllowCopyMedia )
{
isCopyActionEnabled = NO ;
}
2020-01-14 19:23:36 +00:00
if ( isCopyActionEnabled )
{
switch ( event . eventType ) {
case MXEventTypeRoomMessage :
{
NSString * messageType = event . content [ @ "msgtype" ] ;
if ( [ messageType isEqualToString : kMXMessageTypeKeyVerificationRequest ] )
{
isCopyActionEnabled = NO ;
}
break ;
}
case MXEventTypeKeyVerificationStart :
case MXEventTypeKeyVerificationAccept :
case MXEventTypeKeyVerificationKey :
case MXEventTypeKeyVerificationMac :
case MXEventTypeKeyVerificationDone :
case MXEventTypeKeyVerificationCancel :
isCopyActionEnabled = NO ;
break ;
default :
break ;
}
}
2019-05-15 21:24:34 +00:00
RoomContextualMenuItem * copyMenuItem = [ [ RoomContextualMenuItem alloc ] initWithMenuAction : RoomContextualMenuActionCopy ] ;
2020-01-14 19:23:36 +00:00
copyMenuItem . isEnabled = isCopyActionEnabled ;
2019-05-15 21:24:34 +00:00
copyMenuItem . action = ^ {
MXStrongifyAndReturnIfNil ( self ) ;
if ( ! attachment )
{
NSArray * components = roomBubbleTableViewCell . bubbleData . bubbleComponents ;
MXKRoomBubbleComponent * selectedComponent ;
for ( selectedComponent in components )
{
if ( [ selectedComponent . event . eventId isEqualToString : event . eventId ] )
{
break ;
}
selectedComponent = nil ;
}
NSString * textMessage = selectedComponent . textMessage ;
2019-10-21 15:36:58 +00:00
if ( textMessage )
{
2020-10-09 12:20:54 +00:00
MXKPasteboardManager . shared . pasteboard . string = textMessage ;
2019-10-21 15:36:58 +00:00
}
else
{
2019-10-22 08:55:28 +00:00
NSLog ( @ "[RoomViewController] Contextual menu copy failed. Text is nil for room id/event id: %@/%@" , selectedComponent . event . roomId , selectedComponent . event . eventId ) ;
2019-10-21 15:36:58 +00:00
}
2019-05-15 21:24:34 +00:00
[ self hideContextualMenuAnimated : YES ] ;
}
else if ( attachment . type ! = MXKAttachmentTypeSticker )
{
[ self hideContextualMenuAnimated : YES completion : ^ {
[ self startActivityIndicator ] ;
[ attachment copy : ^ {
[ self stopActivityIndicator ] ;
} failure : ^ ( NSError * error ) {
[ self stopActivityIndicator ] ;
// Alert user
[ [ AppDelegate theDelegate ] showErrorAsAlert : error ] ;
} ] ;
// Start animation in case of download during attachment preparing
[ roomBubbleTableViewCell startProgressUI ] ;
} ] ;
}
} ;
// Reply action
RoomContextualMenuItem * replyMenuItem = [ [ RoomContextualMenuItem alloc ] initWithMenuAction : RoomContextualMenuActionReply ] ;
replyMenuItem . isEnabled = [ self . roomDataSource canReplyToEventWithId : eventId ] ;
replyMenuItem . action = ^ {
MXStrongifyAndReturnIfNil ( self ) ;
[ self hideContextualMenuAnimated : YES cancelEventSelection : NO completion : nil ] ;
2019-06-13 14:38:20 +00:00
[ self selectEventWithId : eventId inputToolBarSendMode : RoomInputToolbarViewSendModeReply showTimestamp : NO ] ;
2019-06-14 14:28:21 +00:00
// And display the keyboard
[ self . inputToolbarView becomeFirstResponder ] ;
2019-05-15 21:24:34 +00:00
} ;
// Edit action
RoomContextualMenuItem * editMenuItem = [ [ RoomContextualMenuItem alloc ] initWithMenuAction : RoomContextualMenuActionEdit ] ;
2019-06-13 14:38:20 +00:00
editMenuItem . action = ^ {
MXStrongifyAndReturnIfNil ( self ) ;
[ self hideContextualMenuAnimated : YES cancelEventSelection : NO completion : nil ] ;
[ self editEventContentWithId : eventId ] ;
2019-06-14 14:28:21 +00:00
// And display the keyboard
[ self . inputToolbarView becomeFirstResponder ] ;
2019-06-13 14:38:20 +00:00
} ;
editMenuItem . isEnabled = [ self . roomDataSource canEditEventWithId : eventId ] ;
2019-05-15 21:24:34 +00:00
// More action
RoomContextualMenuItem * moreMenuItem = [ [ RoomContextualMenuItem alloc ] initWithMenuAction : RoomContextualMenuActionMore ] ;
moreMenuItem . action = ^ {
MXStrongifyAndReturnIfNil ( self ) ;
2019-06-27 08:23:20 +00:00
[ self hideContextualMenuAnimated : YES completion : nil ] ;
[ self showAdditionalActionsMenuForEvent : event inCell : cell animated : YES ] ;
2019-05-15 21:24:34 +00:00
} ;
// Actions list
NSArray < RoomContextualMenuItem * > * actionItems = @ [
copyMenuItem ,
replyMenuItem ,
editMenuItem ,
moreMenuItem
] ;
return actionItems ;
}
2019-06-27 09:41:25 +00:00
- ( void ) showContextualMenuForEvent : ( MXEvent * ) event fromSingleTapGesture : ( BOOL ) usedSingleTapGesture cell : ( id < MXKCellRendering > ) cell animated : ( BOOL ) animated
2019-05-15 21:24:34 +00:00
{
if ( self . roomContextualMenuPresenter . isPresenting )
{
return ;
}
2019-06-25 12:00:12 +00:00
NSString * selectedEventId = event . eventId ;
2019-05-15 21:24:34 +00:00
2019-06-25 12:00:12 +00:00
NSArray < RoomContextualMenuItem * > * contextualMenuItems = [ self contextualMenuItemsForEvent : event andCell : cell ] ;
ReactionsMenuViewModel * reactionsMenuViewModel ;
CGRect bubbleComponentFrameInOverlayView = CGRectNull ;
2019-05-22 16:48:50 +00:00
2019-07-09 11:51:14 +00:00
if ( [ cell isKindOfClass : MXKRoomBubbleTableViewCell . class ] && [ self . roomDataSource canReactToEventWithId : event . eventId ] )
2019-05-17 10:10:32 +00:00
{
2019-05-22 16:48:50 +00:00
MXKRoomBubbleTableViewCell * roomBubbleTableViewCell = ( MXKRoomBubbleTableViewCell * ) cell ;
MXKRoomBubbleCellData * bubbleCellData = roomBubbleTableViewCell . bubbleData ;
NSArray * bubbleComponents = bubbleCellData . bubbleComponents ;
2019-07-25 14:46:35 +00:00
NSInteger foundComponentIndex = [ bubbleCellData bubbleComponentIndexForEventId : event . eventId ] ;
2019-05-22 16:48:50 +00:00
CGRect bubbleComponentFrame ;
if ( bubbleComponents . count > 0 )
{
NSInteger selectedComponentIndex = foundComponentIndex ! = NSNotFound ? foundComponentIndex : 0 ;
2019-07-08 16:43:55 +00:00
bubbleComponentFrame = [ roomBubbleTableViewCell surroundingFrameInTableViewForComponentIndex : selectedComponentIndex ] ;
2019-05-22 16:48:50 +00:00
}
else
{
bubbleComponentFrame = roomBubbleTableViewCell . frame ;
}
2019-06-25 12:00:12 +00:00
bubbleComponentFrameInOverlayView = [ self . bubblesTableView convertRect : bubbleComponentFrame toView : self . overlayContainerView ] ;
2019-05-22 16:48:50 +00:00
2019-06-06 09:30:57 +00:00
NSString * roomId = self . roomDataSource . roomId ;
MXAggregations * aggregations = self . mainSession . aggregations ;
2019-06-25 12:00:12 +00:00
MXAggregatedReactions * aggregatedReactions = [ aggregations aggregatedReactionsOnEvent : selectedEventId inRoom : roomId ] ;
2019-06-06 09:30:57 +00:00
2019-06-25 12:00:12 +00:00
reactionsMenuViewModel = [ [ ReactionsMenuViewModel alloc ] initWithAggregatedReactions : aggregatedReactions eventId : selectedEventId ] ;
2019-06-06 09:30:57 +00:00
reactionsMenuViewModel . coordinatorDelegate = self ;
2019-05-17 10:10:32 +00:00
}
2019-06-25 12:00:12 +00:00
2019-06-27 12:37:17 +00:00
if ( ! self . roomContextualMenuViewController )
{
self . roomContextualMenuViewController = [ RoomContextualMenuViewController instantiate ] ;
self . roomContextualMenuViewController . delegate = self ;
}
[ self . roomContextualMenuViewController updateWithContextualMenuItems : contextualMenuItems reactionsMenuViewModel : reactionsMenuViewModel ] ;
2019-06-25 12:00:12 +00:00
[ self enableOverlayContainerUserInteractions : YES ] ;
2019-06-27 12:37:17 +00:00
[ self . roomContextualMenuPresenter presentWithRoomContextualMenuViewController : self . roomContextualMenuViewController
2019-06-25 12:00:12 +00:00
from : self
on : self . overlayContainerView
contentToReactFrame : bubbleComponentFrameInOverlayView
2019-06-27 09:41:25 +00:00
fromSingleTapGesture : usedSingleTapGesture
animated : animated
2019-06-25 12:00:12 +00:00
completion : ^ {
} ] ;
2019-06-27 09:41:25 +00:00
2019-10-28 17:55:55 +00:00
preventBubblesTableViewScroll = YES ;
2019-10-29 08:19:37 +00:00
[ self selectEventWithId : selectedEventId ] ;
2019-05-15 21:24:34 +00:00
}
- ( void ) hideContextualMenuAnimated : ( BOOL ) animated
{
[ self hideContextualMenuAnimated : animated completion : nil ] ;
}
- ( void ) hideContextualMenuAnimated : ( BOOL ) animated completion : ( void ( ^ ) ( void ) ) completion
{
[ self hideContextualMenuAnimated : animated cancelEventSelection : YES completion : completion ] ;
}
- ( void ) hideContextualMenuAnimated : ( BOOL ) animated cancelEventSelection : ( BOOL ) cancelEventSelection completion : ( void ( ^ ) ( void ) ) completion
{
if ( ! self . roomContextualMenuPresenter . isPresenting )
{
return ;
}
if ( cancelEventSelection )
{
[ self cancelEventSelection ] ;
}
2019-10-29 08:19:37 +00:00
preventBubblesTableViewScroll = NO ;
2019-05-15 21:24:34 +00:00
[ self . roomContextualMenuPresenter hideContextualMenuWithAnimated : animated completion : ^ {
2019-05-24 09:33:44 +00:00
[ self enableOverlayContainerUserInteractions : NO ] ;
2019-05-15 21:24:34 +00:00
if ( completion )
{
completion ( ) ;
}
} ] ;
}
2019-05-24 09:33:44 +00:00
- ( void ) enableOverlayContainerUserInteractions : ( BOOL ) enableOverlayContainerUserInteractions
2019-05-15 21:24:34 +00:00
{
2019-05-24 09:33:44 +00:00
self . inputToolbarView . editable = ! enableOverlayContainerUserInteractions ;
self . bubblesTableView . scrollsToTop = ! enableOverlayContainerUserInteractions ;
self . overlayContainerView . userInteractionEnabled = enableOverlayContainerUserInteractions ;
2019-05-15 21:24:34 +00:00
}
# pragma mark - RoomContextualMenuViewControllerDelegate
- ( void ) roomContextualMenuViewControllerDidTapBackgroundOverlay : ( RoomContextualMenuViewController * ) viewController
{
[ self hideContextualMenuAnimated : YES ] ;
}
2019-06-06 09:30:57 +00:00
# pragma mark - ReactionsMenuViewModelCoordinatorDelegate
- ( void ) reactionsMenuViewModel : ( ReactionsMenuViewModel * ) viewModel didAddReaction : ( NSString * ) reaction forEventId : ( NSString * ) eventId
{
2019-06-11 15:43:32 +00:00
MXWeakify ( self ) ;
2019-06-25 12:00:12 +00:00
[ self hideContextualMenuAnimated : YES completion : ^ {
2019-06-06 09:30:57 +00:00
2019-06-25 12:00:12 +00:00
[ self . roomDataSource addReaction : reaction forEventId : eventId success : ^ {
} failure : ^ ( NSError * error ) {
MXStrongifyAndReturnIfNil ( self ) ;
[ self . errorPresenter presentErrorFromViewController : self forError : error animated : YES handler : nil ] ;
} ] ;
2019-06-06 09:30:57 +00:00
} ] ;
}
- ( void ) reactionsMenuViewModel : ( ReactionsMenuViewModel * ) viewModel didRemoveReaction : ( NSString * ) reaction forEventId : ( NSString * ) eventId
{
2019-06-11 15:43:32 +00:00
MXWeakify ( self ) ;
2019-06-25 12:00:12 +00:00
[ self hideContextualMenuAnimated : YES completion : ^ {
2019-06-06 09:30:57 +00:00
2019-06-25 12:00:12 +00:00
[ self . roomDataSource removeReaction : reaction forEventId : eventId success : ^ {
} failure : ^ ( NSError * error ) {
MXStrongifyAndReturnIfNil ( self ) ;
[ self . errorPresenter presentErrorFromViewController : self forError : error animated : YES handler : nil ] ;
} ] ;
2019-06-06 09:30:57 +00:00
} ] ;
}
2019-07-25 14:46:35 +00:00
- ( void ) reactionsMenuViewModelDidTapMoreReactions : ( ReactionsMenuViewModel * ) viewModel forEventId : ( NSString * ) eventId
{
[ self hideContextualMenuAnimated : YES ] ;
EmojiPickerCoordinatorBridgePresenter * emojiPickerCoordinatorBridgePresenter = [ [ EmojiPickerCoordinatorBridgePresenter alloc ] initWithSession : self . mainSession roomId : self . roomDataSource . roomId eventId : eventId ] ;
emojiPickerCoordinatorBridgePresenter . delegate = self ;
NSInteger cellRow = [ self . roomDataSource indexOfCellDataWithEventId : eventId ] ;
UIView * sourceView ;
CGRect sourceRect = CGRectNull ;
2019-07-25 17:16:15 +00:00
if ( cellRow >= 0 )
2019-07-25 14:46:35 +00:00
{
NSIndexPath * cellIndexPath = [ NSIndexPath indexPathForRow : cellRow inSection : 0 ] ;
UITableViewCell * cell = [ self . bubblesTableView cellForRowAtIndexPath : cellIndexPath ] ;
sourceView = cell ;
if ( [ cell isKindOfClass : [ MXKRoomBubbleTableViewCell class ] ] )
{
MXKRoomBubbleTableViewCell * roomBubbleTableViewCell = ( MXKRoomBubbleTableViewCell * ) cell ;
NSInteger bubbleComponentIndex = [ roomBubbleTableViewCell . bubbleData bubbleComponentIndexForEventId : eventId ] ;
sourceRect = [ roomBubbleTableViewCell componentFrameInContentViewForIndex : bubbleComponentIndex ] ;
}
}
[ emojiPickerCoordinatorBridgePresenter presentFrom : self sourceView : sourceView sourceRect : sourceRect animated : YES ] ;
self . emojiPickerCoordinatorBridgePresenter = emojiPickerCoordinatorBridgePresenter ;
}
2019-07-04 17:24:15 +00:00
# pragma mark -
- ( void ) showEditHistoryForEventId : ( NSString * ) eventId animated : ( BOOL ) animated
{
MXEvent * event = [ self . roomDataSource eventWithEventId : eventId ] ;
EditHistoryCoordinatorBridgePresenter * presenter = [ [ EditHistoryCoordinatorBridgePresenter alloc ] initWithSession : self . roomDataSource . mxSession event : event ] ;
presenter . delegate = self ;
[ presenter presentFrom : self animated : animated ] ;
self . editHistoryPresenter = presenter ;
}
# pragma mark - EditHistoryCoordinatorBridgePresenterDelegate
- ( void ) editHistoryCoordinatorBridgePresenterDelegateDidComplete : ( EditHistoryCoordinatorBridgePresenter * ) coordinatorBridgePresenter
{
[ coordinatorBridgePresenter dismissWithAnimated : YES completion : nil ] ;
self . editHistoryPresenter = nil ;
}
2019-07-11 17:17:55 +00:00
# pragma mark - DocumentPickerPresenterDelegate
- ( void ) documentPickerPresenterWasCancelled : ( MXKDocumentPickerPresenter * ) presenter
{
self . documentPickerPresenter = nil ;
}
- ( void ) documentPickerPresenter : ( MXKDocumentPickerPresenter * ) presenter didPickDocumentsAt : ( NSURL * ) url
{
self . documentPickerPresenter = nil ;
MXKUTI * fileUTI = [ [ MXKUTI alloc ] initWithLocalFileURL : url ] ;
NSString * mimeType = fileUTI . mimeType ;
if ( fileUTI . isImage )
{
NSData * imageData = [ [ NSData alloc ] initWithContentsOfURL : url ] ;
[ self . roomDataSource sendImage : imageData mimeType : mimeType success : nil failure : ^ ( NSError * error ) {
// Nothing to do . The image is marked as unsent in the room history by the datasource
NSLog ( @ "[MXKRoomViewController] sendImage failed." ) ;
} ] ;
}
else if ( fileUTI . isVideo )
{
[ ( RoomDataSource * ) self . roomDataSource sendVideo : url success : nil failure : ^ ( NSError * error ) {
// Nothing to do . The video is marked as unsent in the room history by the datasource
NSLog ( @ "[MXKRoomViewController] sendVideo failed." ) ;
} ] ;
}
else if ( fileUTI . isFile )
{
[ self . roomDataSource sendFile : url mimeType : mimeType success : nil failure : ^ ( NSError * error ) {
// Nothing to do . The file is marked as unsent in the room history by the datasource
NSLog ( @ "[MXKRoomViewController] sendFile failed." ) ;
} ] ;
}
else
{
NSLog ( @ "[MXKRoomViewController] File upload using MIME type %@ is not supported." , mimeType ) ;
[ [ AppDelegate theDelegate ] showAlertWithTitle : NSLocalizedStringFromTable ( @ "file_upload_error_title" , @ "Vector" , nil )
message : NSLocalizedStringFromTable ( @ "file_upload_error_unsupported_file_type_message" , @ "Vector" , nil ) ] ;
}
}
2019-07-25 14:46:35 +00:00
# pragma mark - EmojiPickerCoordinatorBridgePresenterDelegate
- ( void ) emojiPickerCoordinatorBridgePresenter : ( EmojiPickerCoordinatorBridgePresenter * ) coordinatorBridgePresenter didAddEmoji : ( NSString * ) emoji forEventId : ( NSString * ) eventId
{
MXWeakify ( self ) ;
[ coordinatorBridgePresenter dismissWithAnimated : YES completion : ^ {
[ self . roomDataSource addReaction : emoji forEventId : eventId success : ^ {
} failure : ^ ( NSError * error ) {
MXStrongifyAndReturnIfNil ( self ) ;
[ self . errorPresenter presentErrorFromViewController : self forError : error animated : YES handler : nil ] ;
} ] ;
} ] ;
self . emojiPickerCoordinatorBridgePresenter = nil ;
}
- ( void ) emojiPickerCoordinatorBridgePresenter : ( EmojiPickerCoordinatorBridgePresenter * ) coordinatorBridgePresenter didRemoveEmoji : ( NSString * ) emoji forEventId : ( NSString * ) eventId
{
MXWeakify ( self ) ;
[ coordinatorBridgePresenter dismissWithAnimated : YES completion : ^ {
[ self . roomDataSource removeReaction : emoji forEventId : eventId success : ^ {
} failure : ^ ( NSError * error ) {
MXStrongifyAndReturnIfNil ( self ) ;
[ self . errorPresenter presentErrorFromViewController : self forError : error animated : YES handler : nil ] ;
} ] ;
} ] ;
self . emojiPickerCoordinatorBridgePresenter = nil ;
}
- ( void ) emojiPickerCoordinatorBridgePresenterDidCancel : ( EmojiPickerCoordinatorBridgePresenter * ) coordinatorBridgePresenter
{
[ coordinatorBridgePresenter dismissWithAnimated : YES completion : nil ] ;
self . emojiPickerCoordinatorBridgePresenter = nil ;
}
2019-07-30 15:18:39 +00:00
# pragma mark - ReactionHistoryCoordinatorBridgePresenterDelegate
- ( void ) reactionHistoryCoordinatorBridgePresenterDelegateDidClose : ( ReactionHistoryCoordinatorBridgePresenter * ) coordinatorBridgePresenter
{
[ coordinatorBridgePresenter dismissWithAnimated : YES completion : ^ {
self . reactionHistoryCoordinatorBridgePresenter = nil ;
} ] ;
}
2019-08-02 15:19:29 +00:00
# pragma mark - CameraPresenterDelegate
- ( void ) cameraPresenterDidCancel : ( CameraPresenter * ) cameraPresenter
{
[ cameraPresenter dismissWithAnimated : YES completion : nil ] ;
self . cameraPresenter = nil ;
}
- ( void ) cameraPresenter : ( CameraPresenter * ) cameraPresenter didSelectImageData : ( NSData * ) imageData withUTI : ( MXKUTI * ) uti
{
[ cameraPresenter dismissWithAnimated : YES completion : nil ] ;
self . cameraPresenter = nil ;
RoomInputToolbarView * roomInputToolbarView = [ self inputToolbarViewAsRoomInputToolbarView ] ;
if ( roomInputToolbarView )
{
2020-10-26 09:24:40 +00:00
[ roomInputToolbarView sendSelectedImage : imageData withMimeType : uti . mimeType andCompressionMode : BuildSettings . roomInputToolbarCompressionMode isPhotoLibraryAsset : NO ] ;
2019-08-02 15:19:29 +00:00
}
}
- ( void ) cameraPresenter : ( CameraPresenter * ) cameraPresenter didSelectVideoAt : ( NSURL * ) url
{
[ cameraPresenter dismissWithAnimated : YES completion : nil ] ;
self . cameraPresenter = nil ;
RoomInputToolbarView * roomInputToolbarView = [ self inputToolbarViewAsRoomInputToolbarView ] ;
if ( roomInputToolbarView )
{
[ roomInputToolbarView sendSelectedVideo : url isPhotoLibraryAsset : NO ] ;
}
}
# pragma mark - MediaPickerCoordinatorBridgePresenterDelegate
- ( void ) mediaPickerCoordinatorBridgePresenterDidCancel : ( MediaPickerCoordinatorBridgePresenter * ) coordinatorBridgePresenter
{
[ coordinatorBridgePresenter dismissWithAnimated : YES completion : nil ] ;
self . mediaPickerPresenter = nil ;
}
- ( void ) mediaPickerCoordinatorBridgePresenter : ( MediaPickerCoordinatorBridgePresenter * ) coordinatorBridgePresenter didSelectImageData : ( NSData * ) imageData withUTI : ( MXKUTI * ) uti
{
[ coordinatorBridgePresenter dismissWithAnimated : YES completion : nil ] ;
self . mediaPickerPresenter = nil ;
RoomInputToolbarView * roomInputToolbarView = [ self inputToolbarViewAsRoomInputToolbarView ] ;
if ( roomInputToolbarView )
{
2020-10-26 09:24:40 +00:00
[ roomInputToolbarView sendSelectedImage : imageData withMimeType : uti . mimeType andCompressionMode : BuildSettings . roomInputToolbarCompressionMode isPhotoLibraryAsset : YES ] ;
2019-08-02 15:19:29 +00:00
}
}
- ( void ) mediaPickerCoordinatorBridgePresenter : ( MediaPickerCoordinatorBridgePresenter * ) coordinatorBridgePresenter didSelectVideoAt : ( NSURL * ) url
{
[ coordinatorBridgePresenter dismissWithAnimated : YES completion : nil ] ;
self . mediaPickerPresenter = nil ;
RoomInputToolbarView * roomInputToolbarView = [ self inputToolbarViewAsRoomInputToolbarView ] ;
if ( roomInputToolbarView )
{
[ roomInputToolbarView sendSelectedVideo : url isPhotoLibraryAsset : YES ] ;
}
}
- ( void ) mediaPickerCoordinatorBridgePresenter : ( MediaPickerCoordinatorBridgePresenter * ) coordinatorBridgePresenter didSelectAssets : ( NSArray < PHAsset * > * ) assets
{
[ coordinatorBridgePresenter dismissWithAnimated : YES completion : nil ] ;
self . mediaPickerPresenter = nil ;
RoomInputToolbarView * roomInputToolbarView = [ self inputToolbarViewAsRoomInputToolbarView ] ;
if ( roomInputToolbarView )
{
2020-10-26 09:24:40 +00:00
[ roomInputToolbarView sendSelectedAssets : assets withCompressionMode : BuildSettings . roomInputToolbarCompressionMode ] ;
2019-08-02 15:19:29 +00:00
}
}
2020-09-14 16:44:30 +00:00
# pragma mark - RoomCreationModalCoordinatorBridgePresenter
- ( void ) roomCreationModalCoordinatorBridgePresenterDelegateDidComplete : ( RoomCreationModalCoordinatorBridgePresenter * ) coordinatorBridgePresenter
{
[ coordinatorBridgePresenter dismissWithAnimated : YES completion : nil ] ;
self . roomCreationModalCoordinatorBridgePresenter = nil ;
}
2020-09-18 15:57:40 +00:00
# pragma mark - RoomInfoCoordinatorBridgePresenterDelegate
- ( void ) roomInfoCoordinatorBridgePresenterDelegateDidComplete : ( RoomInfoCoordinatorBridgePresenter * ) coordinatorBridgePresenter
{
[ coordinatorBridgePresenter dismissWithAnimated : YES completion : nil ] ;
self . roomInfoCoordinatorBridgePresenter = nil ;
}
2014-10-02 15:02:47 +00:00
@ end