mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-05-05 23:47:44 +02:00
Moved code under a subdirectory: Console
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
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 "MXCViewController.h"
|
||||
|
||||
#import "AuthInputsView.h"
|
||||
|
||||
@interface AuthenticationViewController : MXCViewController <UITextFieldDelegate, AuthInputsViewDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,574 @@
|
||||
/*
|
||||
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 "AuthenticationViewController.h"
|
||||
|
||||
#import "MatrixSDKHandler.h"
|
||||
#import "AppDelegate.h"
|
||||
#import "MXCAlert.h"
|
||||
#import "MXCRegistrationWebView.h"
|
||||
|
||||
@interface AuthenticationViewController () {
|
||||
// Current request in progress
|
||||
MXHTTPOperation *mxCurrentOperation;
|
||||
|
||||
// Array of flows supported by the home server and implemented by the app (for the current auth type)
|
||||
NSMutableArray *supportedFlows;
|
||||
|
||||
// The current view in which auth inputs are displayed
|
||||
AuthInputsView *currentAuthInputsView;
|
||||
|
||||
// reference to any opened alert view
|
||||
MXCAlert *alert;
|
||||
}
|
||||
|
||||
// Return true if the provided flow (kMXLoginFlowType) is supported by the application
|
||||
+ (BOOL)isImplementedFlowType:(NSString*)flowType forAuthType:(AuthenticationType)authType;
|
||||
|
||||
// The current authentication type
|
||||
@property (nonatomic) AuthenticationType authType;
|
||||
@property (nonatomic) MXLoginFlow *selectedFlow;
|
||||
|
||||
@property (strong, nonatomic) IBOutlet UIScrollView *authenticationScrollView;
|
||||
@property (weak, nonatomic) IBOutlet UIView *contentView;
|
||||
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *contentViewHeightConstraint;
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UILabel *createAccountLabel;
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UIView *authInputsContainerView;
|
||||
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *authInputContainerViewHeightConstraint;
|
||||
@property (weak, nonatomic) IBOutlet AuthInputsPasswordBasedView *authInputsPasswordBasedView;
|
||||
@property (weak, nonatomic) IBOutlet AuthInputsEmailCodeBasedView *authInputsEmailCodeBasedView;
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UITextField *homeServerTextField;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *homeServerInfoLabel;
|
||||
@property (weak, nonatomic) IBOutlet UITextField *identityServerTextField;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *identityServerInfoLabel;
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UIButton *submitButton;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *authSwitchButton;
|
||||
|
||||
@property (strong, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *noFlowLabel;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *retryButton;
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UIView *registrationFallbackContentView;
|
||||
@property (weak, nonatomic) IBOutlet MXCRegistrationWebView *registrationFallbackWebView;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *cancelRegistrationFallbackButton;
|
||||
|
||||
@end
|
||||
|
||||
@implementation AuthenticationViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view, typically from a nib.
|
||||
|
||||
// Force contentView in full width
|
||||
NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:self.contentView
|
||||
attribute:NSLayoutAttributeLeading
|
||||
relatedBy:0
|
||||
toItem:self.view
|
||||
attribute:NSLayoutAttributeLeft
|
||||
multiplier:1.0
|
||||
constant:0];
|
||||
[self.view addConstraint:leftConstraint];
|
||||
|
||||
NSLayoutConstraint *rightConstraint = [NSLayoutConstraint constraintWithItem:self.contentView
|
||||
attribute:NSLayoutAttributeTrailing
|
||||
relatedBy:0
|
||||
toItem:self.view
|
||||
attribute:NSLayoutAttributeRight
|
||||
multiplier:1.0
|
||||
constant:0];
|
||||
[self.view addConstraint:rightConstraint];
|
||||
|
||||
_authenticationScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
|
||||
|
||||
_submitButton.enabled = NO;
|
||||
_authSwitchButton.enabled = YES;
|
||||
_authInputsPasswordBasedView.delegate = self;
|
||||
_authInputsEmailCodeBasedView.delegate = self;
|
||||
|
||||
supportedFlows = [NSMutableArray array];
|
||||
|
||||
_homeServerTextField.text = [[MatrixSDKHandler sharedHandler] homeServerURL];
|
||||
_identityServerTextField.text = [[MatrixSDKHandler sharedHandler] identityServerURL];
|
||||
|
||||
// Set initial auth type
|
||||
_authType = AuthenticationTypeLogin;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
supportedFlows = nil;
|
||||
if (mxCurrentOperation){
|
||||
[mxCurrentOperation cancel];
|
||||
mxCurrentOperation = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning {
|
||||
[super didReceiveMemoryWarning];
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
// Update supported authentication flow
|
||||
self.authType = _authType;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onKeyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onKeyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
|
||||
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onTextFieldChange:) name:UITextFieldTextDidChangeNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
[self dismissKeyboard];
|
||||
|
||||
// close any opened alert
|
||||
if (alert) {
|
||||
[alert dismiss:NO];
|
||||
alert = nil;
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:AFNetworkingReachabilityDidChangeNotification object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidChangeNotification object:nil];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
+ (BOOL)isImplementedFlowType:(NSString*)flowType forAuthType:(AuthenticationType)authType {
|
||||
if (authType == AuthenticationTypeLogin) {
|
||||
if ([flowType isEqualToString:kMXLoginFlowTypePassword]
|
||||
/*|| [flowType isEqualToString:kMXLoginFlowTypeEmailCode]*/) {
|
||||
return YES;
|
||||
}
|
||||
} else { // AuthenticationTypeRegister
|
||||
// No registration flow is supported yet
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)setAuthType:(AuthenticationType)authType {
|
||||
if (authType == AuthenticationTypeLogin) {
|
||||
_createAccountLabel.hidden = YES;
|
||||
[_submitButton setTitle:@"Login" forState:UIControlStateNormal];
|
||||
[_submitButton setTitle:@"Login" forState:UIControlStateHighlighted];
|
||||
[_authSwitchButton setTitle:@"Create account" forState:UIControlStateNormal];
|
||||
[_authSwitchButton setTitle:@"Create account" forState:UIControlStateHighlighted];
|
||||
} else {
|
||||
_createAccountLabel.hidden = NO;
|
||||
[_submitButton setTitle:@"Sign up" forState:UIControlStateNormal];
|
||||
[_submitButton setTitle:@"Sign up" forState:UIControlStateHighlighted];
|
||||
[_authSwitchButton setTitle:@"Back" forState:UIControlStateNormal];
|
||||
[_authSwitchButton setTitle:@"Back" forState:UIControlStateHighlighted];
|
||||
}
|
||||
|
||||
_authType = authType;
|
||||
|
||||
// Update supported authentication flow
|
||||
[self refreshSupportedAuthFlow];
|
||||
}
|
||||
|
||||
- (void)setSelectedFlow:(MXLoginFlow *)selectedFlow {
|
||||
// Hide views which depend on auth flow
|
||||
_submitButton.hidden = YES;
|
||||
_authInputsPasswordBasedView.hidden = YES;
|
||||
_authInputsEmailCodeBasedView.hidden = YES;
|
||||
_noFlowLabel.hidden = YES;
|
||||
_retryButton.hidden = YES;
|
||||
currentAuthInputsView = nil;
|
||||
|
||||
// Select the right auth inputs view
|
||||
if ([selectedFlow.type isEqualToString:kMXLoginFlowTypePassword]) {
|
||||
currentAuthInputsView = _authInputsPasswordBasedView;
|
||||
} else if ([selectedFlow.type isEqualToString:kMXLoginFlowTypeEmailCode]) {
|
||||
currentAuthInputsView = _authInputsEmailCodeBasedView;
|
||||
}
|
||||
|
||||
if (currentAuthInputsView) {
|
||||
_submitButton.hidden = NO;
|
||||
currentAuthInputsView.hidden = NO;
|
||||
currentAuthInputsView.authType = _authType;
|
||||
_authInputContainerViewHeightConstraint.constant = currentAuthInputsView.actualHeight;
|
||||
} else {
|
||||
// No input fields are displayed
|
||||
_authInputContainerViewHeightConstraint.constant = 80;
|
||||
}
|
||||
|
||||
[self.view layoutIfNeeded];
|
||||
|
||||
// Refresh content view height
|
||||
_contentViewHeightConstraint.constant = _authSwitchButton.frame.origin.y + _authSwitchButton.frame.size.height + 15;
|
||||
|
||||
_selectedFlow = selectedFlow;
|
||||
}
|
||||
|
||||
- (void)setUserInteractionEnabled:(BOOL)isEnabled {
|
||||
_submitButton.enabled = (isEnabled && currentAuthInputsView.areAllRequiredFieldsFilled && _homeServerTextField.text.length);
|
||||
_authSwitchButton.enabled = isEnabled;
|
||||
|
||||
_homeServerTextField.enabled = isEnabled;
|
||||
_identityServerTextField.enabled = isEnabled;
|
||||
}
|
||||
|
||||
- (void)refreshSupportedAuthFlow {
|
||||
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
|
||||
|
||||
// Remove reachability observer
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:AFNetworkingReachabilityDidChangeNotification object:nil];
|
||||
|
||||
// Cancel potential request in progress
|
||||
[mxCurrentOperation cancel];
|
||||
mxCurrentOperation = nil;
|
||||
|
||||
[_activityIndicator startAnimating];
|
||||
self.selectedFlow = nil;
|
||||
if (_authType == AuthenticationTypeLogin) {
|
||||
mxCurrentOperation = [mxHandler.mxRestClient getLoginFlow:^(NSArray *flows) {
|
||||
[self handleHomeServerFlows:flows];
|
||||
} failure:^(NSError *error) {
|
||||
NSLog(@"[AuthenticationVC] Failed to get Login flows: %@", error);
|
||||
[self onFailureDuringMXOperation:error];
|
||||
}];
|
||||
} else {
|
||||
// mxCurrentOperation = [mxHandler.mxRestClient getRegisterFlow:^(NSArray *flows) {
|
||||
// [self handleHomeServerFlows:flows];
|
||||
// } failure:^(NSError *error) {
|
||||
// NSLog(@"[AuthenticationVC] Failed to get Register flows: %@", error);
|
||||
// [self onFailureDuringMXOperation:error];
|
||||
// }];
|
||||
|
||||
// Currently no registration flow are supported, we switch directly to the fallback page
|
||||
[self showRegistrationFallBackView:[mxHandler.mxRestClient registerFallback]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleHomeServerFlows:(NSArray *)flows {
|
||||
[_activityIndicator stopAnimating];
|
||||
|
||||
[supportedFlows removeAllObjects];
|
||||
for (MXLoginFlow* flow in flows) {
|
||||
if ([AuthenticationViewController isImplementedFlowType:flow.type forAuthType:_authType]) {
|
||||
// Check here all stages
|
||||
BOOL isSupported = YES;
|
||||
if (flow.stages.count) {
|
||||
for (NSString *stage in flow.stages) {
|
||||
if ([AuthenticationViewController isImplementedFlowType:stage forAuthType:_authType] == NO) {
|
||||
isSupported = NO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isSupported) {
|
||||
[supportedFlows addObject:flow];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (supportedFlows.count) {
|
||||
// FIXME display supported flows
|
||||
// Currently we select the first one
|
||||
self.selectedFlow = [supportedFlows firstObject];
|
||||
}
|
||||
|
||||
if (!_selectedFlow) {
|
||||
// Notify user that no flow is supported
|
||||
if (_authType == AuthenticationTypeLogin) {
|
||||
_noFlowLabel.text = @"Currently we do not support Login flows defined by this Home Server.";
|
||||
} else {
|
||||
_noFlowLabel.text = @"Registration is not currently supported.";
|
||||
}
|
||||
NSLog(@"[AuthenticationVC] Warning: %@", _noFlowLabel.text);
|
||||
|
||||
_noFlowLabel.hidden = NO;
|
||||
_retryButton.hidden = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onFailureDuringMXOperation:(NSError*)error {
|
||||
mxCurrentOperation = nil;
|
||||
|
||||
if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == kCFURLErrorCancelled) {
|
||||
// Ignore this error
|
||||
return;
|
||||
}
|
||||
|
||||
[_activityIndicator stopAnimating];
|
||||
|
||||
// Alert user
|
||||
NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey];
|
||||
if (!title)
|
||||
{
|
||||
title = @"Error";
|
||||
}
|
||||
NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey];
|
||||
|
||||
alert = [[MXCAlert alloc] initWithTitle:title message:msg style:MXCAlertStyleAlert];
|
||||
alert.cancelButtonIndex = [alert addActionWithTitle:@"Dismiss" style:MXCAlertActionStyleDefault handler:^(MXCAlert *alert) {}];
|
||||
[alert showInViewController:self];
|
||||
|
||||
// Display failure reason
|
||||
_noFlowLabel.hidden = NO;
|
||||
_noFlowLabel.text = [error.userInfo valueForKey:NSLocalizedDescriptionKey];
|
||||
if (!_noFlowLabel.text.length) {
|
||||
_noFlowLabel.text = @"We failed to retrieve authentication information from this Home Server";
|
||||
}
|
||||
_retryButton.hidden = NO;
|
||||
|
||||
// Handle specific error code here
|
||||
if ([error.domain isEqualToString:NSURLErrorDomain]) {
|
||||
// Check network reachability
|
||||
if (error.code == NSURLErrorNotConnectedToInternet) {
|
||||
// Add reachability observer in order to launch a new request when network will be available
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onReachabilityStatusChange:) name:AFNetworkingReachabilityDidChangeNotification object:nil];
|
||||
} else if (error.code == kCFURLErrorTimedOut) {
|
||||
// Send a new request in 2 sec
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self refreshSupportedAuthFlow];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onReachabilityStatusChange:(NSNotification *)notif {
|
||||
AFNetworkReachabilityManager *reachabilityManager = [AFNetworkReachabilityManager sharedManager];
|
||||
AFNetworkReachabilityStatus status = reachabilityManager.networkReachabilityStatus;
|
||||
|
||||
if (status == AFNetworkReachabilityStatusReachableViaWiFi || status == AFNetworkReachabilityStatusReachableViaWWAN) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self refreshSupportedAuthFlow];
|
||||
});
|
||||
} else if (status == AFNetworkReachabilityStatusNotReachable) {
|
||||
_noFlowLabel.text = @"Please check your network connectivity";
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)onButtonPressed:(id)sender {
|
||||
[self dismissKeyboard];
|
||||
|
||||
if (sender == _submitButton) {
|
||||
MatrixSDKHandler *matrix = [MatrixSDKHandler sharedHandler];
|
||||
if (matrix.mxRestClient) {
|
||||
// Disable user interaction to prevent multiple requests
|
||||
[self setUserInteractionEnabled:NO];
|
||||
[_activityIndicator startAnimating];
|
||||
|
||||
if (_authType == AuthenticationTypeLogin) {
|
||||
if ([_selectedFlow.type isEqualToString:kMXLoginFlowTypePassword]) {
|
||||
[matrix.mxRestClient loginWithUser:matrix.userLogin andPassword:_authInputsPasswordBasedView.passWordTextField.text
|
||||
success:^(MXCredentials *credentials){
|
||||
[_activityIndicator stopAnimating];
|
||||
|
||||
// Report credentials
|
||||
[matrix setUserId:credentials.userId];
|
||||
[matrix setAccessToken:credentials.accessToken];
|
||||
// Extract homeServer name from userId
|
||||
NSArray *components = [credentials.userId componentsSeparatedByString:@":"];
|
||||
if (components.count == 2) {
|
||||
[matrix setHomeServer:[components lastObject]];
|
||||
} else {
|
||||
NSLog(@"[AuthenticationVC] Warning: the userId is not correctly formatted: %@", credentials.userId);
|
||||
}
|
||||
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
failure:^(NSError *error){
|
||||
[self onFailureDuringAuthRequest:error];
|
||||
}];
|
||||
} else {
|
||||
// FIXME
|
||||
[self onFailureDuringAuthRequest:[NSError errorWithDomain:nil code:0 userInfo:@{@"error": @"Not supported yet"}]];
|
||||
}
|
||||
} else {
|
||||
// FIXME
|
||||
[self onFailureDuringAuthRequest:[NSError errorWithDomain:nil code:0 userInfo:@{@"error": @"Not supported yet"}]];
|
||||
}
|
||||
}
|
||||
} else if (sender == _authSwitchButton){
|
||||
if (_authType == AuthenticationTypeLogin) {
|
||||
self.authType = AuthenticationTypeRegister;
|
||||
} else {
|
||||
self.authType = AuthenticationTypeLogin;
|
||||
}
|
||||
} else if (sender == _retryButton) {
|
||||
[self refreshSupportedAuthFlow];
|
||||
} else if (sender == _cancelRegistrationFallbackButton) {
|
||||
// Hide fallback webview
|
||||
[self hideRegistrationFallbackView];
|
||||
self.authType = AuthenticationTypeLogin;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onFailureDuringAuthRequest:(NSError *)error {
|
||||
[_activityIndicator stopAnimating];
|
||||
[self setUserInteractionEnabled:YES];
|
||||
|
||||
NSLog(@"[AuthenticationVC] Auth request failed: %@", error);
|
||||
|
||||
// translate the error code to a human message
|
||||
NSString* message = error.localizedDescription;
|
||||
NSDictionary* dict = error.userInfo;
|
||||
|
||||
// detect if it is a Matrix SDK issue
|
||||
if (dict) {
|
||||
NSString* localizedError = [dict valueForKey:@"error"];
|
||||
NSString* errCode = [dict valueForKey:@"errcode"];
|
||||
|
||||
if (errCode) {
|
||||
if ([errCode isEqualToString:@"M_FORBIDDEN"]) {
|
||||
message = @"Invalid username/password";
|
||||
} else if (localizedError.length > 0) {
|
||||
message = localizedError;
|
||||
} else if ([errCode isEqualToString:@"M_UNKNOWN_TOKEN"]) {
|
||||
message = @"The access token specified was not recognised";
|
||||
} else if ([errCode isEqualToString:@"M_BAD_JSON"]) {
|
||||
message = @"Malformed JSON";
|
||||
} else if ([errCode isEqualToString:@"M_NOT_JSON"]) {
|
||||
message = @"Did not contain valid JSON";
|
||||
} else if ([errCode isEqualToString:@"M_LIMIT_EXCEEDED"]) {
|
||||
message = @"Too many requests have been sent";
|
||||
} else if ([errCode isEqualToString:@"M_USER_IN_USE"]) {
|
||||
message = @"This user name is already used";
|
||||
} else if ([errCode isEqualToString:@"M_LOGIN_EMAIL_URL_NOT_YET"]) {
|
||||
message = @"The email link which has not been clicked yet";
|
||||
} else {
|
||||
message = errCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Alert user
|
||||
alert = [[MXCAlert alloc] initWithTitle:@"Login Failed" message:message style:MXCAlertStyleAlert];
|
||||
[alert addActionWithTitle:@"Dismiss" style:MXCAlertActionStyleCancel handler:^(MXCAlert *alert) {}];
|
||||
[alert showInViewController:self];
|
||||
}
|
||||
|
||||
#pragma mark - Keyboard handling
|
||||
|
||||
- (void)onKeyboardWillShow:(NSNotification *)notif {
|
||||
NSValue *rectVal = notif.userInfo[UIKeyboardFrameEndUserInfoKey];
|
||||
CGRect endRect = rectVal.CGRectValue;
|
||||
|
||||
UIEdgeInsets insets = self.authenticationScrollView.contentInset;
|
||||
// Handle portrait/landscape mode
|
||||
insets.bottom = (endRect.origin.y == 0) ? endRect.size.width : endRect.size.height;
|
||||
self.authenticationScrollView.contentInset = insets;
|
||||
}
|
||||
|
||||
- (void)onKeyboardWillHide:(NSNotification *)notif {
|
||||
UIEdgeInsets insets = self.authenticationScrollView.contentInset;
|
||||
insets.bottom = 0;
|
||||
self.authenticationScrollView.contentInset = insets;
|
||||
}
|
||||
|
||||
- (void)dismissKeyboard {
|
||||
// Hide the keyboard
|
||||
[currentAuthInputsView dismissKeyboard];
|
||||
[_homeServerTextField resignFirstResponder];
|
||||
[_identityServerTextField resignFirstResponder];
|
||||
}
|
||||
|
||||
#pragma mark - UITextField delegate
|
||||
|
||||
- (void)onTextFieldChange:(NSNotification *)notif {
|
||||
NSString *homeServerURL = _homeServerTextField.text;
|
||||
|
||||
if (currentAuthInputsView.areAllRequiredFieldsFilled && homeServerURL.length) {
|
||||
_submitButton.enabled = YES;
|
||||
} else {
|
||||
_submitButton.enabled = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)textFieldDidEndEditing:(UITextField *)textField {
|
||||
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
|
||||
if (textField == _homeServerTextField) {
|
||||
if (![[mxHandler homeServerURL] isEqualToString:textField.text]) {
|
||||
[mxHandler setHomeServerURL:textField.text];
|
||||
if (!textField.text.length) {
|
||||
// Force refresh with default value
|
||||
textField.text = [mxHandler homeServerURL];
|
||||
}
|
||||
// Refresh UI
|
||||
[self refreshSupportedAuthFlow];
|
||||
}
|
||||
}
|
||||
else if (textField == _identityServerTextField) {
|
||||
[mxHandler setIdentityServerURL:textField.text];
|
||||
if (!textField.text.length) {
|
||||
// Force refresh with default value
|
||||
textField.text = [mxHandler identityServerURL];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField*)textField {
|
||||
if (textField.returnKeyType == UIReturnKeyDone) {
|
||||
// "Done" key has been pressed
|
||||
[textField resignFirstResponder];
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - AuthInputsViewDelegate delegate
|
||||
|
||||
- (void)authInputsDoneKeyHasBeenPressed:(AuthInputsView *)authInputsView {
|
||||
if (_submitButton.isEnabled) {
|
||||
// Launch authentication now
|
||||
[self onButtonPressed:_submitButton];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Registration Fallback
|
||||
|
||||
- (void)showRegistrationFallBackView:(NSString*)fallbackPage {
|
||||
_authenticationScrollView.hidden = YES;
|
||||
_registrationFallbackContentView.hidden = NO;
|
||||
|
||||
[_registrationFallbackWebView openFallbackPage:fallbackPage success:^(MXCredentials *credentials) {
|
||||
// Report credentials
|
||||
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
|
||||
[mxHandler setUserId:credentials.userId];
|
||||
[mxHandler setAccessToken:credentials.accessToken];
|
||||
// Extract homeServer name from userId
|
||||
NSArray *components = [credentials.userId componentsSeparatedByString:@":"];
|
||||
if (components.count == 2) {
|
||||
[mxHandler setHomeServer:[components lastObject]];
|
||||
} else {
|
||||
NSLog(@"[AuthenticationVC] Warning: the userId is not correctly formatted: %@", credentials.userId);
|
||||
}
|
||||
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)hideRegistrationFallbackView {
|
||||
[_registrationFallbackWebView stopLoading];
|
||||
_authenticationScrollView.hidden = NO;
|
||||
_registrationFallbackContentView.hidden = YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
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 "MXCTableViewController.h"
|
||||
|
||||
#import "MXCContact.h"
|
||||
|
||||
@interface ContactDetailsViewController : MXCTableViewController
|
||||
|
||||
@property (strong, nonatomic) MXCContact* contact;
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
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 "ContactDetailsViewController.h"
|
||||
|
||||
#import "ContactDetailsTableCell.h"
|
||||
|
||||
#import "MatrixSDKHandler.h"
|
||||
|
||||
@interface ContactDetailsViewController () {
|
||||
NSArray* matrixIDs;
|
||||
}
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UIButton *memberThumbnailButton;
|
||||
@property (weak, nonatomic) IBOutlet UITextView *roomMemberMID;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ContactDetailsViewController
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
self.memberThumbnailButton = nil;
|
||||
self.roomMemberMID = nil;
|
||||
matrixIDs = nil;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
// remove the line separator color
|
||||
self.tableView.separatorColor = [UIColor clearColor];
|
||||
self.tableView.rowHeight = 44;
|
||||
self.tableView.allowsSelection = NO;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
self.roomMemberMID.text = _contact.displayName;
|
||||
|
||||
// set the thumbnail info
|
||||
[self.memberThumbnailButton.imageView setContentMode: UIViewContentModeScaleAspectFill];
|
||||
[self.memberThumbnailButton.imageView setClipsToBounds:YES];
|
||||
|
||||
if (_contact.thumbnail) {
|
||||
self.memberThumbnailButton.imageView.image = _contact.thumbnail;
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onThumbnailUpdate:) name:kMXCContactThumbnailUpdateNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
matrixIDs = _contact.matrixIdentifiers;
|
||||
return matrixIDs.count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
NSInteger row = indexPath.row;
|
||||
ContactDetailsTableCell* contactDetailsTableCell = (ContactDetailsTableCell*)[aTableView dequeueReusableCellWithIdentifier:@"ContactDetailsTableCell" forIndexPath:indexPath];
|
||||
|
||||
if (row < matrixIDs.count) {
|
||||
contactDetailsTableCell.matrixUserIDLabel.text = [matrixIDs objectAtIndex:row];
|
||||
} else {
|
||||
// should never happen
|
||||
contactDetailsTableCell.matrixUserIDLabel.text = @"";
|
||||
}
|
||||
|
||||
[contactDetailsTableCell.startChatButton addTarget:self action:@selector(startChat:) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
return contactDetailsTableCell;
|
||||
}
|
||||
|
||||
- (void)startChat:(UIButton*)sender {
|
||||
UIView* view = sender;
|
||||
|
||||
// search the parentce cell
|
||||
while (view && ![view isKindOfClass:[ContactDetailsTableCell class]]) {
|
||||
view = view.superview;
|
||||
}
|
||||
|
||||
if ([view isKindOfClass:[ContactDetailsTableCell class]]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[MatrixSDKHandler sharedHandler] startPrivateOneToOneRoomWithUserId:((ContactDetailsTableCell*)view).matrixUserIDLabel.text];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onThumbnailUpdate:(NSNotification *)notif {
|
||||
// sanity check
|
||||
if ([notif.object isKindOfClass:[NSString class]]) {
|
||||
NSString* matrixID = notif.object;
|
||||
|
||||
if ([matrixID isEqualToString:self.contact.contactID]) {
|
||||
if (_contact.thumbnail) {
|
||||
self.memberThumbnailButton.imageView.image = _contact.thumbnail;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
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 "MXCViewController.h"
|
||||
|
||||
// SMS
|
||||
#import <MessageUI/MessageUI.h>
|
||||
#import <MessageUI/MFMessageComposeViewController.h>
|
||||
|
||||
#import "SectionedContacts.h"
|
||||
|
||||
@interface ContactsViewController : MXCViewController <UITableViewDataSource, UITableViewDelegate, UINavigationControllerDelegate, MFMessageComposeViewControllerDelegate, UISearchBarDelegate, UIGestureRecognizerDelegate> {
|
||||
NSArray* collationTitles;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,765 @@
|
||||
/*
|
||||
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 "ContactsViewController.h"
|
||||
|
||||
// SDK api
|
||||
#import "MatrixSDKHandler.h"
|
||||
|
||||
// application info
|
||||
#import "AppDelegate.h"
|
||||
|
||||
// contacts management
|
||||
#import "ContactManager.h"
|
||||
#import "MXCContact.h"
|
||||
#import "MXCEmail.h"
|
||||
#import "MXCPhoneNumber.h"
|
||||
|
||||
// contact cell
|
||||
#import "ContactTableCell.h"
|
||||
|
||||
// alert
|
||||
#import "MXCAlert.h"
|
||||
|
||||
// settings
|
||||
#import "AppSettings.h"
|
||||
|
||||
//
|
||||
#import "ContactDetailsViewController.h"
|
||||
|
||||
NSString *const kInvitationMessage = @"I'd like to chat with you with matrix. Please, visit the website http://matrix.org to have more information.";
|
||||
|
||||
@interface ContactsViewController () {
|
||||
// YES -> only matrix users
|
||||
// NO -> display local contacts
|
||||
BOOL displayMatrixUsers;
|
||||
|
||||
// screenshot of the local contacts
|
||||
NSMutableArray* localContacts;
|
||||
SectionedContacts* sectionedLocalContacts;
|
||||
|
||||
// screenshot of the matrix users
|
||||
NSMutableDictionary* matrixUserByMatrixID;
|
||||
SectionedContacts* sectionedMatrixContacts;
|
||||
|
||||
// tap on thumbnail to display contact info
|
||||
MXCContact* selectedContact;
|
||||
|
||||
// Search
|
||||
UISearchBar *contactsSearchBar;
|
||||
NSMutableArray *filteredContacts;
|
||||
SectionedContacts* sectionedFilteredContacts;
|
||||
BOOL searchBarShouldEndEditing;
|
||||
NSString* latestSearchedPattern;
|
||||
}
|
||||
|
||||
@property (strong, nonatomic) MXCAlert *startChatMenu;
|
||||
@property (strong, nonatomic) MXCAlert *allowContactSyncAlert;
|
||||
@property (weak, nonatomic) IBOutlet UITableView* tableView;
|
||||
@property (weak, nonatomic) IBOutlet UISegmentedControl* contactsControls;
|
||||
@property (strong, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;
|
||||
@end
|
||||
|
||||
@implementation ContactsViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
// get the system collation titles
|
||||
collationTitles = [[UILocalizedIndexedCollation currentCollation]sectionTitles];
|
||||
|
||||
// global init
|
||||
displayMatrixUsers = (0 == self.contactsControls.selectedSegmentIndex);
|
||||
matrixUserByMatrixID = [[NSMutableDictionary alloc] init];
|
||||
|
||||
// event listener
|
||||
[[MatrixSDKHandler sharedHandler] addObserver:self forKeyPath:@"status" options:0 context:nil];
|
||||
|
||||
// add the search icon on the right
|
||||
// need to add more buttons ?
|
||||
UIBarButtonItem *searchButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@selector(search:)];
|
||||
self.navigationItem.rightBarButtonItems = @[searchButton];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onContactsRefresh:) name:kContactManagerContactsListRefreshNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
// required to reduce the tableview height while searching
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onKeyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onKeyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
// Leave potential search session
|
||||
if (contactsSearchBar) {
|
||||
[self searchBarCancelButtonClicked:contactsSearchBar];
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)startActivityIndicator {
|
||||
[_activityIndicator.layer setCornerRadius:5];
|
||||
_activityIndicator.hidden = NO;
|
||||
[_activityIndicator startAnimating];
|
||||
}
|
||||
|
||||
- (void)stopActivityIndicator {
|
||||
[_activityIndicator stopAnimating];
|
||||
_activityIndicator.hidden = YES;
|
||||
}
|
||||
|
||||
- (void)scrollToTop {
|
||||
// stop any scrolling effect
|
||||
[UIView setAnimationsEnabled:NO];
|
||||
// before scrolling to the tableview top
|
||||
self.tableView.contentOffset = CGPointMake(-self.tableView.contentInset.left, -self.tableView.contentInset.top);
|
||||
[UIView setAnimationsEnabled:YES];
|
||||
}
|
||||
|
||||
// should be called when resetting the application
|
||||
// the contact manager warn there is a contacts list update
|
||||
// but the Matrix SDK handler has no more userID -> so assume there is a reset
|
||||
- (void)reset {
|
||||
// Leave potential search session
|
||||
if (contactsSearchBar) {
|
||||
[self searchBarCancelButtonClicked:contactsSearchBar];
|
||||
}
|
||||
|
||||
localContacts = nil;
|
||||
sectionedLocalContacts = nil;
|
||||
|
||||
matrixUserByMatrixID = [[NSMutableDictionary alloc] init];;
|
||||
sectionedMatrixContacts = nil;
|
||||
|
||||
[self.contactsControls setSelectedSegmentIndex:0];
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
#pragma mark - Keyboard handling
|
||||
|
||||
- (void)onKeyboardWillShow:(NSNotification *)notif {
|
||||
// get the keyboard size
|
||||
NSValue *rectVal = notif.userInfo[UIKeyboardFrameEndUserInfoKey];
|
||||
CGRect endRect = rectVal.CGRectValue;
|
||||
|
||||
// IOS 8 triggers some unexpected keyboard events
|
||||
if ((endRect.size.height == 0) || (endRect.size.width == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGFloat keyboardHeight = (endRect.origin.y == 0) ? endRect.size.width : endRect.size.height;
|
||||
|
||||
// the tableview bottom inset must also be updated
|
||||
UIEdgeInsets insets = self.tableView.contentInset;
|
||||
insets.bottom = keyboardHeight;
|
||||
|
||||
// get the animation info
|
||||
NSNumber *curveValue = [[notif userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey];
|
||||
UIViewAnimationCurve animationCurve = curveValue.intValue;
|
||||
|
||||
// the duration is ignored but it is better to define it
|
||||
double animationDuration = [[[notif userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
|
||||
|
||||
[UIView animateWithDuration:animationDuration delay:0 options:UIViewAnimationOptionBeginFromCurrentState | (animationCurve << 16) animations:^{
|
||||
// reduce the tableview height
|
||||
self.tableView.contentInset = insets;
|
||||
[self.view layoutIfNeeded];
|
||||
|
||||
} completion:^(BOOL finished) {
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)onKeyboardWillHide:(NSNotification *)notif {
|
||||
// get the keyboard size
|
||||
NSValue *rectVal = notif.userInfo[UIKeyboardFrameEndUserInfoKey];
|
||||
CGRect endRect = rectVal.CGRectValue;
|
||||
|
||||
rectVal = notif.userInfo[UIKeyboardFrameBeginUserInfoKey];
|
||||
CGRect beginRect = rectVal.CGRectValue;
|
||||
|
||||
UIEdgeInsets insets = self.tableView.contentInset;
|
||||
insets.bottom = 0;
|
||||
|
||||
// do not animate if the both rect are the same
|
||||
// but ensure that the fields are properly resetted
|
||||
// e.g. when the user swipes to hide the keyboard
|
||||
// this method is called with invalid rects
|
||||
// animationDuration is ignored because of the animation curve
|
||||
// use it to be sure that it will be broken with any new IOS update
|
||||
if (CGRectEqualToRect(endRect, beginRect)) {
|
||||
|
||||
self.tableView.contentInset = insets;
|
||||
[self.view layoutIfNeeded];
|
||||
|
||||
} else {
|
||||
// get the animation info
|
||||
NSNumber *curveValue = [[notif userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey];
|
||||
UIViewAnimationCurve animationCurve = curveValue.intValue;
|
||||
|
||||
// the duration is ignored but it is better to define it
|
||||
double animationDuration = [[[notif userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
|
||||
|
||||
// animate the keyboard closing
|
||||
[UIView animateWithDuration:animationDuration delay:0 options:UIViewAnimationOptionBeginFromCurrentState | (animationCurve << 16) animations:^{
|
||||
self.tableView.contentInset = insets;
|
||||
[self.view layoutIfNeeded];
|
||||
|
||||
} completion:^(BOOL finished) {
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UITableView delegate
|
||||
|
||||
- (void)updateSectionedLocalContacts {
|
||||
[self stopActivityIndicator];
|
||||
|
||||
ContactManager* sharedManager = [ContactManager sharedManager];
|
||||
|
||||
if (!localContacts) {
|
||||
localContacts = sharedManager.contacts;
|
||||
}
|
||||
|
||||
if (!sectionedLocalContacts) {
|
||||
sectionedLocalContacts = [sharedManager getSectionedContacts:sharedManager.contacts];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateSectionedMatrixContacts {
|
||||
// Check whether mxSession is available in matrix handler
|
||||
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
|
||||
if (!mxHandler.mxSession) {
|
||||
[self startActivityIndicator];
|
||||
sectionedMatrixContacts = nil;
|
||||
} else {
|
||||
[self stopActivityIndicator];
|
||||
|
||||
NSArray* usersIDs = [mxHandler oneToOneRoomMemberIDs];
|
||||
// return a MatrixIDs list of 1:1 room members
|
||||
|
||||
NSMutableArray* knownUserIDs = [[matrixUserByMatrixID allKeys] mutableCopy];
|
||||
|
||||
// list the contacts IDs
|
||||
// avoid delete and create the same ones
|
||||
// it could save thumbnail downloads
|
||||
for(NSString* userID in usersIDs) {
|
||||
//
|
||||
MXUser* user = [mxHandler.mxSession userWithUserId:userID];
|
||||
|
||||
// sanity check
|
||||
if (user) {
|
||||
// managed UserID
|
||||
[knownUserIDs removeObject:userID];
|
||||
|
||||
MXCContact* contact = [matrixUserByMatrixID objectForKey:userID];
|
||||
|
||||
// already defined
|
||||
if (contact) {
|
||||
contact.displayName = (user.displayname.length > 0) ? user.displayname : user.userId;
|
||||
} else {
|
||||
contact = [[MXCContact alloc] initWithDisplayName:((user.displayname.length > 0) ? user.displayname : user.userId) matrixID:user.userId];
|
||||
[matrixUserByMatrixID setValue:contact forKey:userID];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// some userIDs don't exist anymore
|
||||
for (NSString* userID in knownUserIDs) {
|
||||
[matrixUserByMatrixID removeObjectForKey:userID];
|
||||
}
|
||||
|
||||
sectionedMatrixContacts = [[ContactManager sharedManager] getSectionedContacts:[matrixUserByMatrixID allValues]];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
// search in progress
|
||||
if (contactsSearchBar) {
|
||||
return sectionedFilteredContacts.sectionedContacts.count;
|
||||
}
|
||||
else if (displayMatrixUsers) {
|
||||
[self updateSectionedMatrixContacts];
|
||||
return sectionedMatrixContacts.sectionedContacts.count;
|
||||
|
||||
} else {
|
||||
[self updateSectionedLocalContacts];
|
||||
return sectionedLocalContacts.sectionedContacts.count;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
SectionedContacts* sectionedContacts = contactsSearchBar ? sectionedFilteredContacts : (displayMatrixUsers ? sectionedMatrixContacts : sectionedLocalContacts);
|
||||
|
||||
return [[sectionedContacts.sectionedContacts objectAtIndex:section] count];
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return 50;
|
||||
}
|
||||
|
||||
- (NSString *)tableView:(UITableView *)aTableView titleForHeaderInSection:(NSInteger)section {
|
||||
SectionedContacts* sectionedContacts = contactsSearchBar ? sectionedFilteredContacts : (displayMatrixUsers ? sectionedMatrixContacts : sectionedLocalContacts);
|
||||
|
||||
if (sectionedContacts.sectionTitles.count <= section) {
|
||||
return nil;
|
||||
}
|
||||
else {
|
||||
return (NSString*)[sectionedContacts.sectionTitles objectAtIndex:section];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)aTableView {
|
||||
// do not display the collation during a search
|
||||
if (contactsSearchBar) {
|
||||
return nil;
|
||||
} else {
|
||||
[self.tableView setSectionIndexColor:[AppDelegate theDelegate].masterTabBarController.tabBar.tintColor];
|
||||
[self.tableView setSectionIndexBackgroundColor:[UIColor clearColor]];
|
||||
|
||||
return [[UILocalizedIndexedCollation currentCollation] sectionIndexTitles];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)aTableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
|
||||
SectionedContacts* sectionedContacts = contactsSearchBar ? sectionedFilteredContacts : (displayMatrixUsers ? sectionedMatrixContacts : sectionedLocalContacts);
|
||||
NSUInteger section = [sectionedContacts.sectionTitles indexOfObject:title];
|
||||
|
||||
// undefined title -> jump to the first valid non empty section
|
||||
if (NSNotFound == section) {
|
||||
NSUInteger systemCollationIndex = [collationTitles indexOfObject:title];
|
||||
|
||||
// find in the system collation
|
||||
if (NSNotFound != systemCollationIndex) {
|
||||
systemCollationIndex--;
|
||||
|
||||
while ((systemCollationIndex == 0) && (NSNotFound == section)) {
|
||||
NSString* systemTitle = [collationTitles objectAtIndex:systemCollationIndex];
|
||||
section = [sectionedContacts.sectionTitles indexOfObject:systemTitle];
|
||||
systemCollationIndex--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
ContactTableCell* cell = [tableView dequeueReusableCellWithIdentifier:@"ContactCell" forIndexPath:indexPath];
|
||||
SectionedContacts* sectionedContacts = contactsSearchBar ? sectionedFilteredContacts : (displayMatrixUsers ? sectionedMatrixContacts : sectionedLocalContacts);
|
||||
|
||||
MXCContact* contact = nil;
|
||||
|
||||
if (indexPath.section < sectionedContacts.sectionedContacts.count) {
|
||||
NSArray *thisSection = [sectionedContacts.sectionedContacts objectAtIndex:indexPath.section];
|
||||
|
||||
if (indexPath.row < thisSection.count) {
|
||||
contact = [thisSection objectAtIndex:indexPath.row];
|
||||
}
|
||||
}
|
||||
|
||||
// tap on matrix user thumbnail -> open a detailled sheet
|
||||
UITapGestureRecognizer* tapGesture = nil;
|
||||
|
||||
// check if it is already defined
|
||||
// gesture in storyboard does not seem to work properly
|
||||
// it always triggers a tap event on the first cell
|
||||
for (UIGestureRecognizer* gesture in cell.thumbnailView.gestureRecognizers) {
|
||||
|
||||
if ([gesture isKindOfClass:[UITapGestureRecognizer class]]) {
|
||||
tapGesture = (UITapGestureRecognizer*)gesture;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// add it if it is not yet defined
|
||||
if (!tapGesture) {
|
||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onContactThumbnailTap:)];
|
||||
[tap setNumberOfTouchesRequired:1];
|
||||
[tap setNumberOfTapsRequired:1];
|
||||
[tap setDelegate:self];
|
||||
[cell.thumbnailView addGestureRecognizer:tap];
|
||||
}
|
||||
|
||||
cell.contact = contact;
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
|
||||
SectionedContacts* sectionedContacts = contactsSearchBar ? sectionedFilteredContacts : (displayMatrixUsers ? sectionedMatrixContacts : sectionedLocalContacts);
|
||||
MXCContact* contact = nil;
|
||||
|
||||
if (indexPath.section < sectionedContacts.sectionedContacts.count) {
|
||||
NSArray *thisSection = [sectionedContacts.sectionedContacts objectAtIndex:indexPath.section];
|
||||
|
||||
if (indexPath.row < thisSection.count) {
|
||||
contact = [thisSection objectAtIndex:indexPath.row];
|
||||
}
|
||||
}
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
NSArray* matrixIDs = contact.matrixIdentifiers;
|
||||
|
||||
// matrix user ?
|
||||
if (matrixIDs.count) {
|
||||
|
||||
MatrixSDKHandler* mxHandler = [MatrixSDKHandler sharedHandler];
|
||||
|
||||
// display only if the mxSession is available in matrix SDK handler
|
||||
if (mxHandler.mxSession) {
|
||||
// only 1 matrix ID
|
||||
if (matrixIDs.count == 1) {
|
||||
NSString* matrixID = [matrixIDs objectAtIndex:0];
|
||||
|
||||
self.startChatMenu = [[MXCAlert alloc] initWithTitle:[NSString stringWithFormat:@"Chat with %@", matrixID] message:nil style:MXCAlertStyleAlert];
|
||||
|
||||
[self.startChatMenu addActionWithTitle:@"Cancel" style:MXCAlertActionStyleDefault handler:^(MXCAlert *alert) {
|
||||
weakSelf.startChatMenu = nil;
|
||||
}];
|
||||
|
||||
[self.startChatMenu addActionWithTitle:@"OK" style:MXCAlertActionStyleDefault handler:^(MXCAlert *alert) {
|
||||
weakSelf.startChatMenu = nil;
|
||||
|
||||
[mxHandler startPrivateOneToOneRoomWithUserId:matrixID];
|
||||
}];
|
||||
} else {
|
||||
self.startChatMenu = [[MXCAlert alloc] initWithTitle:[NSString stringWithFormat:@"Chat with "] message:nil style:MXCAlertStyleActionSheet];
|
||||
|
||||
for(NSString* matrixID in matrixIDs) {
|
||||
[self.startChatMenu addActionWithTitle:matrixID style:MXCAlertActionStyleDefault handler:^(MXCAlert *alert) {
|
||||
weakSelf.startChatMenu = nil;
|
||||
|
||||
[mxHandler startPrivateOneToOneRoomWithUserId:matrixID];
|
||||
}];
|
||||
}
|
||||
|
||||
[self.startChatMenu addActionWithTitle:@"Cancel" style:MXCAlertActionStyleDefault handler:^(MXCAlert *alert) {
|
||||
weakSelf.startChatMenu = nil;
|
||||
}];
|
||||
|
||||
UIView *sourceView = [tableView cellForRowAtIndexPath:indexPath];
|
||||
self.startChatMenu.sourceView = sourceView ? sourceView : tableView;
|
||||
}
|
||||
|
||||
[self.startChatMenu showInViewController:self];
|
||||
}
|
||||
} else {
|
||||
// invite to use matrix
|
||||
if (([MFMessageComposeViewController canSendText] ? contact.emailAddresses.count : 0) + (contact.phoneNumbers.count > 0)) {
|
||||
|
||||
self.startChatMenu = [[MXCAlert alloc] initWithTitle:[NSString stringWithFormat:@"Invite this user to use matrix with"] message:nil style:MXCAlertStyleActionSheet];
|
||||
|
||||
// check if the target can send SMSes
|
||||
if ([MFMessageComposeViewController canSendText]) {
|
||||
// list phonenumbers
|
||||
for(MXCPhoneNumber* phonenumber in contact.phoneNumbers) {
|
||||
|
||||
[self.startChatMenu addActionWithTitle:phonenumber.textNumber style:MXCAlertActionStyleDefault handler:^(MXCAlert *alert) {
|
||||
weakSelf.startChatMenu = nil;
|
||||
|
||||
// launch SMS composer
|
||||
MFMessageComposeViewController *messageComposer = [[MFMessageComposeViewController alloc] init];
|
||||
|
||||
if (messageComposer)
|
||||
{
|
||||
messageComposer.messageComposeDelegate = weakSelf;
|
||||
messageComposer.body =kInvitationMessage;
|
||||
messageComposer.recipients = [NSArray arrayWithObject:phonenumber.textNumber];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf presentViewController:messageComposer animated:YES completion:nil];
|
||||
});
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
// list emails
|
||||
for(MXCEmail* email in contact.emailAddresses) {
|
||||
|
||||
[self.startChatMenu addActionWithTitle:email.emailAddress style:MXCAlertActionStyleDefault handler:^(MXCAlert *alert) {
|
||||
weakSelf.startChatMenu = nil;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
NSString* subject = [ @"Matrix.org is magic" stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
||||
NSString* body = [kInvitationMessage stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"mailto:%@?subject=%@&body=%@", email.emailAddress, subject, body]]];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
[self.startChatMenu addActionWithTitle:@"Cancel" style:MXCAlertActionStyleDefault handler:^(MXCAlert *alert) {
|
||||
weakSelf.startChatMenu = nil;
|
||||
}];
|
||||
|
||||
UIView *sourceView = [tableView cellForRowAtIndexPath:indexPath];
|
||||
self.startChatMenu.sourceView = sourceView ? sourceView : tableView;
|
||||
[self.startChatMenu showInViewController:self];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - KVO
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
|
||||
if ([@"status" isEqualToString:keyPath]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (displayMatrixUsers) {
|
||||
if (contactsSearchBar) {
|
||||
[self updateSectionedMatrixContacts];
|
||||
latestSearchedPattern = nil;
|
||||
[self searchBar:contactsSearchBar textDidChange:contactsSearchBar.text];
|
||||
} else {
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)onContactsRefresh:(NSNotification *)notif {
|
||||
localContacts = nil;
|
||||
sectionedLocalContacts = nil;
|
||||
|
||||
// there is an user id
|
||||
if ([[MatrixSDKHandler sharedHandler] userId]) {
|
||||
[self updateSectionedLocalContacts];
|
||||
//
|
||||
if (!displayMatrixUsers) {
|
||||
if (contactsSearchBar) {
|
||||
latestSearchedPattern = nil;
|
||||
[self searchBar:contactsSearchBar textDidChange:contactsSearchBar.text];
|
||||
} else {
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// the client could have been logged out
|
||||
[self reset];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)onSegmentValueChange:(id)sender {
|
||||
if (sender == self.contactsControls) {
|
||||
displayMatrixUsers = (0 == self.contactsControls.selectedSegmentIndex);
|
||||
|
||||
if (contactsSearchBar) {
|
||||
if (displayMatrixUsers) {
|
||||
[self updateSectionedMatrixContacts];
|
||||
} else {
|
||||
[self updateSectionedLocalContacts];
|
||||
}
|
||||
|
||||
latestSearchedPattern = nil;
|
||||
[self searchBar:contactsSearchBar textDidChange:contactsSearchBar.text];
|
||||
} else {
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
if (!displayMatrixUsers) {
|
||||
AppSettings* appSettings = [AppSettings sharedSettings];
|
||||
|
||||
if (!appSettings.syncLocalContacts) {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
|
||||
self.allowContactSyncAlert = [[MXCAlert alloc] initWithTitle:@"Allow local contacts synchronization ?" message:nil style:MXCAlertStyleAlert];
|
||||
|
||||
[self.allowContactSyncAlert addActionWithTitle:@"No" style:MXCAlertActionStyleDefault handler:^(MXCAlert *alert) {
|
||||
weakSelf.allowContactSyncAlert = nil;
|
||||
}];
|
||||
|
||||
[self.allowContactSyncAlert addActionWithTitle:@"Yes" style:MXCAlertActionStyleDefault handler:^(MXCAlert *alert) {
|
||||
weakSelf.allowContactSyncAlert = nil;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
appSettings.syncLocalContacts = YES;
|
||||
[weakSelf.tableView reloadData];
|
||||
});
|
||||
}];
|
||||
|
||||
[self.allowContactSyncAlert showInViewController:self];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)onContactThumbnailTap:(id)sender {
|
||||
if ([sender isKindOfClass:[UITapGestureRecognizer class]]) {
|
||||
UIView* tappedView = ((UITapGestureRecognizer*)sender).view;
|
||||
|
||||
// search the parentce cell
|
||||
while (tappedView && ![tappedView isKindOfClass:[ContactTableCell class]]) {
|
||||
tappedView = tappedView.superview;
|
||||
}
|
||||
|
||||
// find it ?
|
||||
if ([tappedView isKindOfClass:[ContactTableCell class]]) {
|
||||
MXCContact* contact = ((ContactTableCell*)tappedView).contact;
|
||||
|
||||
// open detailled sheet if there
|
||||
if (contact.matrixIdentifiers.count > 0) {
|
||||
selectedContact = ((ContactTableCell*)tappedView).contact;
|
||||
[self performSegueWithIdentifier:@"showContactDetails" sender:self];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Segues
|
||||
|
||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||
if ([segue.identifier isEqualToString:@"showContactDetails"]) {
|
||||
ContactDetailsViewController *contactDetailsViewController = segue.destinationViewController;
|
||||
contactDetailsViewController.contact = selectedContact;
|
||||
selectedContact = nil;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark MFMessageComposeViewControllerDelegate
|
||||
|
||||
- (void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result {
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark Search management
|
||||
|
||||
- (void)search:(id)sender {
|
||||
if (!contactsSearchBar) {
|
||||
SectionedContacts* sectionedContacts = displayMatrixUsers ? sectionedMatrixContacts : sectionedLocalContacts;
|
||||
|
||||
// Check whether there are data in which search
|
||||
if (sectionedContacts.sectionedContacts.count > 0) {
|
||||
// Create search bar
|
||||
contactsSearchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 44)];
|
||||
contactsSearchBar.showsCancelButton = YES;
|
||||
contactsSearchBar.returnKeyType = UIReturnKeyDone;
|
||||
contactsSearchBar.delegate = self;
|
||||
contactsSearchBar.tintColor = [AppDelegate theDelegate].masterTabBarController.tabBar.tintColor;
|
||||
searchBarShouldEndEditing = NO;
|
||||
|
||||
// init the table content
|
||||
latestSearchedPattern = @"";
|
||||
filteredContacts = [(displayMatrixUsers ? [matrixUserByMatrixID allValues] : localContacts) mutableCopy];
|
||||
sectionedFilteredContacts = [[ContactManager sharedManager] getSectionedContacts:filteredContacts];
|
||||
|
||||
self.tableView.tableHeaderView = contactsSearchBar;
|
||||
[self.tableView reloadData];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[contactsSearchBar becomeFirstResponder];
|
||||
});
|
||||
}
|
||||
} else {
|
||||
[self searchBarCancelButtonClicked:contactsSearchBar];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UISearchBarDelegate
|
||||
|
||||
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
|
||||
searchBarShouldEndEditing = NO;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar {
|
||||
return searchBarShouldEndEditing;
|
||||
}
|
||||
|
||||
- (NSArray*)patternsFromText:(NSString*)text {
|
||||
NSArray* items = [text componentsSeparatedByString:@" "];
|
||||
|
||||
if (items.count <= 1) {
|
||||
return items;
|
||||
}
|
||||
|
||||
NSMutableArray* patterns = [[NSMutableArray alloc] init];
|
||||
|
||||
for (NSString* item in items) {
|
||||
if (item.length > 0) {
|
||||
[patterns addObject:item];
|
||||
}
|
||||
}
|
||||
|
||||
return patterns;
|
||||
}
|
||||
|
||||
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
|
||||
if ((contactsSearchBar == searchBar) && (![latestSearchedPattern isEqualToString:searchText])) {
|
||||
latestSearchedPattern = searchText;
|
||||
|
||||
// contacts
|
||||
NSArray* contacts = displayMatrixUsers ? [matrixUserByMatrixID allValues] : localContacts;
|
||||
|
||||
// Update filtered list
|
||||
if (searchText.length && contacts.count) {
|
||||
|
||||
filteredContacts = [[NSMutableArray alloc] init];
|
||||
|
||||
NSArray* patterns = [self patternsFromText:searchText];
|
||||
for(MXCContact* contact in contacts) {
|
||||
if ([contact matchedWithPatterns:patterns]) {
|
||||
[filteredContacts addObject:contact];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filteredContacts = [contacts mutableCopy];
|
||||
}
|
||||
|
||||
sectionedFilteredContacts = [[ContactManager sharedManager] getSectionedContacts:filteredContacts];
|
||||
|
||||
// Refresh display
|
||||
[self.tableView reloadData];
|
||||
[self scrollToTop];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
|
||||
if (contactsSearchBar == searchBar) {
|
||||
// "Done" key has been pressed
|
||||
searchBarShouldEndEditing = YES;
|
||||
[contactsSearchBar resignFirstResponder];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
|
||||
if (contactsSearchBar == searchBar) {
|
||||
// Leave search
|
||||
searchBarShouldEndEditing = YES;
|
||||
[contactsSearchBar resignFirstResponder];
|
||||
contactsSearchBar = nil;
|
||||
filteredContacts = nil;
|
||||
sectionedFilteredContacts = nil;
|
||||
latestSearchedPattern = nil;
|
||||
self.tableView.tableHeaderView = nil;
|
||||
[self.tableView reloadData];
|
||||
[self scrollToTop];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
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 "MXCTableViewController.h"
|
||||
|
||||
@interface HomeViewController : MXCTableViewController <UITextFieldDelegate, UISearchBarDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,636 @@
|
||||
/*
|
||||
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 "HomeViewController.h"
|
||||
|
||||
#import "MatrixSDKHandler.h"
|
||||
#import "AppDelegate.h"
|
||||
#import "PublicRoomTableCell.h"
|
||||
|
||||
@interface HomeViewController () {
|
||||
NSArray *publicRooms;
|
||||
|
||||
// List of public room names to highlight in displayed list
|
||||
NSArray* highlightedPublicRooms;
|
||||
|
||||
// Search in public room
|
||||
UISearchBar *publicRoomsSearchBar;
|
||||
NSMutableArray *filteredPublicRooms;
|
||||
BOOL searchBarShouldEndEditing;
|
||||
UIView *savedTableHeaderView;
|
||||
|
||||
NSString *homeServerSuffix;
|
||||
}
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UITableView *publicRoomsTable;
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UILabel *roomCreationSectionLabel;
|
||||
@property (weak, nonatomic) IBOutlet UIView *roomCreationSectionView;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *roomNameLabel;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *roomAliasLabel;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *participantsLabel;
|
||||
@property (weak, nonatomic) IBOutlet UITextField *roomNameTextField;
|
||||
@property (weak, nonatomic) IBOutlet UITextField *roomAliasTextField;
|
||||
@property (weak, nonatomic) IBOutlet UITextField *participantsTextField;
|
||||
@property (weak, nonatomic) IBOutlet UISegmentedControl *roomVisibilityControl;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *createRoomBtn;
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UILabel *joinRoomSectionLabel;
|
||||
@property (weak, nonatomic) IBOutlet UITextField *joinRoomAliasTextField;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *joinRoomBtn;
|
||||
|
||||
- (IBAction)onButtonPressed:(id)sender;
|
||||
|
||||
@end
|
||||
|
||||
@implementation HomeViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
// Do any additional setup after loading the view, typically from a nib.
|
||||
_roomCreationSectionLabel.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1.0];
|
||||
_createRoomBtn.enabled = NO;
|
||||
|
||||
_joinRoomSectionLabel.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1.0];
|
||||
_joinRoomBtn.enabled = NO;
|
||||
|
||||
// Init
|
||||
publicRooms = nil;
|
||||
highlightedPublicRooms = @[@"#matrix:matrix.org", @"#matrix-dev:matrix.org", @"#matrix-fr:matrix.org"]; // Add here a room name to highlight its display in public room list
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning {
|
||||
[super didReceiveMemoryWarning];
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
- (void)dealloc{
|
||||
publicRooms = nil;
|
||||
highlightedPublicRooms = nil;
|
||||
|
||||
publicRoomsSearchBar = nil;
|
||||
filteredPublicRooms = nil;
|
||||
savedTableHeaderView = nil;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
// Ensure to display room creation section
|
||||
[self.tableView scrollRectToVisible:_roomCreationSectionLabel.frame animated:NO];
|
||||
|
||||
if ([MatrixSDKHandler sharedHandler].status != MatrixSDKHandlerStatusLoggedOut) {
|
||||
homeServerSuffix = [NSString stringWithFormat:@":%@",[MatrixSDKHandler sharedHandler].homeServer];
|
||||
// Update alias placeholder
|
||||
_roomAliasTextField.placeholder = [NSString stringWithFormat:@"(e.g. #foo%@)", homeServerSuffix];
|
||||
// Refresh listed public rooms
|
||||
[self refreshPublicRooms];
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onTextFieldChange:) name:UITextFieldTextDidChangeNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
// Leave potential search session
|
||||
if (publicRoomsSearchBar) {
|
||||
[self searchBarCancelButtonClicked:publicRoomsSearchBar];
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidChangeNotification object:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Internals
|
||||
|
||||
- (void)refreshPublicRooms {
|
||||
// Retrieve public rooms
|
||||
[[MatrixSDKHandler sharedHandler].mxRestClient publicRooms:^(NSArray *rooms){
|
||||
publicRooms = [rooms sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
|
||||
|
||||
MXPublicRoom *firstRoom = (MXPublicRoom*)a;
|
||||
MXPublicRoom *secondRoom = (MXPublicRoom*)b;
|
||||
|
||||
// Compare member count
|
||||
if (firstRoom.numJoinedMembers < secondRoom.numJoinedMembers) {
|
||||
return NSOrderedDescending;
|
||||
} else if (firstRoom.numJoinedMembers > secondRoom.numJoinedMembers) {
|
||||
return NSOrderedAscending;
|
||||
} else {
|
||||
// Alphabetic order
|
||||
return [firstRoom.displayname compare:secondRoom.displayname options:NSCaseInsensitiveSearch];
|
||||
}
|
||||
}];
|
||||
[_publicRoomsTable reloadData];
|
||||
}
|
||||
failure:^(NSError *error){
|
||||
NSLog(@"[HomeVC] Failed to get public rooms: %@", error);
|
||||
//Alert user
|
||||
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
- (void)search:(id)sender {
|
||||
if (!publicRoomsSearchBar) {
|
||||
// Check whether there are data in which search
|
||||
if (publicRooms.count) {
|
||||
// Create search bar
|
||||
publicRoomsSearchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 44)];
|
||||
publicRoomsSearchBar.showsCancelButton = YES;
|
||||
publicRoomsSearchBar.returnKeyType = UIReturnKeyDone;
|
||||
publicRoomsSearchBar.delegate = self;
|
||||
[publicRoomsSearchBar becomeFirstResponder];
|
||||
publicRoomsSearchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
|
||||
// Hide table header during search session
|
||||
savedTableHeaderView = self.tableView.tableHeaderView;
|
||||
self.tableView.tableHeaderView = nil;
|
||||
// Reload table in order to display search bar as section header
|
||||
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
|
||||
[self.tableView reloadData];
|
||||
|
||||
}
|
||||
} else {
|
||||
[self searchBarCancelButtonClicked: publicRoomsSearchBar];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dismissKeyboard {
|
||||
// Hide the keyboard
|
||||
[_roomNameTextField resignFirstResponder];
|
||||
[_roomAliasTextField resignFirstResponder];
|
||||
[_participantsTextField resignFirstResponder];
|
||||
[_joinRoomAliasTextField resignFirstResponder];
|
||||
}
|
||||
|
||||
- (NSString*)alias {
|
||||
// Extract alias name from alias text field
|
||||
NSString *alias = _roomAliasTextField.text;
|
||||
if (alias.length > 1) {
|
||||
// Remove '#' character
|
||||
alias = [alias substringFromIndex:1];
|
||||
// Remove homeserver
|
||||
NSRange range = [alias rangeOfString:homeServerSuffix];
|
||||
if (range.location == NSNotFound) {
|
||||
NSLog(@"[HomeVC] Wrong room alias has been set (%@)", _roomAliasTextField.text);
|
||||
alias = nil;
|
||||
} else {
|
||||
alias = [alias stringByReplacingCharactersInRange:range withString:@""];
|
||||
}
|
||||
}
|
||||
|
||||
if (! alias.length) {
|
||||
alias = nil;
|
||||
}
|
||||
|
||||
return alias;
|
||||
}
|
||||
|
||||
- (NSArray*)participantsList {
|
||||
NSMutableArray *participants = [NSMutableArray array];
|
||||
|
||||
if (_participantsTextField.text.length) {
|
||||
NSArray *components = [_participantsTextField.text componentsSeparatedByString:@";"];
|
||||
|
||||
for (NSString *component in components) {
|
||||
// Remove white space from both ends
|
||||
NSString *user = [component stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
if (user.length > 1 && [user hasPrefix:@"@"]) {
|
||||
[participants addObject:user];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (participants.count == 0) {
|
||||
participants = nil;
|
||||
}
|
||||
|
||||
return participants;
|
||||
}
|
||||
|
||||
#pragma mark - UITextField delegate
|
||||
|
||||
- (void)onTextFieldChange:(NSNotification *)notif {
|
||||
// Update Create Room button
|
||||
NSString *roomName = _roomNameTextField.text;
|
||||
NSString *roomAlias = _roomAliasTextField.text;
|
||||
NSString *participants = _participantsTextField.text;
|
||||
|
||||
if (roomName.length || roomAlias.length || participants.length) {
|
||||
_createRoomBtn.enabled = YES;
|
||||
} else {
|
||||
_createRoomBtn.enabled = NO;
|
||||
}
|
||||
|
||||
// Update Join Room button
|
||||
_joinRoomBtn.enabled = (_joinRoomAliasTextField.text.length != 0);
|
||||
}
|
||||
|
||||
- (void)textFieldDidBeginEditing:(UITextField *)textField {
|
||||
if (textField == _participantsTextField) {
|
||||
if (textField.text.length == 0) {
|
||||
textField.text = @"@";
|
||||
}
|
||||
} else if (textField == _roomAliasTextField || textField == _joinRoomAliasTextField) {
|
||||
if (textField.text.length == 0) {
|
||||
textField.text = @"#";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)textFieldDidEndEditing:(UITextField *)textField {
|
||||
if (textField == _roomAliasTextField) {
|
||||
// Check whether homeserver suffix should be added
|
||||
NSRange range = [textField.text rangeOfString:@":"];
|
||||
if (range.location == NSNotFound) {
|
||||
textField.text = [textField.text stringByAppendingString:homeServerSuffix];
|
||||
}
|
||||
// Check whether the alias is valid
|
||||
if (!self.alias) {
|
||||
// reset text field
|
||||
textField.text = nil;
|
||||
[self onTextFieldChange:nil];
|
||||
}
|
||||
} else if (textField == _participantsTextField) {
|
||||
NSArray *participants = self.participantsList;
|
||||
textField.text = [participants componentsJoinedByString:@"; "];
|
||||
} else if (textField == _joinRoomAliasTextField) {
|
||||
if (textField.text.length > 1) {
|
||||
// Add homeserver suffix if none
|
||||
NSRange range = [textField.text rangeOfString:@":"];
|
||||
if (range.location == NSNotFound) {
|
||||
textField.text = [textField.text stringByAppendingString:homeServerSuffix];
|
||||
}
|
||||
} else {
|
||||
// reset text field
|
||||
textField.text = nil;
|
||||
[self onTextFieldChange:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
|
||||
// Auto complete participant IDs
|
||||
if (textField == _participantsTextField) {
|
||||
// Add @ if none
|
||||
if (!textField.text.length || textField.text.length == range.length) {
|
||||
if ([string hasPrefix:@"@"] == NO) {
|
||||
textField.text = [NSString stringWithFormat:@"@%@",string];
|
||||
// Update Create button status
|
||||
[self onTextFieldChange:nil];
|
||||
return NO;
|
||||
}
|
||||
} else if (range.location == textField.text.length) {
|
||||
if ([string isEqualToString:@";"]) {
|
||||
// Add '@' character
|
||||
textField.text = [textField.text stringByAppendingString:@"; @"];
|
||||
// Update Create button status
|
||||
[self onTextFieldChange:nil];
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
} else if (textField == _roomAliasTextField) {
|
||||
// Add # if none
|
||||
if (!textField.text.length || textField.text.length == range.length) {
|
||||
if ([string hasPrefix:@"#"] == NO) {
|
||||
if ([string isEqualToString:@":"]) {
|
||||
textField.text = [NSString stringWithFormat:@"#%@",homeServerSuffix];
|
||||
} else {
|
||||
textField.text = [NSString stringWithFormat:@"#%@",string];
|
||||
}
|
||||
// Update Create button status
|
||||
[self onTextFieldChange:nil];
|
||||
return NO;
|
||||
}
|
||||
} else {
|
||||
// Add homeserver automatically when user adds ':' at the end
|
||||
if (range.location == textField.text.length && [string isEqualToString:@":"]) {
|
||||
textField.text = [textField.text stringByAppendingString:homeServerSuffix];
|
||||
// Update Create button status
|
||||
[self onTextFieldChange:nil];
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
} else if (textField == _joinRoomAliasTextField) {
|
||||
// Add # if none
|
||||
if (!textField.text.length || textField.text.length == range.length) {
|
||||
if ([string hasPrefix:@"#"] == NO) {
|
||||
textField.text = [NSString stringWithFormat:@"#%@",string];
|
||||
// Update Create button status
|
||||
[self onTextFieldChange:nil];
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField*) textField {
|
||||
// "Done" key has been pressed
|
||||
[textField resignFirstResponder];
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (IBAction)onButtonPressed:(id)sender {
|
||||
[self dismissKeyboard];
|
||||
|
||||
if (sender == _createRoomBtn) {
|
||||
// Disable button to prevent multiple request
|
||||
_createRoomBtn.enabled = NO;
|
||||
|
||||
NSString *roomName = _roomNameTextField.text;
|
||||
if (! roomName.length) {
|
||||
roomName = nil;
|
||||
}
|
||||
|
||||
// Create new room
|
||||
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
|
||||
[mxHandler.mxRestClient createRoom:roomName
|
||||
visibility:(_roomVisibilityControl.selectedSegmentIndex == 0) ? kMXRoomVisibilityPublic : kMXRoomVisibilityPrivate
|
||||
roomAlias:self.alias
|
||||
topic:nil
|
||||
success:^(MXCreateRoomResponse *response) {
|
||||
// Check whether some users must be invited
|
||||
NSArray *invitedUsers = self.participantsList;
|
||||
for (NSString *userId in invitedUsers) {
|
||||
[mxHandler.mxRestClient inviteUser:userId toRoom:response.roomId success:^{
|
||||
NSLog(@"[HomeVC] %@ has been invited (roomId: %@)", userId, response.roomId);
|
||||
} failure:^(NSError *error) {
|
||||
NSLog(@"[HomeVC] %@ invitation failed (roomId: %@): %@", userId, response.roomId, error);
|
||||
//Alert user
|
||||
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
||||
}];
|
||||
}
|
||||
|
||||
// Reset text fields
|
||||
_roomNameTextField.text = nil;
|
||||
_roomAliasTextField.text = nil;
|
||||
_participantsTextField.text = nil;
|
||||
// Open created room
|
||||
[[AppDelegate theDelegate].masterTabBarController showRoom:response.roomId];
|
||||
} failure:^(NSError *error) {
|
||||
_createRoomBtn.enabled = YES;
|
||||
NSLog(@"[HomeVC] Create room (%@ %@ (%@)) failed: %@", _roomNameTextField.text, self.alias, (_roomVisibilityControl.selectedSegmentIndex == 0) ? @"Public":@"Private", error);
|
||||
//Alert user
|
||||
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
||||
}];
|
||||
} else if (sender == _joinRoomBtn) {
|
||||
// Disable button to prevent multiple request
|
||||
_joinRoomBtn.enabled = NO;
|
||||
|
||||
NSString *roomAlias = _joinRoomAliasTextField.text;
|
||||
// Remove white space from both ends
|
||||
roomAlias = [roomAlias stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
|
||||
// Check
|
||||
if (roomAlias.length) {
|
||||
[[MatrixSDKHandler sharedHandler].mxSession joinRoom:roomAlias success:^(MXRoom *room) {
|
||||
// Reset text fields
|
||||
_joinRoomAliasTextField.text = nil;
|
||||
// Show the room
|
||||
[[AppDelegate theDelegate].masterTabBarController showRoom:room.state.roomId];
|
||||
} failure:^(NSError *error) {
|
||||
_joinRoomBtn.enabled = YES;
|
||||
NSLog(@"[HomeVC] Failed to join room alias (%@): %@", roomAlias, error);
|
||||
//Alert user
|
||||
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
||||
}];
|
||||
} else {
|
||||
// Reset text fields
|
||||
_joinRoomAliasTextField.text = nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Table view data source
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
if (filteredPublicRooms) {
|
||||
return filteredPublicRooms.count;
|
||||
}
|
||||
return publicRooms.count;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
|
||||
if (publicRoomsSearchBar) {
|
||||
return (publicRoomsSearchBar.frame.size.height + 40);
|
||||
}
|
||||
return 40;
|
||||
}
|
||||
|
||||
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
|
||||
UIView *sectionHeader = [[UIView alloc] initWithFrame:[tableView rectForHeaderInSection:section]];
|
||||
sectionHeader.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1.0];
|
||||
UILabel *sectionLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, sectionHeader.frame.size.width, 40)];
|
||||
sectionLabel.font = [UIFont boldSystemFontOfSize:16];
|
||||
sectionLabel.backgroundColor = [UIColor clearColor];
|
||||
[sectionHeader addSubview:sectionLabel];
|
||||
|
||||
if (publicRooms) {
|
||||
NSString *homeserver = [MatrixSDKHandler sharedHandler].homeServerURL;
|
||||
if (homeserver.length) {
|
||||
sectionLabel.text = [NSString stringWithFormat:@" Public Rooms (at %@):", homeserver];
|
||||
} else {
|
||||
sectionLabel.text = @" Public Rooms:";
|
||||
}
|
||||
|
||||
UIButton *searchButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[searchButton setImage:[UIImage imageNamed:@"icon_search"] forState:UIControlStateNormal];
|
||||
[searchButton setImage:[UIImage imageNamed:@"icon_search"] forState:UIControlStateHighlighted];
|
||||
[searchButton addTarget:self action:@selector(search:) forControlEvents:UIControlEventTouchUpInside];
|
||||
searchButton.frame = CGRectMake(sectionLabel.frame.size.width - 45, 0, 40, 40);
|
||||
searchButton.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin);
|
||||
[sectionHeader addSubview:searchButton];
|
||||
sectionHeader.userInteractionEnabled = YES;
|
||||
|
||||
if (publicRoomsSearchBar) {
|
||||
CGRect frame = publicRoomsSearchBar.frame;
|
||||
frame.origin.y = 40;
|
||||
publicRoomsSearchBar.frame = frame;
|
||||
[sectionHeader addSubview:publicRoomsSearchBar];
|
||||
}
|
||||
} else {
|
||||
sectionLabel.text = @" No Public Rooms";
|
||||
}
|
||||
|
||||
return sectionHeader;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
// Cell is larger for public room with topic
|
||||
MXPublicRoom *publicRoom;
|
||||
if (filteredPublicRooms) {
|
||||
publicRoom = [filteredPublicRooms objectAtIndex:indexPath.row];
|
||||
} else {
|
||||
publicRoom = [publicRooms objectAtIndex:indexPath.row];
|
||||
}
|
||||
|
||||
if (publicRoom.topic) {
|
||||
return 60;
|
||||
}
|
||||
return 44;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
PublicRoomTableCell *cell;
|
||||
PublicRoomWithTopicTableCell *cellWithTopic = nil;
|
||||
|
||||
MXPublicRoom *publicRoom;
|
||||
if (filteredPublicRooms) {
|
||||
publicRoom = [filteredPublicRooms objectAtIndex:indexPath.row];
|
||||
} else {
|
||||
publicRoom = [publicRooms objectAtIndex:indexPath.row];
|
||||
}
|
||||
|
||||
// Check whether this public room has topic
|
||||
if (publicRoom.topic) {
|
||||
cellWithTopic = [_publicRoomsTable dequeueReusableCellWithIdentifier:@"PublicRoomWithTopicCell" forIndexPath:indexPath];
|
||||
cellWithTopic.roomTopic.text = publicRoom.topic;
|
||||
cell = cellWithTopic;
|
||||
} else {
|
||||
cell = [_publicRoomsTable dequeueReusableCellWithIdentifier:@"PublicRoomCell" forIndexPath:indexPath];
|
||||
}
|
||||
|
||||
// Set room display name
|
||||
cell.roomDisplayName.text = [publicRoom displayname];
|
||||
|
||||
// Set member count
|
||||
if (publicRoom.numJoinedMembers > 1) {
|
||||
cell.memberCount.text = [NSString stringWithFormat:@"%lu users", (unsigned long)publicRoom.numJoinedMembers];
|
||||
} else if (publicRoom.numJoinedMembers == 1) {
|
||||
cell.memberCount.text = @"1 user";
|
||||
} else {
|
||||
cell.memberCount.text = nil;
|
||||
}
|
||||
|
||||
// Highlight?
|
||||
if (cell.roomDisplayName.text && [highlightedPublicRooms indexOfObject:cell.roomDisplayName.text] != NSNotFound) {
|
||||
cell.roomDisplayName.font = [UIFont boldSystemFontOfSize:20];
|
||||
if (cellWithTopic) {
|
||||
cellWithTopic.roomTopic.font = [UIFont boldSystemFontOfSize:17];
|
||||
}
|
||||
cell.backgroundColor = [UIColor colorWithRed:1.0 green:1.0 blue:0.9 alpha:1.0];
|
||||
} else {
|
||||
cell.roomDisplayName.font = [UIFont systemFontOfSize:19];
|
||||
if (cellWithTopic) {
|
||||
cellWithTopic.roomTopic.font = [UIFont systemFontOfSize:16];
|
||||
}
|
||||
cell.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
#pragma mark - Table view delegate
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
MXPublicRoom *publicRoom;
|
||||
if (filteredPublicRooms) {
|
||||
publicRoom = [filteredPublicRooms objectAtIndex:indexPath.row];
|
||||
} else {
|
||||
publicRoom = [publicRooms objectAtIndex:indexPath.row];
|
||||
}
|
||||
|
||||
// Check whether the user has already joined the selected public room
|
||||
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
|
||||
if ([mxHandler.mxSession roomWithRoomId:publicRoom.roomId]) {
|
||||
// Open selected room
|
||||
[[AppDelegate theDelegate].masterTabBarController showRoom:publicRoom.roomId];
|
||||
} else {
|
||||
// Join the selected room
|
||||
UIActivityIndicatorView *loadingWheel = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
|
||||
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
|
||||
if (selectedCell) {
|
||||
CGPoint center = CGPointMake(selectedCell.frame.size.width / 2, selectedCell.frame.size.height / 2);
|
||||
loadingWheel.center = center;
|
||||
[selectedCell addSubview:loadingWheel];
|
||||
}
|
||||
[loadingWheel startAnimating];
|
||||
[mxHandler.mxSession joinRoom:publicRoom.roomId success:^(MXRoom *room) {
|
||||
// Show joined room
|
||||
[loadingWheel stopAnimating];
|
||||
[loadingWheel removeFromSuperview];
|
||||
[[AppDelegate theDelegate].masterTabBarController showRoom:publicRoom.roomId];
|
||||
} failure:^(NSError *error) {
|
||||
NSLog(@"[HomeVC] Failed to join public room (%@): %@", publicRoom.displayname, error);
|
||||
//Alert user
|
||||
[loadingWheel stopAnimating];
|
||||
[loadingWheel removeFromSuperview];
|
||||
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
||||
}];
|
||||
}
|
||||
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - UISearchBarDelegate
|
||||
|
||||
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
|
||||
searchBarShouldEndEditing = NO;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar {
|
||||
return searchBarShouldEndEditing;
|
||||
}
|
||||
|
||||
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
|
||||
// Update filtered list
|
||||
if (searchText.length) {
|
||||
if (filteredPublicRooms) {
|
||||
[filteredPublicRooms removeAllObjects];
|
||||
} else {
|
||||
filteredPublicRooms = [NSMutableArray arrayWithCapacity:publicRooms.count];
|
||||
}
|
||||
for (MXPublicRoom *publicRoom in publicRooms) {
|
||||
if ([[publicRoom displayname] rangeOfString:searchText options:NSCaseInsensitiveSearch].location != NSNotFound) {
|
||||
[filteredPublicRooms addObject:publicRoom];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filteredPublicRooms = nil;
|
||||
}
|
||||
// Refresh display
|
||||
[self.tableView reloadData];
|
||||
if (filteredPublicRooms.count) {
|
||||
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:NO];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
|
||||
// "Done" key has been pressed
|
||||
searchBarShouldEndEditing = YES;
|
||||
[searchBar resignFirstResponder];
|
||||
}
|
||||
|
||||
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
|
||||
// Leave search
|
||||
searchBarShouldEndEditing = YES;
|
||||
[searchBar resignFirstResponder];
|
||||
publicRoomsSearchBar = nil;
|
||||
filteredPublicRooms = nil;
|
||||
// Restore table header and refresh table display
|
||||
self.tableView.tableHeaderView = savedTableHeaderView;
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
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 <UIKit/UIKit.h>
|
||||
|
||||
#import "AFNetworkReachabilityManager.h"
|
||||
|
||||
/**
|
||||
* MXCTableViewController extends UITableViewController for the following points:
|
||||
* - support rage shake mechanism
|
||||
* - update navigation bar tintColor according to network reachability
|
||||
*/
|
||||
|
||||
@interface MXCTableViewController : UITableViewController
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
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 "MXCTableViewController.h"
|
||||
|
||||
#import "RageShakableUIResponder.h"
|
||||
|
||||
@interface MXCTableViewController () {
|
||||
id mxcTableViewControllerReachabilityObserver;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation MXCTableViewController
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
[RageShakableUIResponder cancel:self];
|
||||
|
||||
if (self.navigationController) {
|
||||
// The navigation bar tintColor depends on reachability status - Register reachability observer
|
||||
__weak typeof(self) weakSelf = self;
|
||||
mxcTableViewControllerReachabilityObserver = [[NSNotificationCenter defaultCenter] addObserverForName:AFNetworkingReachabilityDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) {
|
||||
[weakSelf onReachabilityStatusChange];
|
||||
}];
|
||||
// Force update
|
||||
[self onReachabilityStatusChange];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:mxcTableViewControllerReachabilityObserver];
|
||||
[RageShakableUIResponder cancel:self];
|
||||
}
|
||||
|
||||
#pragma mark - Reachability monitoring
|
||||
|
||||
- (void)onReachabilityStatusChange {
|
||||
// Retrieve the current reachability status
|
||||
AFNetworkReachabilityManager *reachabilityManager = [AFNetworkReachabilityManager sharedManager];
|
||||
AFNetworkReachabilityStatus status = reachabilityManager.networkReachabilityStatus;
|
||||
|
||||
// Retrieve the main navigation controller if the current view controller is embedded inside a split view controller.
|
||||
UINavigationController *mainNavigationController = nil;
|
||||
if (self.splitViewController) {
|
||||
mainNavigationController = self.navigationController;
|
||||
UIViewController *parentViewController = self.parentViewController;
|
||||
while (parentViewController) {
|
||||
if (parentViewController.navigationController) {
|
||||
mainNavigationController = parentViewController.navigationController;
|
||||
parentViewController = parentViewController.parentViewController;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update navigationBar tintColor
|
||||
if (status == AFNetworkReachabilityStatusNotReachable) {
|
||||
self.navigationController.navigationBar.barTintColor = [UIColor redColor];
|
||||
if (mainNavigationController) {
|
||||
mainNavigationController.navigationBar.barTintColor = [UIColor redColor];
|
||||
}
|
||||
} else if (status == AFNetworkReachabilityStatusReachableViaWiFi || status == AFNetworkReachabilityStatusReachableViaWWAN) {
|
||||
self.navigationController.navigationBar.barTintColor = nil;
|
||||
if (mainNavigationController) {
|
||||
mainNavigationController.navigationBar.barTintColor = nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Rage shake handling
|
||||
|
||||
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {
|
||||
if (motion == UIEventSubtypeMotionShake) {
|
||||
[RageShakableUIResponder startShaking:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event {
|
||||
[self motionEnded:motion withEvent:event];
|
||||
}
|
||||
|
||||
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
|
||||
if (motion == UIEventSubtypeMotionShake) {
|
||||
[RageShakableUIResponder stopShaking:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder {
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
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 <UIKit/UIKit.h>
|
||||
|
||||
#import "AFNetworkReachabilityManager.h"
|
||||
|
||||
/**
|
||||
* MXCViewController extends UIViewController for the following points:
|
||||
* - support rage shake mechanism
|
||||
* - update navigation bar tintColor according to network reachability
|
||||
*/
|
||||
|
||||
@interface MXCViewController : UIViewController
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
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 "MXCViewController.h"
|
||||
|
||||
#import "RageShakableUIResponder.h"
|
||||
|
||||
@interface MXCViewController () {
|
||||
id mxcViewControllerReachabilityObserver;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation MXCViewController
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
[RageShakableUIResponder cancel:self];
|
||||
|
||||
if (self.navigationController) {
|
||||
// The navigation bar tintColor depends on reachability status - Register reachability observer
|
||||
__weak typeof(self) weakSelf = self;
|
||||
mxcViewControllerReachabilityObserver = [[NSNotificationCenter defaultCenter] addObserverForName:AFNetworkingReachabilityDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) {
|
||||
[weakSelf onReachabilityStatusChange];
|
||||
}];
|
||||
// Force update
|
||||
[self onReachabilityStatusChange];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:mxcViewControllerReachabilityObserver];
|
||||
[RageShakableUIResponder cancel:self];
|
||||
}
|
||||
|
||||
#pragma mark - Reachability monitoring
|
||||
|
||||
- (void)onReachabilityStatusChange {
|
||||
// Retrieve the current reachability status
|
||||
AFNetworkReachabilityManager *reachabilityManager = [AFNetworkReachabilityManager sharedManager];
|
||||
AFNetworkReachabilityStatus status = reachabilityManager.networkReachabilityStatus;
|
||||
|
||||
// Retrieve the main navigation controller if the current view controller is embedded inside a split view controller.
|
||||
UINavigationController *mainNavigationController = nil;
|
||||
if (self.splitViewController) {
|
||||
mainNavigationController = self.navigationController;
|
||||
UIViewController *parentViewController = self.parentViewController;
|
||||
while (parentViewController) {
|
||||
if (parentViewController.navigationController) {
|
||||
mainNavigationController = parentViewController.navigationController;
|
||||
parentViewController = parentViewController.parentViewController;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update navigationBar tintColor
|
||||
if (status == AFNetworkReachabilityStatusNotReachable) {
|
||||
self.navigationController.navigationBar.barTintColor = [UIColor redColor];
|
||||
if (mainNavigationController) {
|
||||
mainNavigationController.navigationBar.barTintColor = [UIColor redColor];
|
||||
}
|
||||
} else if (status == AFNetworkReachabilityStatusReachableViaWiFi || status == AFNetworkReachabilityStatusReachableViaWWAN) {
|
||||
self.navigationController.navigationBar.barTintColor = nil;
|
||||
if (mainNavigationController) {
|
||||
mainNavigationController.navigationBar.barTintColor = nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Rage shake handling
|
||||
|
||||
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {
|
||||
if (motion == UIEventSubtypeMotionShake) {
|
||||
[RageShakableUIResponder startShaking:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event {
|
||||
[self motionEnded:motion withEvent:event];
|
||||
}
|
||||
|
||||
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
|
||||
if (motion == UIEventSubtypeMotionShake) {
|
||||
[RageShakableUIResponder stopShaking:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder {
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
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 <UIKit/UIKit.h>
|
||||
|
||||
#define TABBAR_HOME_INDEX 0
|
||||
#define TABBAR_RECENTS_INDEX 1
|
||||
#define TABBAR_SETTINGS_INDEX 2
|
||||
#define TABBAR_COUNT 3
|
||||
|
||||
@interface MasterTabBarController : UITabBarController
|
||||
|
||||
- (void)showAuthenticationScreen;
|
||||
- (void)showRoomCreationForm;
|
||||
- (void)showRoom:(NSString*)roomId;
|
||||
|
||||
- (void)popRoomViewControllerAnimated:(BOOL)animated;
|
||||
|
||||
- (BOOL)isPresentingMediaPicker;
|
||||
- (void)presentMediaPicker:(UIImagePickerController*)mediaPicker;
|
||||
- (void)dismissMediaPicker;
|
||||
|
||||
@property (strong, nonatomic) NSString *visibleRoomId; // nil if no room is presently visible
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
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 "MasterTabBarController.h"
|
||||
#import "MatrixSDKHandler.h"
|
||||
|
||||
#import "RecentsViewController.h"
|
||||
|
||||
@interface MasterTabBarController () {
|
||||
UINavigationController *recentsNavigationController;
|
||||
RecentsViewController *recentsViewController;
|
||||
|
||||
UIImagePickerController *mediaPicker;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MasterTabBarController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view, typically from a nib.
|
||||
|
||||
// To simplify navigation into the app, we retrieve here the navigation controller and the view controller related
|
||||
// to the recents list in Recents Tab.
|
||||
// Note: UISplitViewController is not supported on iPhone for iOS < 8.0
|
||||
UIViewController* recents = [self.viewControllers objectAtIndex:TABBAR_RECENTS_INDEX];
|
||||
recentsNavigationController = nil;
|
||||
if ([recents isKindOfClass:[UISplitViewController class]]) {
|
||||
UISplitViewController *splitViewController = (UISplitViewController *)recents;
|
||||
recentsNavigationController = [splitViewController.viewControllers objectAtIndex:0];
|
||||
} else if ([recents isKindOfClass:[UINavigationController class]]) {
|
||||
recentsNavigationController = (UINavigationController*)recents;
|
||||
}
|
||||
|
||||
if (recentsNavigationController) {
|
||||
for (UIViewController *viewController in recentsNavigationController.viewControllers) {
|
||||
if ([viewController isKindOfClass:[RecentsViewController class]]) {
|
||||
recentsViewController = (RecentsViewController*)viewController;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
if ([MatrixSDKHandler sharedHandler].status == MatrixSDKHandlerStatusLoggedOut) {
|
||||
[self showAuthenticationScreen];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning {
|
||||
[super didReceiveMemoryWarning];
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
recentsNavigationController = nil;
|
||||
recentsViewController = nil;
|
||||
|
||||
[self dismissMediaPicker];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)restoreInitialDisplay {
|
||||
// Dismiss potential media picker
|
||||
if (mediaPicker) {
|
||||
if (mediaPicker.delegate && [mediaPicker.delegate respondsToSelector:@selector(imagePickerControllerDidCancel:)]) {
|
||||
[mediaPicker.delegate imagePickerControllerDidCancel:mediaPicker];
|
||||
} else {
|
||||
[self dismissMediaPicker];
|
||||
}
|
||||
}
|
||||
|
||||
[self popRoomViewControllerAnimated:NO];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)showAuthenticationScreen {
|
||||
[self restoreInitialDisplay];
|
||||
|
||||
[self performSegueWithIdentifier:@"showAuth" sender:self];
|
||||
}
|
||||
|
||||
- (void)showRoomCreationForm {
|
||||
// Switch in Home Tab
|
||||
[self setSelectedIndex:TABBAR_HOME_INDEX];
|
||||
}
|
||||
|
||||
- (void)showRoom:(NSString*)roomId {
|
||||
[self restoreInitialDisplay];
|
||||
|
||||
// Switch on Recents Tab
|
||||
[self setSelectedIndex:TABBAR_RECENTS_INDEX];
|
||||
|
||||
// Select room to display its details (dispatch this action in order to let TabBarController end its refresh)
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
recentsViewController.preSelectedRoomId = roomId;
|
||||
});
|
||||
}
|
||||
|
||||
- (void)popRoomViewControllerAnimated:(BOOL)animated {
|
||||
// Force back to recents list if room details is displayed in Recents Tab
|
||||
if (recentsViewController) {
|
||||
[recentsNavigationController popToViewController:recentsViewController animated:animated];
|
||||
// Release the current selected room
|
||||
recentsViewController.preSelectedRoomId = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isPresentingMediaPicker {
|
||||
return nil != mediaPicker;
|
||||
}
|
||||
|
||||
- (void)presentMediaPicker:(UIImagePickerController*)aMediaPicker {
|
||||
[self dismissMediaPicker];
|
||||
[self presentViewController:aMediaPicker animated:YES completion:^{
|
||||
mediaPicker = aMediaPicker;
|
||||
}];
|
||||
}
|
||||
- (void)dismissMediaPicker {
|
||||
if (mediaPicker) {
|
||||
[self dismissViewControllerAnimated:NO completion:nil];
|
||||
mediaPicker.delegate = nil;
|
||||
mediaPicker = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setVisibleRoomId:(NSString *)aVisibleRoomId {
|
||||
[[MatrixSDKHandler sharedHandler] restoreInAppNotificationsForRoomId:aVisibleRoomId];
|
||||
_visibleRoomId = aVisibleRoomId;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
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 "MXCTableViewController.h"
|
||||
|
||||
#import "MatrixSDKHandler.h"
|
||||
|
||||
@interface MemberViewController : MXCTableViewController
|
||||
|
||||
@property (strong, nonatomic) MXRoomMember *mxRoomMember;
|
||||
@property (strong, nonatomic) MXRoom *mxRoom;
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,507 @@
|
||||
/*
|
||||
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 "MemberViewController.h"
|
||||
|
||||
#import "AppDelegate.h"
|
||||
#import "RoomMemberActionsCell.h"
|
||||
#import "MediaManager.h"
|
||||
|
||||
@interface MemberViewController () {
|
||||
NSString *thumbnailURL;
|
||||
MediaLoader* imageLoader;
|
||||
id membersListener;
|
||||
|
||||
NSMutableArray* buttonsTitles;
|
||||
|
||||
// mask view while processing a request
|
||||
UIView* pendingRequestMask;
|
||||
UIActivityIndicatorView * pendingMaskSpinnerView;
|
||||
}
|
||||
|
||||
// graphical objects
|
||||
@property (strong, nonatomic) IBOutlet UITableView *tableView;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *memberThumbnailButton;
|
||||
@property (weak, nonatomic) IBOutlet UITextView *roomMemberMID;
|
||||
|
||||
@property (strong, nonatomic) MXCAlert *actionMenu;
|
||||
|
||||
- (IBAction)onButtonToggle:(id)sender;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MemberViewController
|
||||
@synthesize mxRoom;
|
||||
|
||||
- (void)dealloc {
|
||||
// close any pending actionsheet
|
||||
if (self.actionMenu) {
|
||||
[self.actionMenu dismiss:NO];
|
||||
self.actionMenu = nil;
|
||||
}
|
||||
|
||||
// Remove any pending observers
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
if (imageLoader) {
|
||||
[imageLoader cancel];
|
||||
imageLoader = nil;
|
||||
}
|
||||
|
||||
if (membersListener) {
|
||||
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
|
||||
[mxHandler.mxSession removeListener:membersListener];
|
||||
membersListener = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
// remove the line separator color
|
||||
self.tableView.separatorColor = [UIColor clearColor];
|
||||
self.tableView.rowHeight = 44;
|
||||
self.tableView.allowsSelection = NO;
|
||||
|
||||
buttonsTitles = [[NSMutableArray alloc] init];
|
||||
|
||||
// ignore useless update
|
||||
if (_mxRoomMember) {
|
||||
[self updateMemberInfo];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
|
||||
|
||||
NSArray *mxMembersEvents = @[
|
||||
kMXEventTypeStringRoomMember,
|
||||
kMXEventTypeStringRoomPowerLevels
|
||||
];
|
||||
|
||||
// list on member updates
|
||||
membersListener = [mxHandler.mxSession listenToEventsOfTypes:mxMembersEvents 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:mxRoom.state.roomId] == NO) {
|
||||
// This event does not concern the current room members
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide potential action sheet
|
||||
if (self.actionMenu) {
|
||||
[self.actionMenu dismiss:NO];
|
||||
self.actionMenu = nil;
|
||||
}
|
||||
|
||||
MXRoomMember* nextRoomMember = nil;
|
||||
|
||||
// get the updated memmber
|
||||
NSArray* membersList = [self.mxRoom.state members];
|
||||
for (MXRoomMember* member in membersList) {
|
||||
if ([member.userId isEqual:_mxRoomMember.userId]) {
|
||||
nextRoomMember = member;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// does the member still exist ?
|
||||
if (nextRoomMember) {
|
||||
// Refresh members list
|
||||
_mxRoomMember = nextRoomMember;
|
||||
[self updateMemberInfo];
|
||||
[self.tableView reloadData];
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.navigationController popToRootViewControllerAnimated:NO];
|
||||
[[AppDelegate theDelegate].masterTabBarController setVisibleRoomId:nil];
|
||||
[[AppDelegate theDelegate].masterTabBarController popRoomViewControllerAnimated:YES];
|
||||
});
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
// Remove any pending observers
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
if (imageLoader) {
|
||||
[imageLoader cancel];
|
||||
imageLoader = nil;
|
||||
}
|
||||
|
||||
if (membersListener) {
|
||||
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
|
||||
[mxHandler.mxSession removeListener:membersListener];
|
||||
membersListener = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateMemberInfo {
|
||||
self.title = _mxRoomMember.displayname ? _mxRoomMember.displayname : _mxRoomMember.userId;
|
||||
|
||||
// set the thumbnail info
|
||||
[[self.memberThumbnailButton imageView] setContentMode: UIViewContentModeScaleAspectFill];
|
||||
[[self.memberThumbnailButton imageView] setClipsToBounds:YES];
|
||||
|
||||
// Remove any pending observers
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
|
||||
if (_mxRoomMember.avatarUrl) {
|
||||
// Suppose this url is a matrix content uri, we use SDK to get the well adapted thumbnail from server
|
||||
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
|
||||
thumbnailURL = [mxHandler thumbnailURLForContent:_mxRoomMember.avatarUrl inViewSize:self.memberThumbnailButton.frame.size withMethod:MXThumbnailingMethodCrop];
|
||||
|
||||
// Check whether the image download is in progress
|
||||
id loader = [MediaManager existingDownloaderForURL:thumbnailURL inFolder:kMediaManagerThumbnailFolder];
|
||||
if (loader) {
|
||||
// Add observers
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaDownloadEnd:) name:kMediaDownloadDidFinishNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaDownloadEnd:) name:kMediaDownloadDidFailNotification object:nil];
|
||||
} else {
|
||||
// Retrieve the image from cache
|
||||
UIImage* image = [MediaManager loadCachePictureForURL:thumbnailURL inFolder:kMediaManagerThumbnailFolder];
|
||||
if (image) {
|
||||
[self.memberThumbnailButton setImage:image forState:UIControlStateNormal];
|
||||
[self.memberThumbnailButton setImage:image forState:UIControlStateHighlighted];
|
||||
} else {
|
||||
// Cancel potential download in progress
|
||||
if (imageLoader) {
|
||||
[imageLoader cancel];
|
||||
}
|
||||
// Add observers
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaDownloadEnd:) name:kMediaDownloadDidFinishNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaDownloadEnd:) name:kMediaDownloadDidFailNotification object:nil];
|
||||
imageLoader = [MediaManager downloadMediaFromURL:thumbnailURL withType:@"image/jpeg" inFolder:kMediaManagerThumbnailFolder];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
UIImage *image = [UIImage imageNamed:@"default-profile"];
|
||||
if (image) {
|
||||
[self.memberThumbnailButton setImage:image forState:UIControlStateNormal];
|
||||
[self.memberThumbnailButton setImage:image forState:UIControlStateHighlighted];
|
||||
}
|
||||
}
|
||||
|
||||
self.roomMemberMID.text = _mxRoomMember.userId;
|
||||
}
|
||||
|
||||
- (void)onMediaDownloadEnd:(NSNotification *)notif {
|
||||
// sanity check
|
||||
if ([notif.object isKindOfClass:[NSString class]]) {
|
||||
NSString* url = notif.object;
|
||||
|
||||
if ([url isEqualToString:thumbnailURL]) {
|
||||
// update the image
|
||||
UIImage* image = [MediaManager loadCachePictureForURL:thumbnailURL inFolder:kMediaManagerThumbnailFolder];
|
||||
if (image == nil) {
|
||||
image = [UIImage imageNamed:@"default-profile"];
|
||||
}
|
||||
if (image) {
|
||||
[self.memberThumbnailButton setImage:image forState:UIControlStateNormal];
|
||||
[self.memberThumbnailButton setImage:image forState:UIControlStateHighlighted];
|
||||
}
|
||||
// remove the observers
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
imageLoader = nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setRoomMember:(MXRoomMember*) aRoomMember {
|
||||
// ignore useless update
|
||||
if (![_mxRoomMember.userId isEqualToString:aRoomMember.userId]) {
|
||||
_mxRoomMember = aRoomMember;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Table view data source
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
|
||||
|
||||
// Check user's power level before allowing an action (kick, ban, ...)
|
||||
MXRoomPowerLevels *powerLevels = [mxRoom.state powerLevels];
|
||||
NSUInteger memberPowerLevel = [powerLevels powerLevelOfUserWithUserID:_mxRoomMember.userId];
|
||||
NSUInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:mxHandler.userId];
|
||||
|
||||
[buttonsTitles removeAllObjects];
|
||||
|
||||
// Consider the case of the user himself
|
||||
if ([_mxRoomMember.userId isEqualToString:mxHandler.userId]) {
|
||||
|
||||
[buttonsTitles addObject:@"Leave"];
|
||||
|
||||
if (oneSelfPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomPowerLevels]) {
|
||||
[buttonsTitles addObject:@"Set Power Level"];
|
||||
}
|
||||
} else {
|
||||
// Consider membership of the selected member
|
||||
switch (_mxRoomMember.membership) {
|
||||
case MXMembershipInvite:
|
||||
case MXMembershipJoin: {
|
||||
// Check conditions to be able to kick someone
|
||||
if (oneSelfPowerLevel >= [powerLevels kick] && oneSelfPowerLevel >= memberPowerLevel) {
|
||||
[buttonsTitles addObject:@"Kick"];
|
||||
}
|
||||
// Check conditions to be able to ban someone
|
||||
if (oneSelfPowerLevel >= [powerLevels ban] && oneSelfPowerLevel >= memberPowerLevel) {
|
||||
[buttonsTitles addObject:@"Ban"];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MXMembershipLeave: {
|
||||
// Check conditions to be able to invite someone
|
||||
if (oneSelfPowerLevel >= [powerLevels invite]) {
|
||||
[buttonsTitles addObject:@"Invite"];
|
||||
}
|
||||
// Check conditions to be able to ban someone
|
||||
if (oneSelfPowerLevel >= [powerLevels ban] && oneSelfPowerLevel >= memberPowerLevel) {
|
||||
[buttonsTitles addObject:@"Ban"];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MXMembershipBan: {
|
||||
// Check conditions to be able to unban someone
|
||||
if (oneSelfPowerLevel >= [powerLevels ban] && oneSelfPowerLevel >= memberPowerLevel) {
|
||||
[buttonsTitles addObject:@"Unban"];
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// update power level
|
||||
if (oneSelfPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomPowerLevels]) {
|
||||
[buttonsTitles addObject:@"Set Power Level"];
|
||||
}
|
||||
|
||||
// offer to start a new chat only if the room is not a 1:1 room with this user
|
||||
// it does not make sense : it would open the same room
|
||||
NSString* roomId = [mxHandler privateOneToOneRoomIdWithUserId:_mxRoomMember.userId];
|
||||
if (![roomId isEqualToString:mxRoom.state.roomId]) {
|
||||
[buttonsTitles addObject:@"Start Chat"];
|
||||
}
|
||||
}
|
||||
|
||||
return (buttonsTitles.count + 1) / 2;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (self.tableView == aTableView) {
|
||||
NSInteger row = indexPath.row;
|
||||
RoomMemberActionsCell* memberActionsCell = (RoomMemberActionsCell*)[aTableView dequeueReusableCellWithIdentifier:@"MemberActionsCell" forIndexPath:indexPath];
|
||||
|
||||
NSString* leftTitle = nil;
|
||||
NSString* rightTitle = nil;
|
||||
|
||||
if ((row * 2) < buttonsTitles.count) {
|
||||
leftTitle = [buttonsTitles objectAtIndex:row * 2];
|
||||
}
|
||||
|
||||
if (((row * 2) + 1) < buttonsTitles.count) {
|
||||
rightTitle = [buttonsTitles objectAtIndex:(row * 2) + 1];
|
||||
}
|
||||
|
||||
[memberActionsCell setLeftButtonText:leftTitle];
|
||||
[memberActionsCell setRightButtonText:rightTitle];
|
||||
|
||||
return memberActionsCell;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - button management
|
||||
|
||||
- (BOOL)hasPendingAction {
|
||||
return nil != pendingMaskSpinnerView;
|
||||
}
|
||||
|
||||
- (void) addPendingActionMask {
|
||||
|
||||
// add a spinner above the tableview to avoid that the user tap on any other button
|
||||
pendingMaskSpinnerView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
|
||||
pendingMaskSpinnerView.backgroundColor = [UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:0.5];
|
||||
pendingMaskSpinnerView.frame = self.tableView.frame;
|
||||
pendingMaskSpinnerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin;
|
||||
|
||||
// append it
|
||||
[self.tableView.superview addSubview:pendingMaskSpinnerView];
|
||||
|
||||
// animate it
|
||||
[pendingMaskSpinnerView startAnimating];
|
||||
}
|
||||
|
||||
- (void) removePendingActionMask {
|
||||
if (pendingMaskSpinnerView) {
|
||||
[pendingMaskSpinnerView removeFromSuperview];
|
||||
pendingMaskSpinnerView = nil;
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) setUserPowerLevel:(MXRoomMember*)roomMember to:(int)value {
|
||||
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
|
||||
int currentPowerLevel = (int)([mxHandler getPowerLevel:roomMember inRoom:self.mxRoom] * 100);
|
||||
|
||||
// check if the power level has not yet been set to 0
|
||||
if (value != currentPowerLevel) {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
|
||||
[weakSelf addPendingActionMask];
|
||||
|
||||
// Reset user power level
|
||||
[self.mxRoom setPowerLevelOfUserWithUserID:roomMember.userId powerLevel:value success:^{
|
||||
[weakSelf removePendingActionMask];
|
||||
} failure:^(NSError *error) {
|
||||
[weakSelf removePendingActionMask];
|
||||
NSLog(@"[MemberVC] Set user power (%@) failed: %@", roomMember.userId, error);
|
||||
//Alert user
|
||||
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) updateUserPowerLevel:(MXRoomMember*)roomMember {
|
||||
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
|
||||
// Ask for userId to invite
|
||||
self.actionMenu = [[MXCAlert alloc] initWithTitle:@"Power Level" message:nil style:MXCAlertStyleAlert];
|
||||
|
||||
if (![mxHandler.userId isEqualToString:roomMember.userId]) {
|
||||
self.actionMenu.cancelButtonIndex = [self.actionMenu addActionWithTitle:@"Reset to default" style:MXCAlertActionStyleDefault handler:^(MXCAlert *alert) {
|
||||
weakSelf.actionMenu = nil;
|
||||
|
||||
[weakSelf setUserPowerLevel:roomMember to:0];
|
||||
}];
|
||||
}
|
||||
[self.actionMenu addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
textField.secureTextEntry = NO;
|
||||
textField.text = [NSString stringWithFormat:@"%d", (int)([mxHandler getPowerLevel:roomMember inRoom:weakSelf.mxRoom] * 100)];
|
||||
textField.placeholder = nil;
|
||||
textField.keyboardType = UIKeyboardTypeDecimalPad;
|
||||
}];
|
||||
[self.actionMenu addActionWithTitle:@"OK" style:MXCAlertActionStyleDefault handler:^(MXCAlert *alert) {
|
||||
UITextField *textField = [alert textFieldAtIndex:0];
|
||||
weakSelf.actionMenu = nil;
|
||||
|
||||
if (textField.text.length > 0) {
|
||||
[weakSelf setUserPowerLevel:roomMember to:(int)[textField.text integerValue]];
|
||||
}
|
||||
}];
|
||||
[self.actionMenu showInViewController:self];
|
||||
}
|
||||
|
||||
- (IBAction)onButtonToggle:(id)sender {
|
||||
if ([sender isKindOfClass:[UIButton class]]) {
|
||||
|
||||
// already a pending action
|
||||
if ([self hasPendingAction]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString* text = ((UIButton*)sender).titleLabel.text;
|
||||
|
||||
if ([text isEqualToString:@"Leave"]) {
|
||||
[self addPendingActionMask];
|
||||
[self.mxRoom leave:^{
|
||||
[self removePendingActionMask];
|
||||
[self.navigationController popToRootViewControllerAnimated:NO];
|
||||
[[AppDelegate theDelegate].masterTabBarController setVisibleRoomId:nil];
|
||||
[[AppDelegate theDelegate].masterTabBarController popRoomViewControllerAnimated:YES];
|
||||
} failure:^(NSError *error) {
|
||||
[self removePendingActionMask];
|
||||
NSLog(@"[MemberVC] Leave room %@ failed: %@", mxRoom.state.roomId, error);
|
||||
//Alert user
|
||||
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
||||
}];
|
||||
|
||||
} else if ([text isEqualToString:@"Set Power Level"]) {
|
||||
[self updateUserPowerLevel:_mxRoomMember];
|
||||
} else if ([text isEqualToString:@"Kick"]) {
|
||||
[self addPendingActionMask];
|
||||
[mxRoom kickUser:_mxRoomMember.userId
|
||||
reason:nil
|
||||
success:^{
|
||||
[self removePendingActionMask];
|
||||
[self.navigationController popToRootViewControllerAnimated:YES];
|
||||
}
|
||||
failure:^(NSError *error) {
|
||||
[self removePendingActionMask];
|
||||
NSLog(@"[MemberVC] Kick %@ failed: %@", _mxRoomMember.userId, error);
|
||||
//Alert user
|
||||
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
||||
}];
|
||||
} else if ([text isEqualToString:@"Ban"]) {
|
||||
[self addPendingActionMask];
|
||||
[mxRoom banUser:_mxRoomMember.userId
|
||||
reason:nil
|
||||
success:^{
|
||||
[self removePendingActionMask];
|
||||
}
|
||||
failure:^(NSError *error) {
|
||||
[self removePendingActionMask];
|
||||
NSLog(@"[MemberVC] Ban %@ failed: %@", _mxRoomMember.userId, error);
|
||||
//Alert user
|
||||
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
||||
}];
|
||||
} else if ([text isEqualToString:@"Invite"]) {
|
||||
[self addPendingActionMask];
|
||||
[mxRoom inviteUser:_mxRoomMember.userId
|
||||
success:^{
|
||||
[self removePendingActionMask];
|
||||
}
|
||||
failure:^(NSError *error) {
|
||||
[self removePendingActionMask];
|
||||
NSLog(@"[MemberVC] Invite %@ failed: %@", _mxRoomMember.userId, error);
|
||||
//Alert user
|
||||
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
||||
}];
|
||||
} else if ([text isEqualToString:@"Unban"]) {
|
||||
[self addPendingActionMask];
|
||||
[mxRoom unbanUser:_mxRoomMember.userId
|
||||
success:^{
|
||||
[self removePendingActionMask];
|
||||
}
|
||||
failure:^(NSError *error) {
|
||||
[self removePendingActionMask];
|
||||
NSLog(@"[MemberVC] Unban %@ failed: %@", _mxRoomMember.userId, error);
|
||||
//Alert user
|
||||
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
||||
}];
|
||||
|
||||
} else if ([text isEqualToString:@"Start Chat"]) {
|
||||
[self addPendingActionMask];
|
||||
[[MatrixSDKHandler sharedHandler] startPrivateOneToOneRoomWithUserId:_mxRoomMember.userId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
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 "MXCTableViewController.h"
|
||||
|
||||
@class RoomViewController;
|
||||
|
||||
@interface RecentsViewController : MXCTableViewController <UISearchBarDelegate>
|
||||
|
||||
@property (strong, nonatomic) NSString *preSelectedRoomId; // set a non-nil value to this property will open room details
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,757 @@
|
||||
/*
|
||||
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 "RecentsViewController.h"
|
||||
#import "RoomViewController.h"
|
||||
|
||||
#import "RecentRoom.h"
|
||||
#import "RecentsTableViewCell.h"
|
||||
|
||||
#import "AppDelegate.h"
|
||||
#import "MatrixSDKHandler.h"
|
||||
|
||||
#import "MediaManager.h"
|
||||
|
||||
@interface RecentsViewController () {
|
||||
// Array of RecentRooms
|
||||
NSMutableArray *recents;
|
||||
id recentsListener;
|
||||
NSUInteger unreadCount;
|
||||
|
||||
// Search
|
||||
UISearchBar *recentsSearchBar;
|
||||
NSMutableArray *filteredRecents;
|
||||
BOOL searchBarShouldEndEditing;
|
||||
|
||||
// Date formatter
|
||||
NSDateFormatter *dateFormatter;
|
||||
|
||||
// Keep reference on the current room view controller to release it correctly
|
||||
RoomViewController *currentRoomViewController;
|
||||
|
||||
// Keep the selected cell index to handle correctly split view controller display in landscape mode
|
||||
NSInteger currentSelectedCellIndexPathRow;
|
||||
|
||||
// The activity indicator is displayed on main screen in order to ignore potential table scrolling
|
||||
// In some case this activity indicator shoud be hidden (For example when the recents view controller is not visible).
|
||||
BOOL shouldHideActivityIndicator;
|
||||
}
|
||||
@property (strong, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RecentsViewController
|
||||
|
||||
- (void)awakeFromNib {
|
||||
[super awakeFromNib];
|
||||
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
|
||||
self.preferredContentSize = CGSizeMake(320.0, 600.0);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view, typically from a nib.
|
||||
self.navigationItem.leftBarButtonItem = self.editButtonItem;
|
||||
|
||||
UIBarButtonItem *searchButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@selector(search:)];
|
||||
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(createNewRoom:)];
|
||||
self.navigationItem.rightBarButtonItems = @[searchButton, addButton];
|
||||
|
||||
// Add background to activity indicator
|
||||
CGRect frame = _activityIndicator.frame;
|
||||
frame.size.width += 30;
|
||||
frame.size.height += 30;
|
||||
_activityIndicator.bounds = frame;
|
||||
_activityIndicator.backgroundColor = [UIColor darkGrayColor];
|
||||
[_activityIndicator.layer setCornerRadius:5];
|
||||
|
||||
// Initialisation
|
||||
recents = nil;
|
||||
filteredRecents = nil;
|
||||
unreadCount = 0;
|
||||
currentSelectedCellIndexPathRow = -1;
|
||||
|
||||
NSString *dateFormat = @"MMM dd HH:mm";
|
||||
dateFormatter = [[NSDateFormatter alloc] init];
|
||||
[dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:[[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0]]];
|
||||
[dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
|
||||
[dateFormatter setTimeStyle:NSDateFormatterNoStyle];
|
||||
[dateFormatter setDateFormat:dateFormat];
|
||||
|
||||
[[MatrixSDKHandler sharedHandler] addObserver:self forKeyPath:@"status" options:0 context:nil];
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (currentRoomViewController) {
|
||||
currentRoomViewController.roomId = nil;
|
||||
currentRoomViewController = nil;
|
||||
}
|
||||
if (recentsListener) {
|
||||
[[MatrixSDKHandler sharedHandler].mxSession removeListener:recentsListener];
|
||||
recentsListener = nil;
|
||||
}
|
||||
recents = nil;
|
||||
_preSelectedRoomId = nil;
|
||||
recentsSearchBar = nil;
|
||||
filteredRecents = nil;
|
||||
|
||||
if (dateFormatter) {
|
||||
dateFormatter = nil;
|
||||
}
|
||||
[[MatrixSDKHandler sharedHandler] removeObserver:self forKeyPath:@"status"];
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning {
|
||||
[super didReceiveMemoryWarning];
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
|
||||
[mxHandler addObserver:self forKeyPath:@"isActivityInProgress" options:0 context:nil];
|
||||
|
||||
// Refresh display
|
||||
shouldHideActivityIndicator = NO;
|
||||
if (mxHandler.isActivityInProgress) {
|
||||
[self startActivityIndicator];
|
||||
}
|
||||
[self configureView];
|
||||
|
||||
if (self.splitViewController) {
|
||||
// Deselect the current selected row, it will be restored on viewDidAppear (if any)
|
||||
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
|
||||
if (indexPath) {
|
||||
[self.tableView deselectRowAtIndexPath:indexPath animated:NO];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
[[MatrixSDKHandler sharedHandler] removeObserver:self forKeyPath:@"isActivityInProgress"];
|
||||
|
||||
// Leave potential editing mode
|
||||
[self setEditing:NO];
|
||||
// Leave potential search session
|
||||
if (recentsSearchBar) {
|
||||
[self searchBarCancelButtonClicked:recentsSearchBar];
|
||||
}
|
||||
// Hide activity indicator
|
||||
[self stopActivityIndicator];
|
||||
shouldHideActivityIndicator = YES;
|
||||
|
||||
_preSelectedRoomId = nil;
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
// Release the current selected room (if any) except if the Room ViewController is still visible (see splitViewController.isCollapsed condition)
|
||||
if (!self.splitViewController || self.splitViewController.isCollapsed) {
|
||||
if (currentRoomViewController) {
|
||||
currentRoomViewController.roomId = nil;
|
||||
currentRoomViewController = nil;
|
||||
// Reset selected row index
|
||||
currentSelectedCellIndexPathRow = -1;
|
||||
}
|
||||
} else {
|
||||
// In case of split view controller where the primary and secondary view controllers are displayed side-by-side onscreen,
|
||||
// the selected room (if any) is highlighted.
|
||||
[self refreshCurrentSelectedCell:YES];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)setPreSelectedRoomId:(NSString *)roomId {
|
||||
_preSelectedRoomId = nil;
|
||||
|
||||
if (roomId) {
|
||||
// Check whether recents update is in progress
|
||||
if ([_activityIndicator isAnimating]) {
|
||||
// Postpone room details display
|
||||
_preSelectedRoomId = roomId;
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for the room index in recents list
|
||||
NSIndexPath *indexPath = nil;
|
||||
for (NSUInteger index = 0; index < recents.count; index++) {
|
||||
RecentRoom *recentRoom = [recents objectAtIndex:index];
|
||||
if ([roomId isEqualToString:recentRoom.roomId]) {
|
||||
indexPath = [NSIndexPath indexPathForRow:index inSection:0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (indexPath) {
|
||||
// Open details view
|
||||
[self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionMiddle];
|
||||
UITableViewCell *recentCell = [self.tableView cellForRowAtIndexPath:indexPath];
|
||||
[self performSegueWithIdentifier:@"showDetail" sender:recentCell];
|
||||
} else {
|
||||
NSLog(@"[RecentsVC] We are not able to open room (%@) because it does not appear in recents yet", roomId);
|
||||
// Postpone room details display. We run activity indicator until recents are updated (thanks to recents listener)
|
||||
_preSelectedRoomId = roomId;
|
||||
// Start activity indicator
|
||||
[self startActivityIndicator];
|
||||
}
|
||||
} else if (currentRoomViewController) {
|
||||
// Release the current selected room
|
||||
currentRoomViewController.roomId = nil;
|
||||
currentRoomViewController = nil;
|
||||
|
||||
// Force table refresh to deselect related cell
|
||||
[self refreshRecentsDisplay];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Internal methods
|
||||
|
||||
- (void)refreshRecentsDisplay {
|
||||
// Check whether the current selected room has not been left
|
||||
if (currentRoomViewController.roomId) {
|
||||
MXRoom *mxRoom = [[MatrixSDKHandler sharedHandler].mxSession roomWithRoomId:currentRoomViewController.roomId];
|
||||
if (mxRoom == nil || mxRoom.state.membership == MXMembershipLeave || mxRoom.state.membership == MXMembershipBan) {
|
||||
// release the room viewController
|
||||
currentRoomViewController.roomId = nil;
|
||||
currentRoomViewController = nil;
|
||||
}
|
||||
}
|
||||
|
||||
[self.tableView reloadData];
|
||||
|
||||
// In case of split view controller where the primary and secondary view controllers are displayed side-by-side onscreen,
|
||||
// the selected room (if any) is updated and kept visible.
|
||||
if (self.splitViewController && !self.splitViewController.isCollapsed) {
|
||||
[self refreshCurrentSelectedCell:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)configureView {
|
||||
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kRecentRoomUpdatedByBackPagination object:nil];
|
||||
|
||||
if (mxHandler.mxSession) {
|
||||
// Check matrix handler status
|
||||
if (mxHandler.status == MatrixSDKHandlerStatusStoreDataReady || mxHandler.status == MatrixSDKHandlerStatusInitialServerSyncInProgress) {
|
||||
// Server sync is not complete yet
|
||||
if (!recents) {
|
||||
// Retrieve recents from local storage (some data may not be up-to-date)
|
||||
NSArray *recentEvents = [NSMutableArray arrayWithArray:[mxHandler.mxSession recentsWithTypeIn:mxHandler.eventsFilterForMessages]];
|
||||
recents = [NSMutableArray arrayWithCapacity:recentEvents.count];
|
||||
for (MXEvent *mxEvent in recentEvents) {
|
||||
MXRoom *mxRoom = [mxHandler.mxSession roomWithRoomId:mxEvent.roomId];
|
||||
RecentRoom *recentRoom = [[RecentRoom alloc] initWithLastEvent:mxEvent andRoomState:mxRoom.state markAsUnread:NO];
|
||||
if (recentRoom) {
|
||||
[recents addObject:recentRoom];
|
||||
}
|
||||
}
|
||||
unreadCount = 0;
|
||||
}
|
||||
} else if (mxHandler.status == MatrixSDKHandlerStatusServerSyncDone) {
|
||||
// Force recents refresh and add listener to update them (if it is not already done)
|
||||
if (!recentsListener) {
|
||||
NSArray *recentEvents = [NSMutableArray arrayWithArray:[mxHandler.mxSession recentsWithTypeIn:mxHandler.eventsFilterForMessages]];
|
||||
recents = [NSMutableArray arrayWithCapacity:recentEvents.count];
|
||||
for (MXEvent *mxEvent in recentEvents) {
|
||||
MXRoom *mxRoom = [mxHandler.mxSession roomWithRoomId:mxEvent.roomId];
|
||||
RecentRoom *recentRoom = [[RecentRoom alloc] initWithLastEvent:mxEvent andRoomState:mxRoom.state markAsUnread:NO];
|
||||
if (recentRoom) {
|
||||
[recents addObject:recentRoom];
|
||||
}
|
||||
}
|
||||
unreadCount = 0;
|
||||
|
||||
// Check whether redaction event belongs to the listened events list
|
||||
NSArray *listenedEventTypes = mxHandler.eventsFilterForMessages;
|
||||
BOOL hideRedactionEvent = ([listenedEventTypes indexOfObject:kMXEventTypeStringRoomRedaction] == NSNotFound);
|
||||
if (hideRedactionEvent) {
|
||||
// Add redaction event to the listened events list in order to take into account redaction of the last event in recents.
|
||||
// (See [RecentRoom updateWithLastEvent:...] for more details)
|
||||
listenedEventTypes = [listenedEventTypes arrayByAddingObject:kMXEventTypeStringRoomRedaction];
|
||||
}
|
||||
// Register recent listener
|
||||
recentsListener = [mxHandler.mxSession listenToEventsOfTypes:listenedEventTypes onEvent:^(MXEvent *event, MXEventDirection direction, MXRoomState *roomState) {
|
||||
// Consider first live event
|
||||
if (direction == MXEventDirectionForwards) {
|
||||
// Check user's membership in live room state (We will remove left rooms from recents)
|
||||
MXRoom *mxRoom = [mxHandler.mxSession roomWithRoomId:event.roomId];
|
||||
BOOL isLeft = (mxRoom == nil || mxRoom.state.membership == MXMembershipLeave || mxRoom.state.membership == MXMembershipBan);
|
||||
|
||||
// Consider this new event as unread only if the sender is not the user and if the room is not visible
|
||||
BOOL isUnread = (![event.userId isEqualToString:mxHandler.userId]
|
||||
&& ![[AppDelegate theDelegate].masterTabBarController.visibleRoomId isEqualToString:event.roomId]);
|
||||
|
||||
// Look for the room
|
||||
BOOL isFound = NO;
|
||||
for (NSUInteger index = 0; index < recents.count; index++) {
|
||||
RecentRoom *recentRoom = [recents objectAtIndex:index];
|
||||
if ([event.roomId isEqualToString:recentRoom.roomId]) {
|
||||
isFound = YES;
|
||||
// Decrement here unreads count for this recent (we will add later the refreshed count)
|
||||
unreadCount -= recentRoom.unreadCount;
|
||||
|
||||
if (isLeft) {
|
||||
// Remove left room
|
||||
[recents removeObjectAtIndex:index];
|
||||
if (filteredRecents) {
|
||||
NSUInteger filteredIndex = [filteredRecents indexOfObject:recentRoom];
|
||||
if (filteredIndex != NSNotFound) {
|
||||
[filteredRecents removeObjectAtIndex:filteredIndex];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ([recentRoom updateWithLastEvent:event andRoomState:roomState markAsUnread:isUnread]) {
|
||||
if (index) {
|
||||
// Move this room at first position
|
||||
[recents removeObjectAtIndex:index];
|
||||
[recents insertObject:recentRoom atIndex:0];
|
||||
}
|
||||
// Update filtered recents (if any)
|
||||
if (filteredRecents) {
|
||||
NSUInteger filteredIndex = [filteredRecents indexOfObject:recentRoom];
|
||||
if (filteredIndex && filteredIndex != NSNotFound) {
|
||||
[filteredRecents removeObjectAtIndex:filteredIndex];
|
||||
[filteredRecents insertObject:recentRoom atIndex:0];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Refresh global unreads count
|
||||
unreadCount += recentRoom.unreadCount;
|
||||
}
|
||||
|
||||
// Refresh title
|
||||
[self updateTitleView];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isFound && !isLeft) {
|
||||
// Insert in first position this new room
|
||||
RecentRoom *recentRoom = [[RecentRoom alloc] initWithLastEvent:event andRoomState:roomState markAsUnread:isUnread];
|
||||
if (recentRoom) {
|
||||
[recents insertObject:recentRoom atIndex:0];
|
||||
if (isUnread) {
|
||||
unreadCount++;
|
||||
[self updateTitleView];
|
||||
}
|
||||
|
||||
// Check whether we were waiting for this room
|
||||
if (_preSelectedRoomId) {
|
||||
if ([recentRoom.roomId isEqualToString:_preSelectedRoomId]) {
|
||||
[self stopActivityIndicator];
|
||||
self.preSelectedRoomId = _preSelectedRoomId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reload table
|
||||
[self refreshRecentsDisplay];
|
||||
}
|
||||
}];
|
||||
}
|
||||
// else nothing to do
|
||||
} else if (mxHandler.status != MatrixSDKHandlerStatusPaused) {
|
||||
// Here status is MatrixSDKHandlerStatusLoggedOut or MatrixSDKHandlerStatusLogged - Reset recents
|
||||
recents = nil;
|
||||
}
|
||||
|
||||
// Reload table
|
||||
[self refreshRecentsDisplay];
|
||||
|
||||
// Check whether a room is preselected
|
||||
if (_preSelectedRoomId) {
|
||||
self.preSelectedRoomId = _preSelectedRoomId;
|
||||
}
|
||||
} else {
|
||||
if (mxHandler.status == MatrixSDKHandlerStatusLoggedOut) {
|
||||
// Update title
|
||||
unreadCount = 0;
|
||||
[self updateTitleView];
|
||||
}
|
||||
|
||||
recents = nil;
|
||||
[self refreshRecentsDisplay];
|
||||
}
|
||||
|
||||
if (recents) {
|
||||
// Add observer to force refresh when a recent last description is updated thanks to back pagination
|
||||
// (This happens when the current last event description is blank, a back pagination is triggered to display non empty description)
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onRecentRoomUpdatedByBackPagination:) name:kRecentRoomUpdatedByBackPagination object:nil];
|
||||
} else {
|
||||
// Remove potential listener
|
||||
if (recentsListener && mxHandler.mxSession) {
|
||||
[mxHandler.mxSession removeListener:recentsListener];
|
||||
recentsListener = nil;
|
||||
}
|
||||
}
|
||||
|
||||
[self updateTitleView];
|
||||
}
|
||||
|
||||
- (void)onRecentRoomUpdatedByBackPagination:(NSNotification *)notif{
|
||||
[self refreshRecentsDisplay];
|
||||
[self updateTitleView];
|
||||
|
||||
if ([notif.object isKindOfClass:[NSString class]]) {
|
||||
NSString* roomId = notif.object;
|
||||
// Check whether this room is currently displayed in RoomViewController
|
||||
if ([[AppDelegate theDelegate].masterTabBarController.visibleRoomId isEqualToString:roomId]) {
|
||||
// For sanity reason, we have to force a full refresh in order to restore back state of the room
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[currentRoomViewController forceRefresh];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateTitleView {
|
||||
NSString *title = @"Recents";
|
||||
if (unreadCount) {
|
||||
title = [NSString stringWithFormat:@"Recents (%tu)", unreadCount];
|
||||
}
|
||||
self.navigationItem.title = title;
|
||||
}
|
||||
|
||||
- (void)createNewRoom:(id)sender {
|
||||
[[AppDelegate theDelegate].masterTabBarController showRoomCreationForm];
|
||||
}
|
||||
|
||||
- (void)search:(id)sender {
|
||||
if (!recentsSearchBar) {
|
||||
// Check whether there are data in which search
|
||||
if (recents.count) {
|
||||
// Create search bar
|
||||
recentsSearchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 44)];
|
||||
recentsSearchBar.showsCancelButton = YES;
|
||||
recentsSearchBar.returnKeyType = UIReturnKeyDone;
|
||||
recentsSearchBar.delegate = self;
|
||||
searchBarShouldEndEditing = NO;
|
||||
// add it to the tableHeaderView
|
||||
// do not create a header view
|
||||
// the header view is refreshed every time there is a [tableView reloaddata]
|
||||
// i.e. there is a removeFromSuperView call, the view is added to the tableview..
|
||||
// with a first respondable view, IOS seems lost to find the first responder
|
||||
// so, the keyboard is always displayed and can not be dismissed
|
||||
// tableHeaderView is never removed from superview so the first responder is not lost
|
||||
self.tableView.tableHeaderView = recentsSearchBar;
|
||||
|
||||
[recentsSearchBar becomeFirstResponder];
|
||||
|
||||
[self scrollToTop];
|
||||
}
|
||||
} else {
|
||||
[self searchBarCancelButtonClicked: recentsSearchBar];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)startActivityIndicator {
|
||||
// Add the spinner on main screen in order to ignore potential table scrolling
|
||||
_activityIndicator.center = CGPointMake(self.view.center.x, self.view.center.x);
|
||||
[[AppDelegate theDelegate].window addSubview:_activityIndicator];
|
||||
[_activityIndicator startAnimating];
|
||||
}
|
||||
|
||||
- (void)stopActivityIndicator {
|
||||
[_activityIndicator stopAnimating];
|
||||
[_activityIndicator removeFromSuperview];
|
||||
}
|
||||
|
||||
- (void)scrollToTop {
|
||||
// stop any scrolling effect
|
||||
[UIView setAnimationsEnabled:NO];
|
||||
// before scrolling to the tableview top
|
||||
self.tableView.contentOffset = CGPointMake(-self.tableView.contentInset.left, -self.tableView.contentInset.top);
|
||||
[UIView setAnimationsEnabled:YES];
|
||||
}
|
||||
|
||||
- (void)refreshCurrentSelectedCell:(BOOL)forceVisible {
|
||||
// Update here the index of the current selected cell (if any) - Useful in landscape mode with split view controller.
|
||||
currentSelectedCellIndexPathRow = -1;
|
||||
if (currentRoomViewController) {
|
||||
// Look for the rank of this selected room in displayed recents
|
||||
NSArray *displayedRecents = filteredRecents ? filteredRecents : recents;
|
||||
for (NSInteger index = 0; index < displayedRecents.count; index ++) {
|
||||
RecentRoom *recentRoom = [displayedRecents objectAtIndex:index];
|
||||
if ([currentRoomViewController.roomId isEqualToString:recentRoom.roomId]) {
|
||||
currentSelectedCellIndexPathRow = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentSelectedCellIndexPathRow != -1) {
|
||||
// Select the right row
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:currentSelectedCellIndexPathRow inSection:0];
|
||||
[self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
|
||||
|
||||
if (forceVisible) {
|
||||
// Scroll table view to make the selected row appear at second position
|
||||
NSInteger topCellIndexPathRow = currentSelectedCellIndexPathRow ? currentSelectedCellIndexPathRow - 1: currentSelectedCellIndexPathRow;
|
||||
indexPath = [NSIndexPath indexPathForRow:topCellIndexPathRow inSection:0];
|
||||
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
|
||||
}
|
||||
} else {
|
||||
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
|
||||
if (indexPath) {
|
||||
[self.tableView deselectRowAtIndexPath:indexPath animated:NO];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - KVO
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
|
||||
if ([@"status" isEqualToString:keyPath]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self configureView];
|
||||
});
|
||||
} else if ([@"isActivityInProgress" isEqualToString:keyPath]) {
|
||||
if (!shouldHideActivityIndicator && [MatrixSDKHandler sharedHandler].isActivityInProgress) {
|
||||
[self startActivityIndicator];
|
||||
} else {
|
||||
[self stopActivityIndicator];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Segues
|
||||
|
||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||
if ([[segue identifier] isEqualToString:@"showDetail"]) {
|
||||
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
|
||||
RecentRoom *recentRoom;
|
||||
if (filteredRecents) {
|
||||
recentRoom = filteredRecents[indexPath.row];
|
||||
} else {
|
||||
recentRoom = recents[indexPath.row];
|
||||
}
|
||||
|
||||
UIViewController *controller;
|
||||
if ([[segue destinationViewController] isKindOfClass:[UINavigationController class]]) {
|
||||
controller = [[segue destinationViewController] topViewController];
|
||||
} else {
|
||||
controller = [segue destinationViewController];
|
||||
}
|
||||
|
||||
if ([controller isKindOfClass:[RoomViewController class]]) {
|
||||
// Release potential Room ViewController
|
||||
if (currentRoomViewController) {
|
||||
currentRoomViewController.roomId = nil;
|
||||
currentRoomViewController = nil;
|
||||
}
|
||||
currentRoomViewController = (RoomViewController *)controller;
|
||||
currentRoomViewController.roomId = recentRoom.roomId;
|
||||
}
|
||||
|
||||
// Reset unread count for this room
|
||||
unreadCount -= recentRoom.unreadCount;
|
||||
[recentRoom resetUnreadCount];
|
||||
[self updateTitleView];
|
||||
|
||||
if (self.splitViewController) {
|
||||
// Refresh selected cell without scrolling the selected cell (We suppose it's visible here)
|
||||
[self refreshCurrentSelectedCell:NO];
|
||||
|
||||
// IOS >= 8
|
||||
if ([self.splitViewController respondsToSelector:@selector(displayModeButtonItem)]) {
|
||||
controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
|
||||
}
|
||||
|
||||
// hide the keyboard when opening a new controller
|
||||
// do not hide the searchBar until the RecentsViewController is dismissed
|
||||
// on tablets / iphone 6+, the user could expect to search again while looking at a room
|
||||
if ([recentsSearchBar isFirstResponder]) {
|
||||
searchBarShouldEndEditing = YES;
|
||||
[recentsSearchBar resignFirstResponder];
|
||||
}
|
||||
|
||||
//
|
||||
controller.navigationItem.leftItemsSupplementBackButton = YES;
|
||||
}
|
||||
|
||||
// Hide back button title
|
||||
self.navigationItem.backBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Table View
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
if (filteredRecents) {
|
||||
return filteredRecents.count;
|
||||
}
|
||||
return recents.count;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return 70;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
RecentsTableViewCell *cell = (RecentsTableViewCell*)[tableView dequeueReusableCellWithIdentifier:@"RecentsCell" forIndexPath:indexPath];
|
||||
|
||||
RecentRoom *recentRoom;
|
||||
if (filteredRecents) {
|
||||
recentRoom = filteredRecents[indexPath.row];
|
||||
} else {
|
||||
recentRoom = recents[indexPath.row];
|
||||
}
|
||||
|
||||
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
|
||||
MXRoom *mxRoom = [mxHandler.mxSession roomWithRoomId:recentRoom.roomId];
|
||||
|
||||
cell.roomTitle.text = [mxRoom.state displayname];
|
||||
cell.lastEventDescription.text = recentRoom.lastEventDescription;
|
||||
|
||||
// Set in bold public room name
|
||||
if (mxRoom.state.isPublic) {
|
||||
cell.roomTitle.font = [UIFont boldSystemFontOfSize:20];
|
||||
} else {
|
||||
cell.roomTitle.font = [UIFont systemFontOfSize:19];
|
||||
}
|
||||
|
||||
if (recentRoom.lastEventOriginServerTs != kMXUndefinedTimestamp) {
|
||||
NSDate *date = [NSDate dateWithTimeIntervalSince1970:recentRoom.lastEventOriginServerTs/1000];
|
||||
cell.recentDate.text = [dateFormatter stringFromDate:date];
|
||||
} else {
|
||||
cell.recentDate.text = nil;
|
||||
}
|
||||
|
||||
// Set background color
|
||||
if (recentRoom.unreadCount) {
|
||||
if (recentRoom.containsBingUnread) {
|
||||
cell.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:1 alpha:1.0];
|
||||
} else {
|
||||
cell.backgroundColor = [UIColor colorWithRed:1 green:0.9 blue:0.9 alpha:1.0];
|
||||
}
|
||||
cell.roomTitle.text = [NSString stringWithFormat:@"%@ (%tu)", cell.roomTitle.text, recentRoom.unreadCount];
|
||||
} else {
|
||||
cell.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
// Return NO if you do not want the specified item to be editable.
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
if (editingStyle == UITableViewCellEditingStyleDelete) {
|
||||
// Leave the selected room
|
||||
RecentRoom *selectedRoom;
|
||||
if (filteredRecents) {
|
||||
selectedRoom = filteredRecents[indexPath.row];
|
||||
} else {
|
||||
selectedRoom = recents[indexPath.row];
|
||||
}
|
||||
|
||||
MXRoom *mxRoom = [[MatrixSDKHandler sharedHandler].mxSession roomWithRoomId:selectedRoom.roomId];
|
||||
|
||||
// cancel pending uploads/downloads
|
||||
// they are useless by now
|
||||
[MediaManager cancelDownloadsInFolder:selectedRoom.roomId];
|
||||
[MediaManager cancelUploadsInFolder:selectedRoom.roomId];
|
||||
|
||||
[mxRoom leave:^{
|
||||
// Remove the selected room (if it is not already done by recents listener)
|
||||
for (NSUInteger index = 0; index < recents.count; index++) {
|
||||
RecentRoom *recentRoom = [recents objectAtIndex:index];
|
||||
if ([recentRoom.roomId isEqualToString:selectedRoom.roomId]) {
|
||||
[recents removeObjectAtIndex:index];
|
||||
if (filteredRecents) {
|
||||
NSUInteger filteredIndex = [filteredRecents indexOfObject:selectedRoom];
|
||||
if (filteredIndex != NSNotFound) {
|
||||
[filteredRecents removeObjectAtIndex:filteredIndex];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Refresh table display
|
||||
[self refreshRecentsDisplay];
|
||||
} failure:^(NSError *error) {
|
||||
NSLog(@"[RecentsVC] Failed to leave room (%@) failed: %@", selectedRoom.roomId, error);
|
||||
//Alert user
|
||||
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UISearchBarDelegate
|
||||
|
||||
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
|
||||
searchBarShouldEndEditing = NO;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar {
|
||||
return searchBarShouldEndEditing;
|
||||
}
|
||||
|
||||
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
|
||||
// Update filtered list
|
||||
if (searchText.length) {
|
||||
if (filteredRecents) {
|
||||
[filteredRecents removeAllObjects];
|
||||
} else {
|
||||
filteredRecents = [NSMutableArray arrayWithCapacity:recents.count];
|
||||
}
|
||||
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
|
||||
for (RecentRoom *recentRoom in recents) {
|
||||
MXRoom *mxRoom = [mxHandler.mxSession roomWithRoomId:recentRoom.roomId];
|
||||
if ([[mxRoom.state displayname] rangeOfString:searchText options:NSCaseInsensitiveSearch].location != NSNotFound) {
|
||||
[filteredRecents addObject:recentRoom];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filteredRecents = nil;
|
||||
}
|
||||
// Refresh display
|
||||
[self refreshRecentsDisplay];
|
||||
[self scrollToTop];
|
||||
}
|
||||
|
||||
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
|
||||
// "Done" key has been pressed
|
||||
searchBarShouldEndEditing = YES;
|
||||
[searchBar resignFirstResponder];
|
||||
}
|
||||
|
||||
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
|
||||
// Leave search
|
||||
searchBarShouldEndEditing = YES;
|
||||
[searchBar resignFirstResponder];
|
||||
recentsSearchBar = nil;
|
||||
filteredRecents = nil;
|
||||
self.tableView.tableHeaderView = nil;
|
||||
[self refreshRecentsDisplay];
|
||||
[self scrollToTop];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
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 "MXCViewController.h"
|
||||
|
||||
#import "HPGrowingTextView.h"
|
||||
|
||||
@interface RoomViewController : MXCViewController <UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIGestureRecognizerDelegate, HPGrowingTextViewDelegate>
|
||||
|
||||
@property (strong, nonatomic) NSString *roomId;
|
||||
|
||||
- (void)forceRefresh;
|
||||
|
||||
@end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
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 "MXCTableViewController.h"
|
||||
|
||||
@interface SettingsViewController : MXCTableViewController <UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIPickerViewDataSource, UIPickerViewDelegate>
|
||||
|
||||
typedef void (^blockSettings_onReadyToLeave)();
|
||||
|
||||
- (void)reset;
|
||||
- (BOOL)shouldLeave:(blockSettings_onReadyToLeave)handler;
|
||||
|
||||
@end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user