mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-30 16:22:39 +00:00
Merge pull request #821 from vector-im/user_devices
User Settings: List user's devices
This commit is contained in:
commit
846a89277e
7 changed files with 795 additions and 11 deletions
|
@ -274,6 +274,8 @@
|
|||
F094AA351B78E42600B1FBBF /* RecentsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F094AA231B78E42600B1FBBF /* RecentsViewController.m */; };
|
||||
F094AA371B78E42600B1FBBF /* RoomViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F094AA271B78E42600B1FBBF /* RoomViewController.m */; };
|
||||
F094AA381B78E42600B1FBBF /* SettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F094AA291B78E42600B1FBBF /* SettingsViewController.m */; };
|
||||
F09617B71DE49BAD00093E9D /* DeviceView.m in Sources */ = {isa = PBXBuildFile; fileRef = F09617B51DE49BAD00093E9D /* DeviceView.m */; };
|
||||
F09617B81DE49BAD00093E9D /* DeviceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F09617B61DE49BAD00093E9D /* DeviceView.xib */; };
|
||||
F0989F4E1CD7769000FA6EAC /* ForgotPasswordInputsView.m in Sources */ = {isa = PBXBuildFile; fileRef = F0989F4C1CD7769000FA6EAC /* ForgotPasswordInputsView.m */; };
|
||||
F0989F4F1CD7769000FA6EAC /* ForgotPasswordInputsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F0989F4D1CD7769000FA6EAC /* ForgotPasswordInputsView.xib */; };
|
||||
F09E24ED1C6DE24900D39503 /* RoomMemberTitleView.m in Sources */ = {isa = PBXBuildFile; fileRef = F09E24EB1C6DE24900D39503 /* RoomMemberTitleView.m */; };
|
||||
|
@ -725,6 +727,9 @@
|
|||
F094AA271B78E42600B1FBBF /* RoomViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RoomViewController.m; sourceTree = "<group>"; };
|
||||
F094AA281B78E42600B1FBBF /* SettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsViewController.h; sourceTree = "<group>"; };
|
||||
F094AA291B78E42600B1FBBF /* SettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsViewController.m; sourceTree = "<group>"; };
|
||||
F09617B41DE49BAD00093E9D /* DeviceView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DeviceView.h; sourceTree = "<group>"; };
|
||||
F09617B51DE49BAD00093E9D /* DeviceView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DeviceView.m; sourceTree = "<group>"; };
|
||||
F09617B61DE49BAD00093E9D /* DeviceView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DeviceView.xib; sourceTree = "<group>"; };
|
||||
F0989F4B1CD7769000FA6EAC /* ForgotPasswordInputsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ForgotPasswordInputsView.h; sourceTree = "<group>"; };
|
||||
F0989F4C1CD7769000FA6EAC /* ForgotPasswordInputsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ForgotPasswordInputsView.m; sourceTree = "<group>"; };
|
||||
F0989F4D1CD7769000FA6EAC /* ForgotPasswordInputsView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ForgotPasswordInputsView.xib; sourceTree = "<group>"; };
|
||||
|
@ -993,6 +998,7 @@
|
|||
F001D7521B8207C000A162C3 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F09617B31DE49BAD00093E9D /* DeviceView */,
|
||||
F00ACC5C1DDB11CE0093F646 /* EncryptionInfoView */,
|
||||
F083C4591D9E9C2400E5246C /* Search */,
|
||||
F0CC4DBC1C4E26FA003BBE45 /* MediaAlbum */,
|
||||
|
@ -1563,6 +1569,16 @@
|
|||
path = ViewController;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F09617B31DE49BAD00093E9D /* DeviceView */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F09617B41DE49BAD00093E9D /* DeviceView.h */,
|
||||
F09617B51DE49BAD00093E9D /* DeviceView.m */,
|
||||
F09617B61DE49BAD00093E9D /* DeviceView.xib */,
|
||||
);
|
||||
path = DeviceView;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F09E24E91C6DE24900D39503 /* RoomMember */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1954,6 +1970,7 @@
|
|||
F03BF6AC1D8BF5B1002EF6A7 /* placeholder@3x.png in Resources */,
|
||||
F094A9B91B78D8F000B1FBBF /* LaunchScreen.xib in Resources */,
|
||||
F03BF6A51D8BF5B1002EF6A7 /* notifications@2x.png in Resources */,
|
||||
F09617B81DE49BAD00093E9D /* DeviceView.xib in Resources */,
|
||||
F083C4951D9EAFC500E5246C /* file_photo_icon@3x.png in Resources */,
|
||||
F083C4901D9EAFC500E5246C /* file_music_icon.png in Resources */,
|
||||
F03BF6C11D8BF5B1002EF6A7 /* search_bg@3x.png in Resources */,
|
||||
|
@ -2150,6 +2167,7 @@
|
|||
F09EAFA11DD2109B009C7EFB /* RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.m in Sources */,
|
||||
F09EAF9F1DD2109B009C7EFB /* RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.m in Sources */,
|
||||
F0AC734A1DA2A6130011DAEE /* RoomFilesSearchViewController.m in Sources */,
|
||||
F09617B71DE49BAD00093E9D /* DeviceView.m in Sources */,
|
||||
F083C4581D9E982900E5246C /* FilesSearchCellData.m in Sources */,
|
||||
F083C45D1D9E9C2400E5246C /* FilesSearchTableViewCell.m in Sources */,
|
||||
F05895001B8B7E6600B73E85 /* RoomBubbleCellData.m in Sources */,
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
"active_call" = "Active Call";
|
||||
"active_call_details" = "Active Call (%@)";
|
||||
"later" = "Later";
|
||||
"rename" = "Rename";
|
||||
|
||||
// Authentication
|
||||
"auth_login" = "Log in";
|
||||
|
@ -308,6 +309,16 @@
|
|||
"settings_crypto_device_id" = "\nDevice ID: ";
|
||||
"settings_crypto_device_key" = "\nDevice key: ";
|
||||
|
||||
// Devices
|
||||
"device_details_title" = "Device information\n";
|
||||
"device_details_name" = "Name\n";
|
||||
"device_details_identifier" = "Device ID\n";
|
||||
"device_details_last_seen" = "Last seen\n";
|
||||
"device_details_last_seen_format" = "%@ @ %@\n";
|
||||
"device_details_rename_prompt_message" = "Device name:";
|
||||
"device_details_delete_prompt_title" = "Authentication";
|
||||
"device_details_delete_prompt_message" = "This operation requires additional authentication.\nTo continue, please enter your password.";
|
||||
|
||||
// Room Details
|
||||
"room_details_title" = "Room Details";
|
||||
"room_details_people" = "Members";
|
||||
|
|
|
@ -16,9 +16,11 @@
|
|||
|
||||
#import <MatrixKit/MatrixKit.h>
|
||||
|
||||
#import "DeviceView.h"
|
||||
|
||||
#import "MediaPickerViewController.h"
|
||||
|
||||
@interface SettingsViewController : MXKViewController<UITextFieldDelegate, MediaPickerViewControllerDelegate, UITableViewDelegate, UITableViewDataSource>
|
||||
@interface SettingsViewController : MXKViewController<UITextFieldDelegate, MediaPickerViewControllerDelegate, UITableViewDelegate, UITableViewDataSource, DeviceViewDelegate>
|
||||
|
||||
@property (nonatomic) IBOutlet UITableView *tableView;
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ enum {
|
|||
SETTINGS_SECTION_CONTACTS_INDEX,
|
||||
#endif
|
||||
SETTINGS_SECTION_LABS_INDEX,
|
||||
SETTINGS_SECTION_DEVICES_INDEX,
|
||||
SETTINGS_SECTION_CRYPTOGRAPHY_INDEX,
|
||||
SETTINGS_SECTION_COUNT
|
||||
};
|
||||
|
@ -122,6 +123,10 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)();
|
|||
NSInteger userSettingsNightModeSepIndex;
|
||||
NSInteger userSettingsNightModeIndex;
|
||||
|
||||
// Devices
|
||||
NSMutableArray<MXDevice *> *devicesArray;
|
||||
DeviceView *deviceView;
|
||||
|
||||
// Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar.
|
||||
id kAppDelegateDidTapStatusBarNotificationObserver;
|
||||
|
||||
|
@ -304,6 +309,9 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)();
|
|||
// Refresh the current device information in parallel
|
||||
[self loadCurrentDeviceInformation];
|
||||
|
||||
// Refresh devices in parallel
|
||||
[self loadDevices];
|
||||
|
||||
// Observe kAppDelegateDidTapStatusBarNotificationObserver.
|
||||
kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
||||
|
||||
|
@ -387,6 +395,12 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)();
|
|||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
|
||||
onReadyToDestroyHandler = nil;
|
||||
|
||||
if (deviceView)
|
||||
{
|
||||
[deviceView removeFromSuperview];
|
||||
deviceView = nil;
|
||||
}
|
||||
}
|
||||
|
||||
-(void)setNewEmailEditingEnabled:(BOOL)newEmailEditingEnabled
|
||||
|
@ -590,6 +604,108 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)();
|
|||
return cryptoInformationString;
|
||||
}
|
||||
|
||||
- (void)loadDevices
|
||||
{
|
||||
// Refresh the account devices list
|
||||
MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject;
|
||||
[account.mxRestClient devices:^(NSArray<MXDevice *> *devices) {
|
||||
|
||||
if (devices)
|
||||
{
|
||||
devicesArray = [NSMutableArray arrayWithArray:devices];
|
||||
|
||||
// Sort devices according to the last seen date.
|
||||
NSComparator comparator = ^NSComparisonResult(MXDevice *deviceA, MXDevice *deviceB) {
|
||||
|
||||
if (deviceA.lastSeenTs > deviceB.lastSeenTs)
|
||||
{
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
if (deviceA.lastSeenTs < deviceB.lastSeenTs)
|
||||
{
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
|
||||
return NSOrderedSame;
|
||||
};
|
||||
|
||||
// Sort devices list
|
||||
[devicesArray sortUsingComparator:comparator];
|
||||
}
|
||||
else
|
||||
{
|
||||
devicesArray = nil;
|
||||
|
||||
}
|
||||
|
||||
// Refresh all the table (A slide down animation is observed when we limit the refresh to the concerned section).
|
||||
// Note: The use of 'reloadData' handles the case where the account has been logged out.
|
||||
[self.tableView reloadData];
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
|
||||
// Display the data that has been loaded last time
|
||||
// Note: The use of 'reloadData' handles the case where the account has been logged out.
|
||||
[self.tableView reloadData];
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)showDeviceDetails:(MXDevice *)device
|
||||
{
|
||||
[self dismissKeyboard];
|
||||
|
||||
deviceView = [[DeviceView alloc] initWithDevice:device andMatrixSession:self.mainSession];
|
||||
deviceView.delegate = self;
|
||||
|
||||
// Add the view and define edge constraints
|
||||
[self.view addSubview:deviceView];
|
||||
|
||||
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:deviceView
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self.topLayoutGuide
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1.0f
|
||||
constant:0.0f]];
|
||||
|
||||
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:deviceView
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self.bottomLayoutGuide
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0f
|
||||
constant:0.0f]];
|
||||
|
||||
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view
|
||||
attribute:NSLayoutAttributeLeading
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:deviceView
|
||||
attribute:NSLayoutAttributeLeading
|
||||
multiplier:1.0f
|
||||
constant:0.0f]];
|
||||
|
||||
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view
|
||||
attribute:NSLayoutAttributeTrailing
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:deviceView
|
||||
attribute:NSLayoutAttributeTrailing
|
||||
multiplier:1.0f
|
||||
constant:0.0f]];
|
||||
[self.view setNeedsUpdateConstraints];
|
||||
}
|
||||
|
||||
- (void)deviceView:(DeviceView*)deviceView presentMXKAlert:(MXKAlert*)alert
|
||||
{
|
||||
[self dismissKeyboard];
|
||||
[alert showInViewController:self];
|
||||
}
|
||||
|
||||
- (void)deviceViewDidUpdate:(DeviceView*)deviceView
|
||||
{
|
||||
[self loadDevices];
|
||||
}
|
||||
|
||||
#pragma mark - Segues
|
||||
|
||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
|
||||
|
@ -669,6 +785,10 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)();
|
|||
{
|
||||
count = LABS_COUNT;
|
||||
}
|
||||
else if (section == SETTINGS_SECTION_DEVICES_INDEX)
|
||||
{
|
||||
count = devicesArray.count;
|
||||
}
|
||||
else if (section == SETTINGS_SECTION_CRYPTOGRAPHY_INDEX)
|
||||
{
|
||||
// Check whether this section is visible.
|
||||
|
@ -958,8 +1078,8 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)();
|
|||
if (!privacyPolicyCell)
|
||||
{
|
||||
privacyPolicyCell = [[MXKTableViewCell alloc] init];
|
||||
privacyPolicyCell.textLabel.font = [UIFont systemFontOfSize:17];
|
||||
}
|
||||
privacyPolicyCell.textLabel.font = [UIFont systemFontOfSize:17];
|
||||
|
||||
NSString *ignoredUserId;
|
||||
if (indexPath.row < session.ignoredUsers.count)
|
||||
|
@ -992,8 +1112,8 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)();
|
|||
if (!configCell)
|
||||
{
|
||||
configCell = [[MXKTableViewCell alloc] init];
|
||||
configCell.textLabel.font = [UIFont systemFontOfSize:17];
|
||||
}
|
||||
configCell.textLabel.font = [UIFont systemFontOfSize:17];
|
||||
|
||||
NSString *configFormat = [NSString stringWithFormat:@"%@\n%@\n%@", [NSBundle mxk_localizedStringForKey:@"settings_config_user_id"], [NSBundle mxk_localizedStringForKey:@"settings_config_home_server"], [NSBundle mxk_localizedStringForKey:@"settings_config_identity_server"]];
|
||||
|
||||
|
@ -1011,8 +1131,8 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)();
|
|||
if (!versionCell)
|
||||
{
|
||||
versionCell = [[MXKTableViewCell alloc] init];
|
||||
versionCell.textLabel.font = [UIFont systemFontOfSize:17];
|
||||
}
|
||||
versionCell.textLabel.font = [UIFont systemFontOfSize:17];
|
||||
|
||||
NSString* appVersion = [AppDelegate theDelegate].appVersion;
|
||||
NSString* build = [AppDelegate theDelegate].build;
|
||||
|
@ -1028,8 +1148,8 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)();
|
|||
if (!versionCell)
|
||||
{
|
||||
versionCell = [[MXKTableViewCell alloc] init];
|
||||
versionCell.textLabel.font = [UIFont systemFontOfSize:17];
|
||||
}
|
||||
versionCell.textLabel.font = [UIFont systemFontOfSize:17];
|
||||
|
||||
versionCell.textLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"settings_olm_version", @"Vector", nil), OLMKitVersionString()];
|
||||
versionCell.textLabel.textColor = kVectorTextColorBlack;
|
||||
|
@ -1042,8 +1162,8 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)();
|
|||
if (!termAndConditionCell)
|
||||
{
|
||||
termAndConditionCell = [[MXKTableViewCell alloc] init];
|
||||
termAndConditionCell.textLabel.font = [UIFont systemFontOfSize:17];
|
||||
}
|
||||
termAndConditionCell.textLabel.font = [UIFont systemFontOfSize:17];
|
||||
|
||||
termAndConditionCell.textLabel.text = NSLocalizedStringFromTable(@"settings_term_conditions", @"Vector", nil);
|
||||
termAndConditionCell.textLabel.textColor = kVectorTextColorBlack;
|
||||
|
@ -1056,8 +1176,8 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)();
|
|||
if (!copyrightCell)
|
||||
{
|
||||
copyrightCell = [[MXKTableViewCell alloc] init];
|
||||
copyrightCell.textLabel.font = [UIFont systemFontOfSize:17];
|
||||
}
|
||||
copyrightCell.textLabel.font = [UIFont systemFontOfSize:17];
|
||||
|
||||
copyrightCell.textLabel.text = NSLocalizedStringFromTable(@"settings_copyright", @"Vector", nil);
|
||||
copyrightCell.textLabel.textColor = kVectorTextColorBlack;
|
||||
|
@ -1070,8 +1190,8 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)();
|
|||
if (!privacyPolicyCell)
|
||||
{
|
||||
privacyPolicyCell = [[MXKTableViewCell alloc] init];
|
||||
privacyPolicyCell.textLabel.font = [UIFont systemFontOfSize:17];
|
||||
}
|
||||
privacyPolicyCell.textLabel.font = [UIFont systemFontOfSize:17];
|
||||
|
||||
privacyPolicyCell.textLabel.text = NSLocalizedStringFromTable(@"settings_privacy_policy", @"Vector", nil);
|
||||
privacyPolicyCell.textLabel.textColor = kVectorTextColorBlack;
|
||||
|
@ -1084,8 +1204,8 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)();
|
|||
if (!thirdPartyCell)
|
||||
{
|
||||
thirdPartyCell = [[MXKTableViewCell alloc] init];
|
||||
thirdPartyCell.textLabel.font = [UIFont systemFontOfSize:17];
|
||||
}
|
||||
thirdPartyCell.textLabel.font = [UIFont systemFontOfSize:17];
|
||||
|
||||
thirdPartyCell.textLabel.text = NSLocalizedStringFromTable(@"settings_third_party_notices", @"Vector", nil);
|
||||
thirdPartyCell.textLabel.textColor = kVectorTextColorBlack;
|
||||
|
@ -1166,6 +1286,34 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)();
|
|||
cell = labelAndSwitchCell;
|
||||
}
|
||||
}
|
||||
else if (section == SETTINGS_SECTION_DEVICES_INDEX)
|
||||
{
|
||||
MXKTableViewCell *deviceCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCell defaultReuseIdentifier]];
|
||||
if (!deviceCell)
|
||||
{
|
||||
deviceCell = [[MXKTableViewCell alloc] init];
|
||||
}
|
||||
|
||||
if (row < devicesArray.count)
|
||||
{
|
||||
NSString *name = devicesArray[row].displayName;
|
||||
NSString *deviceId = devicesArray[row].deviceId;
|
||||
deviceCell.textLabel.text = (name.length ? [NSString stringWithFormat:@"%@ [%@]", name, deviceId] : [NSString stringWithFormat:@"[%@]", deviceId]);
|
||||
deviceCell.textLabel.numberOfLines = 0;
|
||||
|
||||
if ([deviceId isEqualToString:self.mainSession.matrixRestClient.credentials.deviceId])
|
||||
{
|
||||
deviceCell.textLabel.font = [UIFont boldSystemFontOfSize:17];
|
||||
}
|
||||
else
|
||||
{
|
||||
deviceCell.textLabel.font = [UIFont systemFontOfSize:17];
|
||||
}
|
||||
}
|
||||
deviceCell.textLabel.textColor = kVectorTextColorBlack;
|
||||
|
||||
cell = deviceCell;
|
||||
}
|
||||
else if (section == SETTINGS_SECTION_CRYPTOGRAPHY_INDEX)
|
||||
{
|
||||
MXKTableViewCell *cryptoCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCell defaultReuseIdentifier]];
|
||||
|
@ -1221,6 +1369,10 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)();
|
|||
{
|
||||
return NSLocalizedStringFromTable(@"settings_labs", @"Vector", nil);
|
||||
}
|
||||
else if (section == SETTINGS_SECTION_DEVICES_INDEX)
|
||||
{
|
||||
return NSLocalizedStringFromTable(@"settings_devices", @"Vector", nil);
|
||||
}
|
||||
else if (section == SETTINGS_SECTION_CRYPTOGRAPHY_INDEX)
|
||||
{
|
||||
// Check whether this section is visible
|
||||
|
@ -1375,8 +1527,7 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)();
|
|||
[currentAlert showInViewController:self];
|
||||
}
|
||||
}
|
||||
else
|
||||
if (section == SETTINGS_SECTION_OTHER_INDEX)
|
||||
else if (section == SETTINGS_SECTION_OTHER_INDEX)
|
||||
{
|
||||
if (row == OTHER_COPYRIGHT_INDEX)
|
||||
{
|
||||
|
@ -1417,6 +1568,13 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)();
|
|||
self.newEmailEditingEnabled = YES;
|
||||
}
|
||||
}
|
||||
else if (section == SETTINGS_SECTION_DEVICES_INDEX)
|
||||
{
|
||||
if (row < devicesArray.count)
|
||||
{
|
||||
[self showDeviceDetails:devicesArray[row]];
|
||||
}
|
||||
}
|
||||
|
||||
[aTableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
}
|
||||
|
|
62
Vector/Views/DeviceView/DeviceView.h
Normal file
62
Vector/Views/DeviceView/DeviceView.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
Copyright 2016 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 <MatrixKit/MatrixKit.h>
|
||||
|
||||
@class DeviceView;
|
||||
@protocol DeviceViewDelegate <NSObject>
|
||||
|
||||
/**
|
||||
Tells the delegate that a MXKAlert must be presented.
|
||||
|
||||
@param deviceView the device view.
|
||||
@param alert the alert to present.
|
||||
*/
|
||||
- (void)deviceView:(DeviceView*)deviceView presentMXKAlert:(MXKAlert*)alert;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
Tells the delegate that the device was updated (renamed, removed...).
|
||||
|
||||
@param deviceView the device view.
|
||||
*/
|
||||
- (void)deviceViewDidUpdate:(DeviceView*)deviceView;
|
||||
|
||||
@end
|
||||
|
||||
@interface DeviceView : UIView <UIGestureRecognizerDelegate>
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UIView *bgView;
|
||||
@property (weak, nonatomic) IBOutlet UIView *containerView;
|
||||
@property (weak, nonatomic) IBOutlet UITextView *textView;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *cancelButton;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *renameButton;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *deleteButton;
|
||||
@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;
|
||||
|
||||
/**
|
||||
Initialize a device view to display the information of a user's device.
|
||||
*/
|
||||
- (instancetype)initWithDevice:(MXDevice*)device andMatrixSession:(MXSession*)session;
|
||||
|
||||
/**
|
||||
The delegate.
|
||||
*/
|
||||
@property (nonatomic) id<DeviceViewDelegate> delegate;
|
||||
|
||||
@end
|
||||
|
416
Vector/Views/DeviceView/DeviceView.m
Normal file
416
Vector/Views/DeviceView/DeviceView.m
Normal file
|
@ -0,0 +1,416 @@
|
|||
/*
|
||||
Copyright 2016 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 "DeviceView.h"
|
||||
|
||||
#import "AppDelegate.h"
|
||||
|
||||
#import "VectorDesignValues.h"
|
||||
|
||||
static NSAttributedString *verticalWhitespace = nil;
|
||||
|
||||
@interface DeviceView ()
|
||||
{
|
||||
/**
|
||||
The displayed device
|
||||
*/
|
||||
MXDevice *mxDevice;
|
||||
|
||||
/**
|
||||
The matrix session.
|
||||
*/
|
||||
MXSession *mxSession;
|
||||
|
||||
/**
|
||||
The current alert
|
||||
*/
|
||||
MXKAlert *currentAlert;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation DeviceView
|
||||
|
||||
- (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];
|
||||
|
||||
// Add shadow on event details view
|
||||
_containerView.layer.cornerRadius = 5;
|
||||
_containerView.layer.shadowOffset = CGSizeMake(0, 1);
|
||||
_containerView.layer.shadowOpacity = 0.5f;
|
||||
|
||||
// Localize string
|
||||
[_cancelButton setTitle:[NSBundle mxk_localizedStringForKey:@"ok"] forState:UIControlStateNormal];
|
||||
[_cancelButton setTitle:[NSBundle mxk_localizedStringForKey:@"ok"] forState:UIControlStateHighlighted];
|
||||
|
||||
[_renameButton setTitle:[NSBundle mxk_localizedStringForKey:@"rename"] forState:UIControlStateNormal];
|
||||
[_renameButton setTitle:[NSBundle mxk_localizedStringForKey:@"rename"] forState:UIControlStateHighlighted];
|
||||
|
||||
[_deleteButton setTitle:[NSBundle mxk_localizedStringForKey:@"delete"] forState:UIControlStateNormal];
|
||||
[_deleteButton setTitle:[NSBundle mxk_localizedStringForKey:@"delete"] forState:UIControlStateHighlighted];
|
||||
}
|
||||
|
||||
- (void)removeFromSuperview
|
||||
{
|
||||
if (currentAlert)
|
||||
{
|
||||
[currentAlert dismiss:NO];
|
||||
currentAlert = nil;
|
||||
}
|
||||
|
||||
[super removeFromSuperview];
|
||||
}
|
||||
|
||||
- (instancetype)initWithDevice:(MXDevice*)device andMatrixSession:(MXSession*)session
|
||||
{
|
||||
NSArray *nibViews = [[NSBundle bundleForClass:[DeviceView class]] loadNibNamed:NSStringFromClass([DeviceView class])
|
||||
owner:nil
|
||||
options:nil];
|
||||
self = nibViews.firstObject;
|
||||
if (self)
|
||||
{
|
||||
mxDevice = device;
|
||||
mxSession = session;
|
||||
|
||||
[self setTranslatesAutoresizingMaskIntoConstraints: NO];
|
||||
|
||||
if (mxDevice)
|
||||
{
|
||||
// Device information
|
||||
NSMutableAttributedString *deviceInformationString = [[NSMutableAttributedString alloc]
|
||||
initWithString:NSLocalizedStringFromTable(@"device_details_title", @"Vector", nil)
|
||||
attributes:@{NSForegroundColorAttributeName : kVectorTextColorBlack,
|
||||
NSFontAttributeName: [UIFont boldSystemFontOfSize:15]}];
|
||||
[deviceInformationString appendAttributedString:[DeviceView verticalWhitespace]];
|
||||
|
||||
[deviceInformationString appendAttributedString:[[NSMutableAttributedString alloc]
|
||||
initWithString:NSLocalizedStringFromTable(@"device_details_name", @"Vector", nil)
|
||||
attributes:@{NSForegroundColorAttributeName : kVectorTextColorBlack,
|
||||
NSFontAttributeName: [UIFont boldSystemFontOfSize:14]}]];
|
||||
[deviceInformationString appendAttributedString:[[NSMutableAttributedString alloc]
|
||||
initWithString:device.displayName.length ? device.displayName : @""
|
||||
attributes:@{NSForegroundColorAttributeName : kVectorTextColorBlack,
|
||||
NSFontAttributeName: [UIFont systemFontOfSize:14]}]];
|
||||
[deviceInformationString appendAttributedString:[DeviceView verticalWhitespace]];
|
||||
|
||||
[deviceInformationString appendAttributedString:[[NSMutableAttributedString alloc]
|
||||
initWithString:NSLocalizedStringFromTable(@"device_details_identifier", @"Vector", nil)
|
||||
attributes:@{NSForegroundColorAttributeName : kVectorTextColorBlack,
|
||||
NSFontAttributeName: [UIFont boldSystemFontOfSize:14]}]];
|
||||
[deviceInformationString appendAttributedString:[[NSMutableAttributedString alloc]
|
||||
initWithString:device.deviceId
|
||||
attributes:@{NSForegroundColorAttributeName : kVectorTextColorBlack,
|
||||
NSFontAttributeName: [UIFont systemFontOfSize:14]}]];
|
||||
[deviceInformationString appendAttributedString:[DeviceView verticalWhitespace]];
|
||||
|
||||
[deviceInformationString appendAttributedString:[[NSMutableAttributedString alloc]
|
||||
initWithString:NSLocalizedStringFromTable(@"device_details_last_seen", @"Vector", nil)
|
||||
attributes:@{NSForegroundColorAttributeName : kVectorTextColorBlack,
|
||||
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 = [NSString stringWithFormat:NSLocalizedStringFromTable(@"device_details_last_seen_format", @"Vector", nil), device.lastSeenIp, [dateFormatter stringFromDate:lastSeenDate]];
|
||||
|
||||
[deviceInformationString appendAttributedString:[[NSMutableAttributedString alloc]
|
||||
initWithString:lastSeen
|
||||
attributes:@{NSForegroundColorAttributeName : kVectorTextColorBlack,
|
||||
NSFontAttributeName: [UIFont systemFontOfSize:14]}]];
|
||||
[deviceInformationString appendAttributedString:[DeviceView 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 removeFromSuperview];
|
||||
}
|
||||
|
||||
- (IBAction)onButtonPressed:(id)sender
|
||||
{
|
||||
if (sender == _cancelButton)
|
||||
{
|
||||
[self removeFromSuperview];
|
||||
}
|
||||
else if (sender == _renameButton)
|
||||
{
|
||||
[self renameDevice];
|
||||
}
|
||||
else if (sender == _deleteButton)
|
||||
{
|
||||
[self deleteDevice];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)renameDevice
|
||||
{
|
||||
if (!self.delegate)
|
||||
{
|
||||
// Ignore
|
||||
NSLog(@"[DeviceView] Rename device failed, delegate is missing");
|
||||
return;
|
||||
}
|
||||
|
||||
[currentAlert dismiss:NO];
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
|
||||
// Prompt the user to enter a device name.
|
||||
currentAlert = [[MXKAlert alloc] initWithTitle:nil message:NSLocalizedStringFromTable(@"device_details_rename_prompt_message", @"Vector", nil) style:MXKAlertStyleAlert];
|
||||
|
||||
[currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
|
||||
textField.secureTextEntry = NO;
|
||||
textField.placeholder = nil;
|
||||
textField.keyboardType = UIKeyboardTypeDefault;
|
||||
if (weakSelf)
|
||||
{
|
||||
__strong __typeof(weakSelf)strongSelf = weakSelf;
|
||||
textField.text = strongSelf->mxDevice.displayName;
|
||||
}
|
||||
}];
|
||||
|
||||
currentAlert.cancelButtonIndex = [currentAlert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] style:MXKAlertActionStyleDefault handler:^(MXKAlert *alert) {
|
||||
|
||||
if (weakSelf)
|
||||
{
|
||||
__strong __typeof(weakSelf)strongSelf = weakSelf;
|
||||
strongSelf->currentAlert = nil;
|
||||
}
|
||||
|
||||
}];
|
||||
|
||||
[currentAlert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] style:MXKAlertActionStyleDefault handler:^(MXKAlert *alert) {
|
||||
|
||||
if (weakSelf)
|
||||
{
|
||||
UITextField *textField = [alert textFieldAtIndex:0];
|
||||
|
||||
__strong __typeof(weakSelf)strongSelf = weakSelf;
|
||||
strongSelf->currentAlert = nil;
|
||||
|
||||
[strongSelf.activityIndicator startAnimating];
|
||||
|
||||
[strongSelf->mxSession.matrixRestClient setDeviceName:textField.text forDeviceId:strongSelf->mxDevice.deviceId success:^{
|
||||
|
||||
if (weakSelf)
|
||||
{
|
||||
__strong __typeof(weakSelf)strongSelf = weakSelf;
|
||||
|
||||
[strongSelf.activityIndicator stopAnimating];
|
||||
|
||||
if (strongSelf.delegate && [strongSelf.delegate respondsToSelector:@selector(deviceViewDidUpdate:)])
|
||||
{
|
||||
[strongSelf.delegate deviceViewDidUpdate:strongSelf];
|
||||
}
|
||||
|
||||
[strongSelf removeFromSuperview];
|
||||
}
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
|
||||
// Notify user
|
||||
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
||||
|
||||
if (weakSelf)
|
||||
{
|
||||
__strong __typeof(weakSelf)strongSelf = weakSelf;
|
||||
|
||||
NSLog(@"[DeviceView] Rename device (%@) failed", strongSelf->mxDevice.deviceId);
|
||||
|
||||
[strongSelf.activityIndicator stopAnimating];
|
||||
[strongSelf removeFromSuperview];
|
||||
}
|
||||
|
||||
}];
|
||||
}
|
||||
}];
|
||||
|
||||
[self.delegate deviceView:self presentMXKAlert:currentAlert];
|
||||
}
|
||||
|
||||
- (void)deleteDevice
|
||||
{
|
||||
if (!self.delegate)
|
||||
{
|
||||
// Ignore
|
||||
NSLog(@"[DeviceView] Delete device failed, delegate is missing");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get an authentication session to prepare device deletion
|
||||
[self.activityIndicator startAnimating];
|
||||
|
||||
[mxSession.matrixRestClient getSessionToDeleteDeviceByDeviceId:mxDevice.deviceId success:^(MXAuthenticationSession *authSession) {
|
||||
|
||||
// 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
|
||||
[currentAlert dismiss:NO];
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
|
||||
// Prompt the user to enter a device name.
|
||||
currentAlert = [[MXKAlert alloc] initWithTitle:NSLocalizedStringFromTable(@"device_details_delete_prompt_title", @"Vector", nil) message:NSLocalizedStringFromTable(@"device_details_delete_prompt_message", @"Vector", nil) style:MXKAlertStyleAlert];
|
||||
|
||||
[currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
|
||||
textField.secureTextEntry = YES;
|
||||
textField.placeholder = nil;
|
||||
textField.keyboardType = UIKeyboardTypeDefault;
|
||||
}];
|
||||
|
||||
currentAlert.cancelButtonIndex = [currentAlert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] style:MXKAlertActionStyleDefault handler:^(MXKAlert *alert) {
|
||||
|
||||
if (weakSelf)
|
||||
{
|
||||
__strong __typeof(weakSelf)strongSelf = weakSelf;
|
||||
strongSelf->currentAlert = nil;
|
||||
|
||||
[strongSelf.activityIndicator stopAnimating];
|
||||
}
|
||||
|
||||
}];
|
||||
|
||||
[currentAlert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"submit"] style:MXKAlertActionStyleDefault handler:^(MXKAlert *alert) {
|
||||
|
||||
if (weakSelf)
|
||||
{
|
||||
UITextField *textField = [alert textFieldAtIndex:0];
|
||||
|
||||
__strong __typeof(weakSelf)strongSelf = weakSelf;
|
||||
strongSelf->currentAlert = nil;
|
||||
|
||||
NSString *userId = strongSelf->mxSession.myUser.userId;
|
||||
NSDictionary *authParams;
|
||||
|
||||
// Sanity check
|
||||
if (userId)
|
||||
{
|
||||
authParams = @{@"session":authSession.session,
|
||||
@"user": userId,
|
||||
@"password": textField.text,
|
||||
@"type": kMXLoginFlowTypePassword};
|
||||
|
||||
}
|
||||
|
||||
[strongSelf->mxSession.matrixRestClient deleteDeviceByDeviceId:strongSelf->mxDevice.deviceId authParams:authParams success:^{
|
||||
|
||||
if (weakSelf)
|
||||
{
|
||||
__strong __typeof(weakSelf)strongSelf = weakSelf;
|
||||
|
||||
[strongSelf.activityIndicator stopAnimating];
|
||||
|
||||
if (strongSelf.delegate && [strongSelf.delegate respondsToSelector:@selector(deviceViewDidUpdate:)])
|
||||
{
|
||||
[strongSelf.delegate deviceViewDidUpdate:strongSelf];
|
||||
}
|
||||
|
||||
[strongSelf removeFromSuperview];
|
||||
}
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
|
||||
// Notify user
|
||||
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
||||
|
||||
if (weakSelf)
|
||||
{
|
||||
__strong __typeof(weakSelf)strongSelf = weakSelf;
|
||||
|
||||
NSLog(@"[DeviceView] Delete device (%@) failed", strongSelf->mxDevice.deviceId);
|
||||
|
||||
[strongSelf.activityIndicator stopAnimating];
|
||||
[strongSelf removeFromSuperview];
|
||||
}
|
||||
|
||||
}];
|
||||
}
|
||||
}];
|
||||
|
||||
[self.delegate deviceView:self presentMXKAlert:currentAlert];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"[DeviceView] Delete device (%@) failed, auth session flow type is not supported", mxDevice.deviceId);
|
||||
[self.activityIndicator stopAnimating];
|
||||
}
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
|
||||
NSLog(@"[DeviceView] Delete device (%@) failed, unable to get auth session", mxDevice.deviceId);
|
||||
[self.activityIndicator stopAnimating];
|
||||
|
||||
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
117
Vector/Views/DeviceView/DeviceView.xib
Normal file
117
Vector/Views/DeviceView/DeviceView.xib
Normal file
|
@ -0,0 +1,117 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="10117" systemVersion="16A323" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="8VI-1E-fge" customClass="DeviceView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<view alpha="0.5" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="D7c-Zw-n8I">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="c3W-Ma-n01">
|
||||
<rect key="frame" x="10" y="10" width="580" height="250"/>
|
||||
<subviews>
|
||||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" bounces="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4nG-AI-PEC">
|
||||
<rect key="frame" x="10" y="10" width="560" height="190"/>
|
||||
<subviews>
|
||||
<textView autoresizesSubviews="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" bounces="NO" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" editable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Vk-Jx-L6Y">
|
||||
<rect key="frame" x="0.0" y="0.0" width="3373" height="33"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.</string>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="3Vk-Jx-L6Y" secondAttribute="bottom" id="Cpy-f6-QMy"/>
|
||||
<constraint firstItem="3Vk-Jx-L6Y" firstAttribute="leading" secondItem="4nG-AI-PEC" secondAttribute="leading" id="S0R-He-sfC"/>
|
||||
<constraint firstAttribute="trailing" secondItem="3Vk-Jx-L6Y" secondAttribute="trailing" id="fd7-Mq-LUm"/>
|
||||
<constraint firstItem="3Vk-Jx-L6Y" firstAttribute="top" secondItem="4nG-AI-PEC" secondAttribute="top" id="u5W-iC-lJL"/>
|
||||
</constraints>
|
||||
</scrollView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ump-1C-SzI">
|
||||
<rect key="frame" x="58" y="210" width="30" height="30"/>
|
||||
<state key="normal" title="OK">
|
||||
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="onButtonPressed:" destination="8VI-1E-fge" eventType="touchUpInside" id="t42-2S-MsY"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="o3Z-D3-2S3">
|
||||
<rect key="frame" x="335" y="210" width="56" height="30"/>
|
||||
<state key="normal" title="Rename">
|
||||
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="onButtonPressed:" destination="8VI-1E-fge" eventType="touchUpInside" id="hbe-VP-vE5"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="qkC-t0-Qd0">
|
||||
<rect key="frame" x="485" y="210" width="45" height="30"/>
|
||||
<state key="normal" title="Delete">
|
||||
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="onButtonPressed:" destination="8VI-1E-fge" eventType="touchUpInside" id="ljK-ul-fY2"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="o3Z-D3-2S3" secondAttribute="bottom" constant="10" id="RLg-NE-JtP"/>
|
||||
<constraint firstItem="qkC-t0-Qd0" firstAttribute="top" secondItem="4nG-AI-PEC" secondAttribute="bottom" constant="10" id="Xb3-D8-X7G"/>
|
||||
<constraint firstAttribute="centerX" secondItem="Ump-1C-SzI" secondAttribute="centerX" multiplier="4" id="anR-Ea-ml6"/>
|
||||
<constraint firstItem="4nG-AI-PEC" firstAttribute="leading" secondItem="c3W-Ma-n01" secondAttribute="leading" constant="10" id="clf-JG-NQa"/>
|
||||
<constraint firstAttribute="bottom" secondItem="qkC-t0-Qd0" secondAttribute="bottom" constant="10" id="eJw-AZ-Okc"/>
|
||||
<constraint firstAttribute="centerX" secondItem="o3Z-D3-2S3" secondAttribute="centerX" multiplier="4/5" id="h0U-I5-5dA"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Ump-1C-SzI" secondAttribute="bottom" constant="10" id="kLj-CN-wSG"/>
|
||||
<constraint firstAttribute="trailing" secondItem="4nG-AI-PEC" secondAttribute="trailing" constant="10" id="mRC-uL-rOS"/>
|
||||
<constraint firstAttribute="height" priority="750" constant="250" id="rtj-ON-bkY"/>
|
||||
<constraint firstAttribute="centerX" secondItem="qkC-t0-Qd0" secondAttribute="centerX" multiplier="4/7" id="s2R-r3-jqz"/>
|
||||
<constraint firstItem="4nG-AI-PEC" firstAttribute="top" secondItem="c3W-Ma-n01" secondAttribute="top" constant="10" id="sXU-7c-hkx"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="4za-i5-u2D">
|
||||
<rect key="frame" x="290" y="290" width="20" height="20"/>
|
||||
</activityIndicatorView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="D7c-Zw-n8I" firstAttribute="top" secondItem="8VI-1E-fge" secondAttribute="top" id="9Md-NO-OM3"/>
|
||||
<constraint firstAttribute="trailing" secondItem="c3W-Ma-n01" secondAttribute="trailing" constant="10" id="F6j-Ia-5EQ"/>
|
||||
<constraint firstItem="D7c-Zw-n8I" firstAttribute="leading" secondItem="8VI-1E-fge" secondAttribute="leading" id="Gzh-sa-sB4"/>
|
||||
<constraint firstItem="c3W-Ma-n01" firstAttribute="height" relation="lessThanOrEqual" secondItem="8VI-1E-fge" secondAttribute="height" constant="-20" id="OE8-Ll-IX0"/>
|
||||
<constraint firstItem="4za-i5-u2D" firstAttribute="centerX" secondItem="8VI-1E-fge" secondAttribute="centerX" id="SGT-wg-tcC"/>
|
||||
<constraint firstItem="c3W-Ma-n01" firstAttribute="top" secondItem="8VI-1E-fge" secondAttribute="top" constant="10" id="a6K-LL-lzT"/>
|
||||
<constraint firstItem="4za-i5-u2D" firstAttribute="centerY" secondItem="8VI-1E-fge" secondAttribute="centerY" id="gjb-ga-edx"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="c3W-Ma-n01" secondAttribute="bottom" constant="10" id="oD7-T4-DuW"/>
|
||||
<constraint firstAttribute="bottom" secondItem="D7c-Zw-n8I" secondAttribute="bottom" id="qda-fo-h6c"/>
|
||||
<constraint firstItem="c3W-Ma-n01" firstAttribute="leading" secondItem="8VI-1E-fge" secondAttribute="leading" constant="10" id="ryV-yq-aqs"/>
|
||||
<constraint firstAttribute="trailing" secondItem="D7c-Zw-n8I" secondAttribute="trailing" id="wCd-aV-jjY"/>
|
||||
</constraints>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<nil key="simulatedTopBarMetrics"/>
|
||||
<nil key="simulatedBottomBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<connections>
|
||||
<outlet property="activityIndicator" destination="4za-i5-u2D" id="IH9-BZ-e26"/>
|
||||
<outlet property="bgView" destination="D7c-Zw-n8I" id="iQs-LG-SpD"/>
|
||||
<outlet property="cancelButton" destination="Ump-1C-SzI" id="U8M-nT-JiK"/>
|
||||
<outlet property="containerView" destination="c3W-Ma-n01" id="Stk-lk-Wew"/>
|
||||
<outlet property="deleteButton" destination="qkC-t0-Qd0" id="bRu-al-rte"/>
|
||||
<outlet property="renameButton" destination="o3Z-D3-2S3" id="4tr-TC-EcJ"/>
|
||||
<outlet property="textView" destination="3Vk-Jx-L6Y" id="uOw-Bq-neN"/>
|
||||
</connections>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
Loading…
Reference in a new issue