mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-30 08:12:40 +00:00
484 lines
19 KiB
Objective-C
484 lines
19 KiB
Objective-C
/*
|
|
Copyright 2014 OpenMarket Ltd
|
|
|
|
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.
|
|
*/
|
|
|
|
#import "RoomViewController.h"
|
|
#import "MemberViewController.h"
|
|
|
|
#import "MXKRoomBubbleTableViewCell.h"
|
|
|
|
#import "RoomTitleView.h"
|
|
|
|
#import "AppDelegate.h"
|
|
|
|
#import "RageShakeManager.h"
|
|
|
|
@interface RoomViewController () {
|
|
|
|
// Members list
|
|
id membersListener;
|
|
|
|
// Voip call options
|
|
UIButton *voipVoiceCallButton;
|
|
UIButton *voipVideoCallButton;
|
|
UIBarButtonItem *voipVoiceCallBarButtonItem;
|
|
UIBarButtonItem *voipVideoCallBarButtonItem;
|
|
|
|
// the user taps on a member thumbnail
|
|
MXRoomMember *selectedRoomMember;
|
|
|
|
// Keep reference on potential pushed view controller to release it correctly
|
|
id pushedViewController;
|
|
}
|
|
|
|
@property (weak, nonatomic) IBOutlet UINavigationItem *roomNavItem;
|
|
@property (strong, nonatomic) IBOutlet UIBarButtonItem *showRoomMembersButtonItem;
|
|
@property (weak, nonatomic) IBOutlet RoomTitleView *roomTitleView;
|
|
|
|
@property (strong, nonatomic) MXKAlert *actionMenu;
|
|
|
|
@end
|
|
|
|
@implementation RoomViewController
|
|
|
|
- (void)viewDidLoad {
|
|
[super viewDidLoad];
|
|
|
|
// Replace the default input toolbar view with the one based on `HPGrowingTextView`.
|
|
[self setRoomInputToolbarViewClass:MXKRoomInputToolbarViewWithHPGrowingText.class];
|
|
|
|
// Set rageShake handler
|
|
self.rageShakeManager = [RageShakeManager sharedManager];
|
|
|
|
// ensure that the titleView will be scaled when it will be required
|
|
// during a screen rotation for example.
|
|
self.roomTitleView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
|
|
}
|
|
|
|
- (void)didReceiveMemoryWarning {
|
|
[super didReceiveMemoryWarning];
|
|
// Dispose of any resources that can be recreated.
|
|
}
|
|
|
|
- (void)viewWillAppear:(BOOL)animated {
|
|
[super viewWillAppear:animated];
|
|
}
|
|
|
|
- (void)viewWillDisappear:(BOOL)animated {
|
|
[super viewWillDisappear:animated];
|
|
|
|
// hide action
|
|
if (self.actionMenu) {
|
|
[self.actionMenu dismiss:NO];
|
|
self.actionMenu = nil;
|
|
}
|
|
|
|
if (self.roomDataSource) {
|
|
// Store the potential message partially typed in text input
|
|
self.roomDataSource.partialTextMessage = self.inputToolbarView.textMessage;
|
|
|
|
if (membersListener) {
|
|
[self.roomDataSource.room removeListener:membersListener];
|
|
membersListener = nil;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)viewDidAppear:(BOOL)animated {
|
|
[super viewDidAppear:animated];
|
|
|
|
if (self.roomDataSource) {
|
|
// Set visible room id
|
|
[AppDelegate theDelegate].masterTabBarController.visibleRoomId = self.roomDataSource.roomId;
|
|
|
|
// Retrieve the potential message partially typed during last room display.
|
|
// Note: We have to wait for viewDidAppear before updating growingTextView (viewWillAppear is too early)
|
|
self.inputToolbarView.textMessage = self.roomDataSource.partialTextMessage;
|
|
}
|
|
|
|
if (pushedViewController) {
|
|
|
|
// Force the pushed view controller to dispose its resources, and release associated data source if any.
|
|
MXKDataSource *dataSource;
|
|
|
|
if ([pushedViewController isKindOfClass:[MXKRoomMemberListViewController class]]) {
|
|
dataSource = [(MXKRoomMemberListViewController*)pushedViewController dataSource];
|
|
}
|
|
|
|
if ([pushedViewController respondsToSelector:@selector(destroy)]) {
|
|
[pushedViewController destroy];
|
|
}
|
|
pushedViewController = nil;
|
|
|
|
if (dataSource) {
|
|
[dataSource destroy];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)viewDidDisappear:(BOOL)animated {
|
|
[super viewDidDisappear:animated];
|
|
|
|
// Reset visible room id
|
|
[AppDelegate theDelegate].masterTabBarController.visibleRoomId = nil;
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)displayRoom:(MXKRoomDataSource *)dataSource {
|
|
|
|
// Remove members listener (if any) before changing dataSource.
|
|
if (membersListener) {
|
|
[self.roomDataSource.room removeListener:membersListener];
|
|
membersListener = nil;
|
|
}
|
|
|
|
[super displayRoom:dataSource];
|
|
}
|
|
|
|
- (void)dismissKeyboard {
|
|
|
|
[_roomTitleView dismissKeyboard];
|
|
[super dismissKeyboard];
|
|
}
|
|
|
|
- (void)didMatrixSessionStateChange {
|
|
|
|
[super didMatrixSessionStateChange];
|
|
|
|
// Check dataSource state
|
|
if (self.roomDataSource && self.roomDataSource.state == MXKDataSourceStatePreparing) {
|
|
// dataSource is not ready, keep running the loading wheel
|
|
[self.activityIndicator startAnimating];
|
|
}
|
|
}
|
|
|
|
- (void)updateViewControllerAppearanceOnRoomDataSourceState {
|
|
|
|
[super updateViewControllerAppearanceOnRoomDataSourceState];
|
|
|
|
// Update UI by considering dataSource state
|
|
if (self.roomDataSource && self.roomDataSource.state == MXKDataSourceStateReady) {
|
|
self.roomTitleView.mxRoom = self.roomDataSource.room;
|
|
self.roomTitleView.editable = YES;
|
|
self.roomTitleView.hidden = NO;
|
|
|
|
// Register a listener for events that concern room members
|
|
if (!membersListener) {
|
|
membersListener = [self.roomDataSource.room listenToEventsOfTypes:@[kMXEventTypeStringRoomMember] onEvent:^(MXEvent *event, MXEventDirection direction, id customObject) {
|
|
|
|
// Consider only live event
|
|
if (direction == MXEventDirectionForwards) {
|
|
|
|
// Check the room Id (if any)
|
|
if (event.roomId && [event.roomId isEqualToString:self.roomDataSource.roomId] == NO) {
|
|
// This event does not concern the current room members
|
|
return;
|
|
}
|
|
|
|
// Check whether no text field is editing before refreshing title view
|
|
if (!self.roomTitleView.isEditing) {
|
|
[self.roomTitleView refreshDisplay];
|
|
}
|
|
|
|
// Update navigation bar items
|
|
[self updateNavigationBarButtonItems];
|
|
}
|
|
}];
|
|
}
|
|
} else {
|
|
// Update the title except if the room has just been left
|
|
if (!self.leftRoomReasonLabel) {
|
|
if (self.roomDataSource && self.roomDataSource.state == MXKDataSourceStatePreparing) {
|
|
self.roomTitleView.mxRoom = self.roomDataSource.room;
|
|
self.roomTitleView.hidden = (!self.roomTitleView.mxRoom);
|
|
}
|
|
else {
|
|
self.roomTitleView.mxRoom = nil;
|
|
self.roomTitleView.hidden = NO;
|
|
}
|
|
}
|
|
self.roomTitleView.editable = NO;
|
|
|
|
// Remove members listener if any.
|
|
if (membersListener) {
|
|
[self.roomDataSource.room removeListener:membersListener];
|
|
membersListener = nil;
|
|
}
|
|
}
|
|
|
|
// Finalize room title refresh
|
|
[self.roomTitleView refreshDisplay];
|
|
|
|
// Update navigation bar items
|
|
[self updateNavigationBarButtonItems];
|
|
}
|
|
|
|
- (void)destroy {
|
|
if (membersListener) {
|
|
[self.roomDataSource.room removeListener:membersListener];
|
|
membersListener = nil;
|
|
}
|
|
|
|
if (self.actionMenu) {
|
|
[self.actionMenu dismiss:NO];
|
|
self.actionMenu = nil;
|
|
}
|
|
|
|
[super destroy];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)updateNavigationBarButtonItems {
|
|
|
|
// Update navigation bar buttons according to room members count
|
|
if (self.roomDataSource && self.roomDataSource.state == MXKDataSourceStateReady) {
|
|
|
|
if (self.roomDataSource.room.state.members.count == 2) {
|
|
if (!voipVoiceCallBarButtonItem || !voipVideoCallBarButtonItem) {
|
|
voipVoiceCallButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
|
voipVoiceCallButton.frame = CGRectMake(0, 0, 36, 36);
|
|
UIImage *voiceImage = [UIImage imageNamed:@"voice"];
|
|
[voipVoiceCallButton setImage:voiceImage forState:UIControlStateNormal];
|
|
[voipVoiceCallButton setImage:voiceImage forState:UIControlStateHighlighted];
|
|
[voipVoiceCallButton addTarget:self action:@selector(onButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
|
|
voipVoiceCallBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:voipVoiceCallButton];
|
|
|
|
voipVideoCallButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
|
voipVideoCallButton.frame = CGRectMake(0, 0, 36, 36);
|
|
UIImage *videoImage = [UIImage imageNamed:@"video"];
|
|
[voipVideoCallButton setImage:videoImage forState:UIControlStateNormal];
|
|
[voipVideoCallButton setImage:videoImage forState:UIControlStateHighlighted];
|
|
[voipVideoCallButton addTarget:self action:@selector(onButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
|
|
voipVideoCallBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:voipVideoCallButton];
|
|
}
|
|
|
|
_showRoomMembersButtonItem.enabled = YES;
|
|
|
|
self.navigationItem.rightBarButtonItems = @[_showRoomMembersButtonItem, voipVideoCallBarButtonItem, voipVoiceCallBarButtonItem];
|
|
} else {
|
|
_showRoomMembersButtonItem.enabled = ([self.roomDataSource.room.state members].count != 0);
|
|
self.navigationItem.rightBarButtonItems = @[_showRoomMembersButtonItem];
|
|
}
|
|
} else {
|
|
_showRoomMembersButtonItem.enabled = NO;
|
|
self.navigationItem.rightBarButtonItems = @[_showRoomMembersButtonItem];
|
|
}
|
|
}
|
|
|
|
#pragma mark - MXKDataSource delegate
|
|
|
|
- (void)dataSource:(MXKDataSource *)dataSource didRecognizeAction:(NSString *)actionIdentifier inCell:(id<MXKCellRendering>)cell userInfo:(NSDictionary *)userInfo {
|
|
|
|
// Override default implementation in case of tap on avatar
|
|
if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellTapOnAvatarView]) {
|
|
selectedRoomMember = [self.roomDataSource.room.state memberWithUserId:userInfo[kMXKRoomBubbleCellUserIdKey]];
|
|
if (selectedRoomMember) {
|
|
[self performSegueWithIdentifier:@"showMemberDetails" sender:self];
|
|
}
|
|
} else {
|
|
// Keep default implementation for other actions
|
|
[super dataSource:dataSource didRecognizeAction:actionIdentifier inCell:cell userInfo:userInfo];
|
|
}
|
|
}
|
|
|
|
#pragma mark - UITextField delegate
|
|
|
|
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
|
|
NSString *alertMsg = nil;
|
|
|
|
if (textField == _roomTitleView.displayNameTextField) {
|
|
// Check whether the user has enough power to rename the room
|
|
MXRoomPowerLevels *powerLevels = [self.roomDataSource.room.state powerLevels];
|
|
NSUInteger userPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mxSession.myUser.userId];
|
|
if (userPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomName]) {
|
|
// Only the room name is edited here, update the text field with the room name
|
|
textField.text = self.roomDataSource.room.state.name;
|
|
textField.backgroundColor = [UIColor whiteColor];
|
|
} else {
|
|
alertMsg = @"You are not authorized to edit this room name";
|
|
}
|
|
|
|
// Check whether the user is allowed to change room topic
|
|
if (userPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomTopic]) {
|
|
// Show topic text field even if the current value is nil
|
|
_roomTitleView.hiddenTopic = NO;
|
|
if (alertMsg) {
|
|
// Here the user can only update the room topic, switch on room topic field (without displaying alert)
|
|
alertMsg = nil;
|
|
[_roomTitleView.topicTextField becomeFirstResponder];
|
|
return NO;
|
|
}
|
|
}
|
|
} else if (textField == _roomTitleView.topicTextField) {
|
|
// Check whether the user has enough power to edit room topic
|
|
MXRoomPowerLevels *powerLevels = [self.roomDataSource.room.state powerLevels];
|
|
NSUInteger userPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mxSession.myUser.userId];
|
|
if (userPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomTopic]) {
|
|
textField.backgroundColor = [UIColor whiteColor];
|
|
[self.roomTitleView stopTopicAnimation];
|
|
} else {
|
|
alertMsg = @"You are not authorized to edit this room topic";
|
|
}
|
|
}
|
|
|
|
if (alertMsg) {
|
|
// Alert user
|
|
__weak typeof(self) weakSelf = self;
|
|
if (self.actionMenu) {
|
|
[self.actionMenu dismiss:NO];
|
|
}
|
|
self.actionMenu = [[MXKAlert alloc] initWithTitle:nil message:alertMsg style:MXKAlertStyleAlert];
|
|
self.actionMenu.cancelButtonIndex = [self.actionMenu addActionWithTitle:@"Cancel" style:MXKAlertActionStyleDefault handler:^(MXKAlert *alert) {
|
|
weakSelf.actionMenu = nil;
|
|
}];
|
|
[self.actionMenu showInViewController:self];
|
|
return NO;
|
|
}
|
|
return YES;
|
|
}
|
|
|
|
- (void)textFieldDidEndEditing:(UITextField *)textField {
|
|
if (textField == _roomTitleView.displayNameTextField) {
|
|
textField.backgroundColor = [UIColor clearColor];
|
|
|
|
NSString *roomName = textField.text;
|
|
if ((roomName.length || self.roomDataSource.room.state.name.length) && [roomName isEqualToString:self.roomDataSource.room.state.name] == NO) {
|
|
[self.activityIndicator startAnimating];
|
|
__weak typeof(self) weakSelf = self;
|
|
[self.roomDataSource.room setName:roomName success:^{
|
|
[self stopActivityIndicator];
|
|
// Refresh title display
|
|
textField.text = weakSelf.roomDataSource.room.state.displayname;
|
|
} failure:^(NSError *error) {
|
|
[self stopActivityIndicator];
|
|
// Revert change
|
|
textField.text = weakSelf.roomDataSource.room.state.displayname;
|
|
NSLog(@"[Console RoomVC] Rename room failed: %@", error);
|
|
// Alert user
|
|
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
|
}];
|
|
} else {
|
|
// No change on room name, restore title with room displayName
|
|
textField.text = self.roomDataSource.room.state.displayname;
|
|
}
|
|
} else if (textField == _roomTitleView.topicTextField) {
|
|
textField.backgroundColor = [UIColor clearColor];
|
|
|
|
NSString *topic = textField.text;
|
|
if ((topic.length || self.roomDataSource.room.state.topic.length) && [topic isEqualToString:self.roomDataSource.room.state.topic] == NO) {
|
|
[self.activityIndicator startAnimating];
|
|
__weak typeof(self) weakSelf = self;
|
|
[self.roomDataSource.room setTopic:topic success:^{
|
|
[self stopActivityIndicator];
|
|
// Hide topic field if empty
|
|
weakSelf.roomTitleView.hiddenTopic = !textField.text.length;
|
|
} failure:^(NSError *error) {
|
|
[self stopActivityIndicator];
|
|
// Revert change
|
|
textField.text = weakSelf.roomDataSource.room.state.topic;
|
|
// Hide topic field if empty
|
|
weakSelf.roomTitleView.hiddenTopic = !textField.text.length;
|
|
NSLog(@"[RoomVC] Topic room change failed: %@", error);
|
|
//Alert user
|
|
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
|
}];
|
|
} else {
|
|
// Hide topic field if empty
|
|
_roomTitleView.hiddenTopic = !topic.length;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (BOOL)textFieldShouldReturn:(UITextField*) textField {
|
|
if (textField == _roomTitleView.displayNameTextField) {
|
|
// "Next" key has been pressed
|
|
[_roomTitleView.topicTextField becomeFirstResponder];
|
|
} else {
|
|
// "Done" key has been pressed
|
|
[textField resignFirstResponder];
|
|
}
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)isIRCStyleCommand:(NSString*)string {
|
|
// Override the default behavior for `/join` command in order to open automatically the joined room
|
|
|
|
if ([string hasPrefix:kCmdJoinRoom]) {
|
|
// Join a room
|
|
NSString *roomAlias = [string substringFromIndex:kCmdJoinRoom.length + 1];
|
|
// Remove white space from both ends
|
|
roomAlias = [roomAlias stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
|
|
|
// Check
|
|
if (roomAlias.length) {
|
|
[self.mxSession joinRoom:roomAlias success:^(MXRoom *room) {
|
|
// Show the room
|
|
[[AppDelegate theDelegate].masterTabBarController showRoom:room.state.roomId];
|
|
} failure:^(NSError *error) {
|
|
NSLog(@"[Console RoomVC] Join roomAlias (%@) failed: %@", roomAlias, error);
|
|
//Alert user
|
|
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
|
}];
|
|
} else {
|
|
// Display cmd usage in text input as placeholder
|
|
self.inputToolbarView.placeholder = @"Usage: /join <room_alias>";
|
|
}
|
|
return YES;
|
|
}
|
|
return [super isIRCStyleCommand:string];
|
|
}
|
|
|
|
#pragma mark - Segues
|
|
|
|
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
|
|
|
pushedViewController = [segue destinationViewController];
|
|
|
|
if ([[segue identifier] isEqualToString:@"showMemberList"]) {
|
|
|
|
if ([pushedViewController isKindOfClass:[MXKRoomMemberListViewController class]]) {
|
|
MXKRoomMemberListViewController* membersController = (MXKRoomMemberListViewController*)pushedViewController;
|
|
|
|
// Dismiss keyboard
|
|
[self dismissKeyboard];
|
|
|
|
MXKRoomMemberListDataSource *membersDataSource = [[MXKRoomMemberListDataSource alloc] initWithRoomId:self.roomDataSource.roomId andMatrixSession:self.mxSession];
|
|
[membersController displayList:membersDataSource];
|
|
}
|
|
} else if ([[segue identifier] isEqualToString:@"showMemberDetails"]) {
|
|
MemberViewController* controller = pushedViewController;
|
|
|
|
if (selectedRoomMember) {
|
|
controller.mxRoomMember = selectedRoomMember;
|
|
selectedRoomMember = nil;
|
|
}
|
|
controller.mxRoom = self.roomDataSource.room;
|
|
}
|
|
}
|
|
|
|
#pragma mark - Action
|
|
|
|
- (IBAction)onButtonPressed:(id)sender {
|
|
|
|
if (sender == voipVoiceCallButton || sender == voipVideoCallButton) {
|
|
[self.mxSession.callManager placeCallInRoom:self.roomDataSource.roomId withVideo:(sender == voipVideoCallButton)];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
|