/* Copyright 2018-2024 New Vector Ltd. Copyright 2017 Vector Creations Ltd Copyright 2015 OpenMarket Ltd SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ #import "MXKAccountDetailsViewController.h" @import MatrixSDK; #import "MXK3PID.h" #import "MXKTools.h" #import "MXKTableViewCellWithButton.h" #import "MXKTableViewCellWithTextFieldAndButton.h" #import "MXKTableViewCellWithLabelTextFieldAndButton.h" #import "MXKTableViewCellWithTextView.h" #import "MXKTableViewCellWithLabelAndSwitch.h" #import "NSBundle+MatrixKit.h" #import "MXKConstants.h" #import "MXKSwiftHeader.h" NSString* const kMXKAccountDetailsLinkedEmailCellId = @"kMXKAccountDetailsLinkedEmailCellId"; @interface MXKAccountDetailsViewController () { NSMutableArray *alertsArray; // User's profile MXMediaLoader *imageLoader; NSString *currentDisplayName; NSString *currentPictureURL; NSString *currentDownloadId; NSString *uploadedPictureURL; // Local changes BOOL isAvatarUpdated; BOOL isSavingInProgress; blockMXKAccountDetailsViewController_onReadyToLeave onReadyToLeaveHandler; // account user's profile observer id accountUserInfoObserver; // Dynamic rows in the Linked emails section NSInteger submittedEmailRowIndex; // Notifications // Dynamic rows in the Notifications section NSInteger enablePushNotifRowIndex; NSInteger enableInAppNotifRowIndex; UIImagePickerController *mediaPicker; } @end @implementation MXKAccountDetailsViewController @synthesize userPictureButton, userDisplayName, saveUserInfoButton; @synthesize profileActivityIndicator, profileActivityIndicatorBgView; #pragma mark - Class methods + (UINib *)nib { return [UINib nibWithNibName:NSStringFromClass([MXKAccountDetailsViewController class]) bundle:[NSBundle bundleForClass:[MXKAccountDetailsViewController class]]]; } + (instancetype)accountDetailsViewController { return [[[self class] alloc] initWithNibName:NSStringFromClass([MXKAccountDetailsViewController class]) bundle:[NSBundle bundleForClass:[MXKAccountDetailsViewController class]]]; } #pragma mark - - (void)finalizeInit { [super finalizeInit]; alertsArray = [NSMutableArray array]; isAvatarUpdated = NO; isSavingInProgress = NO; } - (void)viewDidLoad { [super viewDidLoad]; // Check whether the view controller has been pushed via storyboard if (!userPictureButton) { // Instantiate view controller objects [[[self class] nib] instantiateWithOwner:self options:nil]; } self.userPictureButton.backgroundColor = [UIColor clearColor]; [self updateUserPictureButton:self.picturePlaceholder]; [userPictureButton.layer setCornerRadius:userPictureButton.frame.size.width / 2]; userPictureButton.clipsToBounds = YES; [saveUserInfoButton setTitle:[VectorL10n accountSaveChanges] forState:UIControlStateNormal]; [saveUserInfoButton setTitle:[VectorL10n accountSaveChanges] forState:UIControlStateHighlighted]; // Force refresh self.mxAccount = _mxAccount; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. if (imageLoader) { [imageLoader cancel]; imageLoader = nil; } } - (void)dealloc { alertsArray = nil; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAPNSStatusUpdate) name:kMXKAccountAPNSActivityDidChangeNotification object:nil]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [self stopProfileActivityIndicator]; [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXKAccountAPNSActivityDidChangeNotification object:nil]; } #pragma mark - override - (void)onMatrixSessionChange { [super onMatrixSessionChange]; if (self.mainSession.state != MXSessionStateRunning) { userPictureButton.enabled = NO; userDisplayName.enabled = NO; } else if (!isSavingInProgress) { userPictureButton.enabled = YES; userDisplayName.enabled = YES; } } #pragma mark - - (void)setMxAccount:(MXKAccount *)account { // Remove observer and existing data [self reset]; _mxAccount = account; if (account) { // Report matrix account session [self addMatrixSession:account.mxSession]; // Set current user's information and add observers [self updateUserPicture:_mxAccount.userAvatarUrl force:YES]; currentDisplayName = _mxAccount.userDisplayName; self.userDisplayName.text = currentDisplayName; [self updateSaveUserInfoButtonStatus]; // Load linked emails [self loadLinkedEmails]; // Add observer on user's information accountUserInfoObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountUserInfoDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { // Ignore any refresh when saving is in progress if (self->isSavingInProgress) { return; } NSString *accountUserId = notif.object; if ([accountUserId isEqualToString:self->_mxAccount.mxCredentials.userId]) { // Update displayName if (![self->currentDisplayName isEqualToString:self->_mxAccount.userDisplayName]) { self->currentDisplayName = self->_mxAccount.userDisplayName; self.userDisplayName.text = self->_mxAccount.userDisplayName; } // Update user's avatar [self updateUserPicture:self->_mxAccount.userAvatarUrl force:NO]; // Update button management [self updateSaveUserInfoButtonStatus]; // Display user's presence UIColor *presenceColor = [MXKAccount presenceColor:MXPresenceOffline]; if (presenceColor) { self->userPictureButton.layer.borderWidth = 2; self->userPictureButton.layer.borderColor = presenceColor.CGColor; } else { self->userPictureButton.layer.borderWidth = 0; } } }]; } [self.tableView reloadData]; } - (UIImage*)picturePlaceholder { return [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"default-profile"]; } - (BOOL)shouldLeave:(blockMXKAccountDetailsViewController_onReadyToLeave)handler { // Check whether some local changes have not been saved if (saveUserInfoButton.enabled) { dispatch_async(dispatch_get_main_queue(), ^{ UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:[VectorL10n messageUnsavedChanges] preferredStyle:UIAlertControllerStyleAlert]; [self->alertsArray addObject:alert]; [alert addAction:[UIAlertAction actionWithTitle:[VectorL10n discard] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { [self->alertsArray removeObject:alert]; // Discard changes self.userDisplayName.text = self->currentDisplayName; [self updateUserPicture:self->_mxAccount.userAvatarUrl force:YES]; // Ready to leave if (handler) { handler(); } }]]; [alert addAction:[UIAlertAction actionWithTitle:[VectorL10n save] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { [self->alertsArray removeObject:alert]; // Start saving (Report handler to leave at the end). self->onReadyToLeaveHandler = handler; [self saveUserInfo]; }]]; [self presentViewController:alert animated:YES completion:nil]; }); return NO; } else if (isSavingInProgress) { // Report handler to leave at the end of saving onReadyToLeaveHandler = handler; return NO; } return YES; } #pragma mark - Internal methods - (void)startProfileActivityIndicator { if (profileActivityIndicatorBgView.hidden) { profileActivityIndicatorBgView.hidden = NO; [profileActivityIndicator startAnimating]; } userPictureButton.enabled = NO; userDisplayName.enabled = NO; saveUserInfoButton.enabled = NO; } - (void)stopProfileActivityIndicator { if (!isSavingInProgress) { if (!profileActivityIndicatorBgView.hidden) { profileActivityIndicatorBgView.hidden = YES; [profileActivityIndicator stopAnimating]; } userPictureButton.enabled = YES; userDisplayName.enabled = YES; [self updateSaveUserInfoButtonStatus]; } } - (void)reset { [self dismissMediaPicker]; // Remove observers [[NSNotificationCenter defaultCenter] removeObserver:self]; // Cancel picture loader (if any) if (imageLoader) { [imageLoader cancel]; imageLoader = nil; } // Cancel potential alerts for (UIAlertController *alert in alertsArray) { [alert dismissViewControllerAnimated:NO completion:nil]; } // Remove listener if (accountUserInfoObserver) { [[NSNotificationCenter defaultCenter] removeObserver:accountUserInfoObserver]; accountUserInfoObserver = nil; } currentPictureURL = nil; currentDownloadId = nil; uploadedPictureURL = nil; isAvatarUpdated = NO; [self updateUserPictureButton:self.picturePlaceholder]; currentDisplayName = nil; self.userDisplayName.text = nil; saveUserInfoButton.enabled = NO; submittedEmail = nil; emailSubmitButton = nil; emailTextField = nil; [self removeMatrixSession:self.mainSession]; logoutButton = nil; onReadyToLeaveHandler = nil; } - (void)destroy { if (isSavingInProgress) { __weak typeof(self) weakSelf = self; onReadyToLeaveHandler = ^() { __strong __typeof(weakSelf)strongSelf = weakSelf; [strongSelf destroy]; }; } else { // Reset account to dispose all resources (Discard here potentials changes) self.mxAccount = nil; if (imageLoader) { [imageLoader cancel]; imageLoader = nil; } // Remove listener if (accountUserInfoObserver) { [[NSNotificationCenter defaultCenter] removeObserver:accountUserInfoObserver]; accountUserInfoObserver = nil; } [super destroy]; } } - (void)saveUserInfo { [self startProfileActivityIndicator]; isSavingInProgress = YES; // Check whether the display name has been changed NSString *displayname = self.userDisplayName.text; if ((displayname.length || currentDisplayName.length) && [displayname isEqualToString:currentDisplayName] == NO) { // Save display name __weak typeof(self) weakSelf = self; [_mxAccount setUserDisplayName:displayname success:^{ if (weakSelf) { // Update the current displayname typeof(self) self = weakSelf; self->currentDisplayName = displayname; // Go to the next change saving step [self saveUserInfo]; } } failure:^(NSError *error) { MXLogDebug(@"[MXKAccountDetailsVC] Failed to set displayName"); if (weakSelf) { typeof(self) self = weakSelf; // Alert user NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey]; if (!title) { title = [VectorL10n accountErrorDisplayNameChangeFailed]; } NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey]; UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert]; [self->alertsArray addObject:alert]; [alert addAction:[UIAlertAction actionWithTitle:[VectorL10n abort] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { [self->alertsArray removeObject:alert]; // Discard changes self.userDisplayName.text = self->currentDisplayName; [self updateUserPicture:self.mxAccount.userAvatarUrl force:YES]; // Loop to end saving [self saveUserInfo]; }]]; [alert addAction:[UIAlertAction actionWithTitle:[VectorL10n retry] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { [self->alertsArray removeObject:alert]; // Loop to retry saving [self saveUserInfo]; }]]; [self presentViewController:alert animated:YES completion:nil]; } }]; return; } // Check whether avatar has been updated if (isAvatarUpdated) { if (uploadedPictureURL == nil) { // Retrieve the current picture and make sure its orientation is up UIImage *updatedPicture = [MXKTools forceImageOrientationUp:[self.userPictureButton imageForState:UIControlStateNormal]]; MXWeakify(self); // Upload picture MXMediaLoader *uploader = [MXMediaManager prepareUploaderWithMatrixSession:self.mainSession initialRange:0 andRange:1.0]; [uploader uploadData:UIImageJPEGRepresentation(updatedPicture, 0.5) filename:nil mimeType:@"image/jpeg" success:^(NSString *url) { MXStrongifyAndReturnIfNil(self); // Store uploaded picture url and trigger picture saving self->uploadedPictureURL = url; [self saveUserInfo]; } failure:^(NSError *error) { MXLogDebug(@"[MXKAccountDetailsVC] Failed to upload image"); MXStrongifyAndReturnIfNil(self); [self handleErrorDuringPictureSaving:error]; }]; } else { MXWeakify(self); [_mxAccount setUserAvatarUrl:uploadedPictureURL success:^{ // uploadedPictureURL becomes the user's picture MXStrongifyAndReturnIfNil(self); [self updateUserPicture:self->uploadedPictureURL force:YES]; // Loop to end saving [self saveUserInfo]; } failure:^(NSError *error) { MXLogDebug(@"[MXKAccountDetailsVC] Failed to set avatar url"); MXStrongifyAndReturnIfNil(self); [self handleErrorDuringPictureSaving:error]; }]; } return; } // Backup is complete isSavingInProgress = NO; [self stopProfileActivityIndicator]; // Ready to leave if (onReadyToLeaveHandler) { onReadyToLeaveHandler(); onReadyToLeaveHandler = nil; } } - (void)handleErrorDuringPictureSaving:(NSError*)error { NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey]; if (!title) { title = [VectorL10n accountErrorPictureChangeFailed]; } NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey]; UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert]; [alertsArray addObject:alert]; [alert addAction:[UIAlertAction actionWithTitle:[VectorL10n abort] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { [self->alertsArray removeObject:alert]; // Remove change self.userDisplayName.text = self->currentDisplayName; [self updateUserPicture:self->_mxAccount.userAvatarUrl force:YES]; // Loop to end saving [self saveUserInfo]; }]]; [alert addAction:[UIAlertAction actionWithTitle:[VectorL10n retry] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { [self->alertsArray removeObject:alert]; // Loop to retry saving [self saveUserInfo]; }]]; [self presentViewController:alert animated:YES completion:nil]; } - (void)updateUserPicture:(NSString *)avatar_url force:(BOOL)force { if (force || currentPictureURL == nil || [currentPictureURL isEqualToString:avatar_url] == NO) { // Remove any pending observers [[NSNotificationCenter defaultCenter] removeObserver:self]; // Cancel previous loader (if any) if (imageLoader) { [imageLoader cancel]; imageLoader = nil; } // Cancel any local change isAvatarUpdated = NO; uploadedPictureURL = nil; currentPictureURL = [avatar_url isEqual:[NSNull null]] ? nil : avatar_url; // Check whether this url is valid currentDownloadId = [MXMediaManager thumbnailDownloadIdForMatrixContentURI:currentPictureURL inFolder:kMXMediaManagerAvatarThumbnailFolder toFitViewSize:self.userPictureButton.frame.size withMethod:MXThumbnailingMethodCrop]; if (!currentDownloadId) { // Set the placeholder in case of invalid Matrix Content URI. [self updateUserPictureButton:self.picturePlaceholder]; } else { // Check whether the image download is in progress id loader = [MXMediaManager existingDownloaderWithIdentifier:currentDownloadId]; if (loader) { // Observe this loader [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaLoaderStateChange:) name:kMXMediaLoaderStateDidChangeNotification object:loader]; } else { NSString *cacheFilePath = [MXMediaManager thumbnailCachePathForMatrixContentURI:currentPictureURL andType:nil inFolder:kMXMediaManagerAvatarThumbnailFolder toFitViewSize:self.userPictureButton.frame.size withMethod:MXThumbnailingMethodCrop]; // Retrieve the image from cache UIImage* image = [MXMediaManager loadPictureFromFilePath:cacheFilePath]; if (image) { [self updateUserPictureButton:image]; } else { // Download the image, by adding download observer [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaLoaderStateChange:) name:kMXMediaLoaderStateDidChangeNotification object:nil]; imageLoader = [self.mainSession.mediaManager downloadThumbnailFromMatrixContentURI:currentPictureURL withType:nil inFolder:kMXMediaManagerAvatarThumbnailFolder toFitViewSize:self.userPictureButton.frame.size withMethod:MXThumbnailingMethodCrop success:nil failure:nil]; } } } } } - (void)updateUserPictureButton:(UIImage*)image { [self.userPictureButton setImage:image forState:UIControlStateNormal]; [self.userPictureButton setImage:image forState:UIControlStateHighlighted]; [self.userPictureButton setImage:image forState:UIControlStateDisabled]; } - (void)updateSaveUserInfoButtonStatus { // Check whether display name has been changed NSString *displayname = self.userDisplayName.text; BOOL isDisplayNameUpdated = ((displayname.length || currentDisplayName.length) && [displayname isEqualToString:currentDisplayName] == NO); saveUserInfoButton.enabled = (isDisplayNameUpdated || isAvatarUpdated) && !isSavingInProgress; } - (void)onMediaLoaderStateChange:(NSNotification *)notif { MXMediaLoader *loader = (MXMediaLoader*)notif.object; if ([loader.downloadId isEqualToString:currentDownloadId]) { // update the image switch (loader.state) { case MXMediaLoaderStateDownloadCompleted: { UIImage *image = [MXMediaManager loadPictureFromFilePath:loader.downloadOutputFilePath]; if (image == nil) { image = self.picturePlaceholder; } [self updateUserPictureButton:image]; // remove the observers [[NSNotificationCenter defaultCenter] removeObserver:self]; imageLoader = nil; break; } case MXMediaLoaderStateDownloadFailed: case MXMediaLoaderStateCancelled: [self updateUserPictureButton:self.picturePlaceholder]; // remove the observers [[NSNotificationCenter defaultCenter] removeObserver:self]; imageLoader = nil; // Reset picture URL in order to try next time currentPictureURL = nil; break; default: break; } } } - (void)onAPNSStatusUpdate { // Force table reload to update notifications section apnsNotificationsSwitch = nil; [self.tableView reloadData]; } - (void)dismissMediaPicker { if (mediaPicker) { [self dismissViewControllerAnimated:NO completion:nil]; mediaPicker.delegate = nil; mediaPicker = nil; } } - (void)showValidationEmailDialogWithMessage:(NSString*)message { UIAlertController *alert = [UIAlertController alertControllerWithTitle:[VectorL10n accountEmailValidationTitle] message:message preferredStyle:UIAlertControllerStyleAlert]; [alertsArray addObject:alert]; [alert addAction:[UIAlertAction actionWithTitle:[VectorL10n abort] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { [self->alertsArray removeObject:alert]; self->emailSubmitButton.enabled = YES; }]]; [alert addAction:[UIAlertAction actionWithTitle:[BWIL10n continue] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { [self->alertsArray removeObject:alert]; __weak typeof(self) weakSelf = self; // We do not bind anymore emails when registering, so let's do the same here [self->submittedEmail add3PIDToUser:NO success:^{ if (weakSelf) { typeof(self) self = weakSelf; // Release pending email and refresh table to remove related cell self->emailTextField.text = nil; self->submittedEmail = nil; // Update linked emails [self loadLinkedEmails]; } } failure:^(NSError *error) { if (weakSelf) { typeof(self) self = weakSelf; MXLogDebug(@"[MXKAccountDetailsVC] Failed to bind email"); // Display the same popup again if the error is M_THREEPID_AUTH_FAILED MXError *mxError = [[MXError alloc] initWithNSError:error]; if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringThreePIDAuthFailed]) { [self showValidationEmailDialogWithMessage:[VectorL10n accountEmailValidationError]]; } else { // Notify MatrixKit user NSString *myUserId = self.mxAccount.mxCredentials.userId; [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; } // Release the pending email (even if it is Authenticated) [self.tableView reloadData]; } }]; }]]; [self presentViewController:alert animated:YES completion:nil]; } - (void)loadLinkedEmails { // Refresh the account 3PIDs list [_mxAccount load3PIDs:^{ [self.tableView reloadData]; } failure:^(NSError *error) { // Display the data that has been loaded last time [self.tableView reloadData]; }]; } #pragma mark - Actions - (IBAction)onButtonPressed:(id)sender { [self dismissKeyboard]; if (sender == saveUserInfoButton) { [self saveUserInfo]; } else if (sender == userPictureButton) { // Open picture gallery mediaPicker = [[UIImagePickerController alloc] init]; mediaPicker.delegate = self; mediaPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; mediaPicker.allowsEditing = NO; [self presentViewController:mediaPicker animated:YES completion:nil]; } else if (sender == logoutButton) { [[MXKAccountManager sharedManager] removeAccount:_mxAccount completion:nil]; self.mxAccount = nil; } else if (sender == emailSubmitButton) { // Email check if (![MXTools isEmailAddress:emailTextField.text]) { UIAlertController *alert = [UIAlertController alertControllerWithTitle:[VectorL10n accountErrorEmailWrongTitle] message:[VectorL10n accountErrorEmailWrongDescription] preferredStyle:UIAlertControllerStyleAlert]; [alertsArray addObject:alert]; [alert addAction:[UIAlertAction actionWithTitle:[VectorL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { [self->alertsArray removeObject:alert]; }]]; [self presentViewController:alert animated:YES completion:nil]; return; } if (!submittedEmail || ![submittedEmail.address isEqualToString:emailTextField.text]) { submittedEmail = [[MXK3PID alloc] initWithMedium:kMX3PIDMediumEmail andAddress:emailTextField.text]; } emailSubmitButton.enabled = NO; __weak typeof(self) weakSelf = self; [submittedEmail requestValidationTokenWithMatrixRestClient:self.mainSession.matrixRestClient isDuringRegistration:NO nextLink:nil success:^{ if (weakSelf) { typeof(self) self = weakSelf; [self showValidationEmailDialogWithMessage:[VectorL10n accountEmailValidationMessage]]; } } failure:^(NSError *error) { MXLogDebug(@"[MXKAccountDetailsVC] Failed to request email token"); if (weakSelf) { typeof(self) self = weakSelf; // Notify MatrixKit user NSString *myUserId = self.mxAccount.mxCredentials.userId; [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; self->emailSubmitButton.enabled = YES; } }]; } else if (sender == apnsNotificationsSwitch) { [_mxAccount enablePushNotifications:apnsNotificationsSwitch.on success:nil failure:nil]; apnsNotificationsSwitch.enabled = NO; } else if (sender == inAppNotificationsSwitch) { _mxAccount.enableInAppNotifications = inAppNotificationsSwitch.on; [self.tableView reloadData]; } } #pragma mark - keyboard - (void)dismissKeyboard { if ([userDisplayName isFirstResponder]) { // Hide the keyboard [userDisplayName resignFirstResponder]; [self updateSaveUserInfoButtonStatus]; } else if ([emailTextField isFirstResponder]) { [emailTextField resignFirstResponder]; } } #pragma mark - UITextField delegate - (BOOL)textFieldShouldReturn:(UITextField*)textField { // "Done" key has been pressed [self dismissKeyboard]; return YES; } - (IBAction)textFieldEditingChanged:(id)sender { if (sender == userDisplayName) { [self updateSaveUserInfoButtonStatus]; } } #pragma mark - UITableView data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { NSInteger count = 0; linkedEmailsSection = notificationsSection = configurationSection = -1; if (!_mxAccount.disabled) { linkedEmailsSection = count ++; notificationsSection = count ++; } configurationSection = count ++; return count; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { NSInteger count = 0; if (section == linkedEmailsSection) { count = _mxAccount.linkedEmails.count; submittedEmailRowIndex = count++; } else if (section == notificationsSection) { enableInAppNotifRowIndex = enablePushNotifRowIndex = -1; if ([MXKAccountManager sharedManager].isAPNSAvailable) { enablePushNotifRowIndex = count++; } enableInAppNotifRowIndex = count++; } else if (section == configurationSection) { count = 2; } return count; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == configurationSection) { if (indexPath.row == 0) { UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, MAXFLOAT)]; textView.font = [UIFont systemFontOfSize:14]; textView.text = [NSString stringWithFormat:@"%@\n%@\n%@", [VectorL10n settingsConfigHomeServer:_mxAccount.mxCredentials.homeServer], [VectorL10n settingsConfigIdentityServer:_mxAccount.identityServerURL], [VectorL10n settingsConfigUserId:_mxAccount.mxCredentials.userId]]; CGSize contentSize = [textView sizeThatFits:textView.frame.size]; return contentSize.height + 1; } } return 44; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = nil; if (indexPath.section == linkedEmailsSection) { if (indexPath.row < _mxAccount.linkedEmails.count) { cell = [tableView dequeueReusableCellWithIdentifier:kMXKAccountDetailsLinkedEmailCellId]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kMXKAccountDetailsLinkedEmailCellId]; } cell.selectionStyle = UITableViewCellSelectionStyleNone; cell.textLabel.text = [_mxAccount.linkedEmails objectAtIndex:indexPath.row]; } else if (indexPath.row == submittedEmailRowIndex) { // Report the current email value (if any) NSString *currentEmail = nil; if (emailTextField) { currentEmail = emailTextField.text; } MXKTableViewCellWithTextFieldAndButton *submittedEmailCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithTextFieldAndButton defaultReuseIdentifier]]; if (!submittedEmailCell) { submittedEmailCell = [[MXKTableViewCellWithTextFieldAndButton alloc] init]; } submittedEmailCell.mxkTextField.text = currentEmail; submittedEmailCell.mxkTextField.keyboardType = UIKeyboardTypeEmailAddress; submittedEmailCell.mxkButton.enabled = (currentEmail.length != 0); [submittedEmailCell.mxkButton setTitle:[VectorL10n accountLinkEmail] forState:UIControlStateNormal]; [submittedEmailCell.mxkButton setTitle:[VectorL10n accountLinkEmail] forState:UIControlStateHighlighted]; [submittedEmailCell.mxkButton addTarget:self action:@selector(onButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; emailSubmitButton = submittedEmailCell.mxkButton; emailTextField = submittedEmailCell.mxkTextField; cell = submittedEmailCell; } } else if (indexPath.section == notificationsSection) { MXKTableViewCellWithLabelAndSwitch *notificationsCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithLabelAndSwitch defaultReuseIdentifier]]; if (!notificationsCell) { notificationsCell = [[MXKTableViewCellWithLabelAndSwitch alloc] init]; } else { // Force layout before reusing a cell (fix switch displayed outside the screen) [notificationsCell layoutIfNeeded]; } [notificationsCell.mxkSwitch addTarget:self action:@selector(onButtonPressed:) forControlEvents:UIControlEventValueChanged]; if (indexPath.row == enableInAppNotifRowIndex) { notificationsCell.mxkLabel.text = [VectorL10n settingsEnableInappNotifications]; notificationsCell.mxkSwitch.on = _mxAccount.enableInAppNotifications; inAppNotificationsSwitch = notificationsCell.mxkSwitch; } else /* enablePushNotifRowIndex */ { notificationsCell.mxkLabel.text = [VectorL10n settingsEnablePushNotifications]; notificationsCell.mxkSwitch.on = _mxAccount.pushNotificationServiceIsActive; notificationsCell.mxkSwitch.enabled = YES; apnsNotificationsSwitch = notificationsCell.mxkSwitch; } cell = notificationsCell; } else if (indexPath.section == configurationSection) { if (indexPath.row == 0) { MXKTableViewCellWithTextView *configCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithTextView defaultReuseIdentifier]]; if (!configCell) { configCell = [[MXKTableViewCellWithTextView alloc] init]; } configCell.mxkTextView.text = [NSString stringWithFormat:@"%@\n%@\n%@", [VectorL10n settingsConfigHomeServer:_mxAccount.mxCredentials.homeServer], [VectorL10n settingsConfigIdentityServer:_mxAccount.identityServerURL], [VectorL10n settingsConfigUserId:_mxAccount.mxCredentials.userId]]; cell = configCell; } else { MXKTableViewCellWithButton *logoutBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; if (!logoutBtnCell) { logoutBtnCell = [[MXKTableViewCellWithButton alloc] init]; } [logoutBtnCell.mxkButton setTitle:[VectorL10n actionLogout] forState:UIControlStateNormal]; [logoutBtnCell.mxkButton setTitle:[VectorL10n actionLogout] forState:UIControlStateHighlighted]; [logoutBtnCell.mxkButton addTarget:self action:@selector(onButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; logoutButton = logoutBtnCell.mxkButton; cell = logoutBtnCell; } } else { // Return a fake cell to prevent app from crashing. cell = [[UITableViewCell alloc] init]; } return cell; } #pragma mark - UITableView delegate - (CGFloat) tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { return 30; } - (CGFloat) tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { return 1; } - (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(5, 5, sectionHeader.frame.size.width - 10, sectionHeader.frame.size.height - 10)]; sectionLabel.font = [UIFont boldSystemFontOfSize:16]; sectionLabel.backgroundColor = [UIColor clearColor]; [sectionHeader addSubview:sectionLabel]; if (section == linkedEmailsSection) { sectionLabel.text = [VectorL10n accountLinkedEmails]; } else if (section == notificationsSection) { sectionLabel.text = [VectorL10n settingsTitleNotifications]; } else if (section == configurationSection) { sectionLabel.text = [VectorL10n settingsTitleConfig]; } return sectionHeader; } - (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (self.tableView == aTableView) { [aTableView deselectRowAtIndexPath:indexPath animated:YES]; } } # pragma mark - UIImagePickerControllerDelegate - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { UIImage *selectedImage = [info objectForKey:UIImagePickerControllerOriginalImage]; if (selectedImage) { [self updateUserPictureButton:selectedImage]; isAvatarUpdated = YES; saveUserInfoButton.enabled = YES; } [self dismissMediaPicker]; } @end