1164 lines
45 KiB
Objective-C
1164 lines
45 KiB
Objective-C
/*
|
|
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:self->_mxAccount.userPresence];
|
|
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:[VectorL10n 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
|