element-ios/Riot/Modules/MatrixKit/Views/DeviceView/MXKDeviceView.m

473 lines
23 KiB
Objective-C

/*
Copyright 2018-2024 New Vector Ltd.
Copyright 2017 Vector Creations Ltd
Copyright 2016 OpenMarket Ltd
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
#import "MXKDeviceView.h"
#import "NSBundle+MatrixKit.h"
#import "MXKConstants.h"
#import "MXKSwiftHeader.h"
static NSAttributedString *verticalWhitespace = nil;
@interface MXKDeviceView ()
{
/**
The displayed device
*/
MXDevice *mxDevice;
/**
The matrix session.
*/
MXSession *mxSession;
/**
The current alert
*/
UIAlertController *currentAlert;
/**
Current request in progress.
*/
MXHTTPOperation *mxCurrentOperation;
}
@end
@implementation MXKDeviceView
+ (UINib *)nib
{
// Check whether a nib file is available
NSBundle *mainBundle = [NSBundle mxk_bundleForClass:self.class];
NSString *path = [mainBundle pathForResource:NSStringFromClass([self class]) ofType:@"nib"];
if (path)
{
return [UINib nibWithNibName:NSStringFromClass([self class]) bundle:mainBundle];
}
return [UINib nibWithNibName:NSStringFromClass([MXKDeviceView class]) bundle:[NSBundle mxk_bundleForClass:[MXKDeviceView class]]];
}
- (void)awakeFromNib
{
[super awakeFromNib];
// Add tap recognizer to discard the view on bg view tap
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onBgViewTap:)];
[tap setNumberOfTouchesRequired:1];
[tap setNumberOfTapsRequired:1];
[tap setDelegate:self];
[self.bgView addGestureRecognizer:tap];
// Localize string
[_cancelButton setTitle:[VectorL10n ok] forState:UIControlStateNormal];
[_cancelButton setTitle:[VectorL10n ok] forState:UIControlStateHighlighted];
[_renameButton setTitle:[VectorL10n rename] forState:UIControlStateNormal];
[_renameButton setTitle:[VectorL10n rename] forState:UIControlStateHighlighted];
[_deleteButton setTitle:[VectorL10n delete] forState:UIControlStateNormal];
[_deleteButton setTitle:[VectorL10n delete] forState:UIControlStateHighlighted];
}
- (void)layoutSubviews
{
[super layoutSubviews];
// Scroll to the top the text view content
self.textView.contentOffset = CGPointZero;
}
#pragma mark - Override MXKView
-(void)customizeViewRendering
{
[super customizeViewRendering];
_defaultTextColor = [UIColor blackColor];
// Add shadow on added view
_containerView.layer.cornerRadius = 5;
_containerView.layer.shadowOffset = CGSizeMake(0, 1);
_containerView.layer.shadowOpacity = 0.5f;
}
#pragma mark -
- (void)removeFromSuperviewDidUpdate:(BOOL)isUpdated
{
if (currentAlert)
{
[currentAlert dismissViewControllerAnimated:NO completion:nil];
currentAlert = nil;
}
if (mxCurrentOperation)
{
[mxCurrentOperation cancel];
mxCurrentOperation = nil;
}
if (self.delegate && [self.delegate respondsToSelector:@selector(dismissDeviceView:didUpdate:)])
{
[self.delegate dismissDeviceView:self didUpdate:isUpdated];
}
else
{
[self removeFromSuperview];
}
}
- (instancetype)initWithDevice:(MXDevice*)device andMatrixSession:(MXSession*)session
{
self = [[[self class] nib] instantiateWithOwner:nil options:nil].firstObject;
if (self)
{
mxDevice = device;
mxSession = session;
[self setTranslatesAutoresizingMaskIntoConstraints: NO];
if (mxDevice)
{
// Device information
NSMutableAttributedString *deviceInformationString = [[NSMutableAttributedString alloc]
initWithString:[VectorL10n deviceDetailsTitle]
attributes:@{NSForegroundColorAttributeName : _defaultTextColor,
NSFontAttributeName: [UIFont boldSystemFontOfSize:15]}];
[deviceInformationString appendAttributedString:[MXKDeviceView verticalWhitespace]];
[deviceInformationString appendAttributedString:[[NSMutableAttributedString alloc]
initWithString:[VectorL10n deviceDetailsName]
attributes:@{NSForegroundColorAttributeName : _defaultTextColor,
NSFontAttributeName: [UIFont boldSystemFontOfSize:14]}]];
[deviceInformationString appendAttributedString:[[NSMutableAttributedString alloc]
initWithString:device.displayName.length ? device.displayName : @""
attributes:@{NSForegroundColorAttributeName : _defaultTextColor,
NSFontAttributeName: [UIFont systemFontOfSize:14]}]];
[deviceInformationString appendAttributedString:[MXKDeviceView verticalWhitespace]];
[deviceInformationString appendAttributedString:[[NSMutableAttributedString alloc]
initWithString:[VectorL10n deviceDetailsIdentifier]
attributes:@{NSForegroundColorAttributeName : _defaultTextColor,
NSFontAttributeName: [UIFont boldSystemFontOfSize:14]}]];
[deviceInformationString appendAttributedString:[[NSMutableAttributedString alloc]
initWithString:device.deviceId
attributes:@{NSForegroundColorAttributeName : _defaultTextColor,
NSFontAttributeName: [UIFont systemFontOfSize:14]}]];
[deviceInformationString appendAttributedString:[MXKDeviceView verticalWhitespace]];
[deviceInformationString appendAttributedString:[[NSMutableAttributedString alloc]
initWithString:[VectorL10n deviceDetailsLastSeen]
attributes:@{NSForegroundColorAttributeName : _defaultTextColor,
NSFontAttributeName: [UIFont boldSystemFontOfSize:14]}]];
NSDate *lastSeenDate = [NSDate dateWithTimeIntervalSince1970:device.lastSeenTs/1000];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:[[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0]]];
[dateFormatter setDateStyle:NSDateFormatterShortStyle];
[dateFormatter setTimeStyle:NSDateFormatterShortStyle];
[dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
NSString *lastSeen = [VectorL10n deviceDetailsLastSeenFormat:device.lastSeenIp :[dateFormatter stringFromDate:lastSeenDate]];
[deviceInformationString appendAttributedString:[[NSMutableAttributedString alloc]
initWithString:lastSeen
attributes:@{NSForegroundColorAttributeName : _defaultTextColor,
NSFontAttributeName: [UIFont systemFontOfSize:14]}]];
[deviceInformationString appendAttributedString:[MXKDeviceView verticalWhitespace]];
self.textView.attributedText = deviceInformationString;
}
else
{
_textView.text = nil;
}
// Hide potential activity indicator
[_activityIndicator stopAnimating];
}
return self;
}
- (void)dealloc
{
mxDevice = nil;
mxSession = nil;
}
+ (NSAttributedString *)verticalWhitespace
{
if (verticalWhitespace == nil)
{
verticalWhitespace = [[NSAttributedString alloc] initWithString:@"\n\n" attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:4]}];
}
return verticalWhitespace;
}
#pragma mark - Actions
- (IBAction)onBgViewTap:(UITapGestureRecognizer*)sender
{
[self removeFromSuperviewDidUpdate:NO];
}
- (IBAction)onButtonPressed:(id)sender
{
if (sender == _cancelButton)
{
[self removeFromSuperviewDidUpdate:NO];
}
else if (sender == _renameButton)
{
[self renameDevice];
}
else if (sender == _deleteButton)
{
[self deleteDevice];
}
}
#pragma mark -
- (void)renameDevice
{
if (!self.delegate)
{
// Ignore
MXLogDebug(@"[MXKDeviceView] Rename device failed, delegate is missing");
return;
}
// Prompt the user to enter a device name.
[currentAlert dismissViewControllerAnimated:NO completion:nil];
__weak typeof(self) weakSelf = self;
currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n deviceDetailsRenamePromptTitle]
message:[VectorL10n deviceDetailsRenamePromptMessage] preferredStyle:UIAlertControllerStyleAlert];
[currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.secureTextEntry = NO;
textField.placeholder = nil;
textField.keyboardType = UIKeyboardTypeDefault;
if (weakSelf)
{
typeof(self) self = weakSelf;
textField.text = self->mxDevice.displayName;
}
}];
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
if (weakSelf)
{
typeof(self) self = weakSelf;
self->currentAlert = nil;
}
}]];
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n ok]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
if (weakSelf)
{
typeof(self) self = weakSelf;
NSString *text = [self->currentAlert textFields].firstObject.text;
self->currentAlert = nil;
[self.activityIndicator startAnimating];
self->mxCurrentOperation = [self->mxSession.matrixRestClient setDeviceName:text forDeviceId:self->mxDevice.deviceId success:^{
if (weakSelf)
{
typeof(self) self = weakSelf;
self->mxCurrentOperation = nil;
[self.activityIndicator stopAnimating];
[self removeFromSuperviewDidUpdate:YES];
}
} failure:^(NSError *error) {
if (weakSelf)
{
typeof(self) self = weakSelf;
// Notify MatrixKit user
NSString *myUserId = self->mxSession.myUser.userId;
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
self->mxCurrentOperation = nil;
MXLogDebug(@"[MXKDeviceView] Rename device (%@) failed", self->mxDevice.deviceId);
[self.activityIndicator stopAnimating];
[self removeFromSuperviewDidUpdate:NO];
}
}];
}
}]];
[self.delegate deviceView:self presentAlertController:currentAlert];
}
- (void)deleteDevice
{
if (!self.delegate)
{
// Ignore
MXLogDebug(@"[MXKDeviceView] Delete device failed, delegate is missing");
return;
}
// Get an authentication session to prepare device deletion
[self.activityIndicator startAnimating];
mxCurrentOperation = [mxSession.matrixRestClient getSessionToDeleteDeviceByDeviceId:mxDevice.deviceId success:^(MXAuthenticationSession *authSession) {
self->mxCurrentOperation = nil;
// Check whether the password based type is supported
BOOL isPasswordBasedTypeSupported = NO;
for (MXLoginFlow *loginFlow in authSession.flows)
{
if ([loginFlow.type isEqualToString:kMXLoginFlowTypePassword] || [loginFlow.stages indexOfObject:kMXLoginFlowTypePassword] != NSNotFound)
{
isPasswordBasedTypeSupported = YES;
break;
}
}
if (isPasswordBasedTypeSupported && authSession.session)
{
// Prompt for a password
[self->currentAlert dismissViewControllerAnimated:NO completion:nil];
__weak typeof(self) weakSelf = self;
// Prompt the user before deleting the device.
self->currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n deviceDetailsDeletePromptTitle] message:[VectorL10n deviceDetailsDeletePromptMessage] preferredStyle:UIAlertControllerStyleAlert];
[self->currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.secureTextEntry = YES;
textField.placeholder = nil;
textField.keyboardType = UIKeyboardTypeDefault;
}];
[self->currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
if (weakSelf)
{
typeof(self) self = weakSelf;
self->currentAlert = nil;
[self.activityIndicator stopAnimating];
}
}]];
[self->currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n submit]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
if (weakSelf)
{
typeof(self) self = weakSelf;
UITextField *textField = [self->currentAlert textFields].firstObject;
self->currentAlert = nil;
NSString *userId = self->mxSession.myUser.userId;
NSDictionary *authParams;
// Sanity check
if (userId)
{
authParams = @{@"session":authSession.session,
@"user": userId,
@"password": textField.text,
@"type": kMXLoginFlowTypePassword};
}
self->mxCurrentOperation = [self->mxSession.matrixRestClient deleteDeviceByDeviceId:self->mxDevice.deviceId authParams:authParams success:^{
if (weakSelf)
{
typeof(self) self = weakSelf;
self->mxCurrentOperation = nil;
[self.activityIndicator stopAnimating];
[self removeFromSuperviewDidUpdate:YES];
}
} failure:^(NSError *error) {
if (weakSelf)
{
typeof(self) self = weakSelf;
// Notify MatrixKit user
NSString *myUserId = self->mxSession.myUser.userId;
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
self->mxCurrentOperation = nil;
MXLogDebug(@"[MXKDeviceView] Delete device (%@) failed", self->mxDevice.deviceId);
[self.activityIndicator stopAnimating];
[self removeFromSuperviewDidUpdate:NO];
}
}];
}
}]];
[self.delegate deviceView:self presentAlertController:self->currentAlert];
}
else
{
MXLogDebug(@"[MXKDeviceView] Delete device (%@) failed, auth session flow type is not supported", self->mxDevice.deviceId);
[self.activityIndicator stopAnimating];
}
} failure:^(NSError *error) {
self->mxCurrentOperation = nil;
MXLogDebug(@"[MXKDeviceView] Delete device (%@) failed, unable to get auth session", self->mxDevice.deviceId);
[self.activityIndicator stopAnimating];
// Notify MatrixKit user
NSString *myUserId = self->mxSession.myUser.userId;
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
}];
}
@end