mirror of https://github.com/oxen-io/session-ios
WIP
parent
580a9f2c09
commit
06e6230c3b
@ -1,23 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <SignalMessaging/OWSViewController.h>
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@class HomeViewController;
|
|
||||||
|
|
||||||
@interface ProfileViewController : OWSViewController
|
|
||||||
|
|
||||||
- (instancetype)init NS_UNAVAILABLE;
|
|
||||||
|
|
||||||
+ (BOOL)shouldDisplayProfileViewOnLaunch;
|
|
||||||
|
|
||||||
+ (void)presentForAppSettings:(UINavigationController *)navigationController;
|
|
||||||
+ (void)presentForRegistration:(UINavigationController *)navigationController;
|
|
||||||
+ (void)presentForUpgradeOrNag:(HomeViewController *)fromViewController NS_SWIFT_NAME(presentForUpgradeOrNag(from:));
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
@ -1,648 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "ProfileViewController.h"
|
|
||||||
#import "AppDelegate.h"
|
|
||||||
#import "AvatarViewHelper.h"
|
|
||||||
#import "OWSNavigationController.h"
|
|
||||||
#import "Session-Swift.h"
|
|
||||||
#import "SignalsNavigationController.h"
|
|
||||||
#import "UIColor+OWS.h"
|
|
||||||
#import "UIFont+OWS.h"
|
|
||||||
#import "UIView+OWS.h"
|
|
||||||
#import <SessionCoreKit/NSDate+OWS.h>
|
|
||||||
#import <SignalMessaging/OWSNavigationController.h>
|
|
||||||
#import <SignalMessaging/OWSProfileManager.h>
|
|
||||||
#import <SignalMessaging/SignalMessaging-Swift.h>
|
|
||||||
#import <SignalMessaging/UIUtil.h>
|
|
||||||
#import <SignalMessaging/UIViewController+OWS.h>
|
|
||||||
#import <SessionServiceKit/NSString+SSK.h>
|
|
||||||
#import <SessionServiceKit/OWSPrimaryStorage.h>
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
typedef NS_ENUM(NSInteger, ProfileViewMode) {
|
|
||||||
ProfileViewMode_AppSettings = 0,
|
|
||||||
ProfileViewMode_Registration,
|
|
||||||
ProfileViewMode_UpgradeOrNag,
|
|
||||||
};
|
|
||||||
|
|
||||||
NSString *const kProfileView_Collection = @"kProfileView_Collection";
|
|
||||||
NSString *const kProfileView_LastPresentedDate = @"kProfileView_LastPresentedDate";
|
|
||||||
|
|
||||||
@interface ProfileViewController () <UITextFieldDelegate, AvatarViewHelperDelegate, OWSNavigationView>
|
|
||||||
|
|
||||||
@property (nonatomic, readonly) AvatarViewHelper *avatarViewHelper;
|
|
||||||
|
|
||||||
@property (nonatomic) UITextField *nameTextField;
|
|
||||||
|
|
||||||
@property (nonatomic) AvatarImageView *avatarView;
|
|
||||||
|
|
||||||
@property (nonatomic) UIImageView *cameraImageView;
|
|
||||||
|
|
||||||
@property (nonatomic) OWSFlatButton *saveButton;
|
|
||||||
|
|
||||||
@property (nonatomic, nullable) UIImage *avatar;
|
|
||||||
|
|
||||||
@property (nonatomic) BOOL hasUnsavedChanges;
|
|
||||||
|
|
||||||
@property (nonatomic) ProfileViewMode profileViewMode;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
#pragma mark -
|
|
||||||
|
|
||||||
@implementation ProfileViewController
|
|
||||||
|
|
||||||
- (instancetype)initWithMode:(ProfileViewMode)profileViewMode
|
|
||||||
{
|
|
||||||
self = [super init];
|
|
||||||
|
|
||||||
if (!self) {
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.profileViewMode = profileViewMode;
|
|
||||||
|
|
||||||
// Use the OWSPrimaryStorage.dbReadWriteConnection for consistency with the reads below.
|
|
||||||
[[[OWSPrimaryStorage sharedManager] dbReadWriteConnection] setDate:[NSDate new]
|
|
||||||
forKey:kProfileView_LastPresentedDate
|
|
||||||
inCollection:kProfileView_Collection];
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)loadView
|
|
||||||
{
|
|
||||||
[super loadView];
|
|
||||||
|
|
||||||
self.title = NSLocalizedString(@"PROFILE_VIEW_TITLE", @"Title for the profile view.");
|
|
||||||
|
|
||||||
_avatarViewHelper = [AvatarViewHelper new];
|
|
||||||
_avatarViewHelper.delegate = self;
|
|
||||||
|
|
||||||
_avatar = [OWSProfileManager.sharedManager localProfileAvatarImage];
|
|
||||||
|
|
||||||
[self createViews];
|
|
||||||
[self updateNavigationItem];
|
|
||||||
|
|
||||||
if (self.nameTextField.text.length > 0) {
|
|
||||||
self.hasUnsavedChanges = YES;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)createViews
|
|
||||||
{
|
|
||||||
self.view.backgroundColor = Theme.backgroundColor;
|
|
||||||
|
|
||||||
UIView *contentView = [UIView containerView];
|
|
||||||
contentView.backgroundColor = Theme.backgroundColor;
|
|
||||||
[self.view addSubview:contentView];
|
|
||||||
[contentView autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:self.view withOffset:0.0f];
|
|
||||||
[contentView autoPinWidthToSuperview];
|
|
||||||
|
|
||||||
const CGFloat fontSizePoints = ScaleFromIPhone5To7Plus(16.f, 20.f);
|
|
||||||
NSMutableArray<UIView *> *rows = [NSMutableArray new];
|
|
||||||
|
|
||||||
// Name
|
|
||||||
|
|
||||||
UIView *nameRow = [UIView containerView];
|
|
||||||
nameRow.userInteractionEnabled = YES;
|
|
||||||
[nameRow
|
|
||||||
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(nameRowTapped:)]];
|
|
||||||
nameRow.accessibilityIdentifier = ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"nameRow");
|
|
||||||
[rows addObject:nameRow];
|
|
||||||
|
|
||||||
UILabel *nameLabel = [UILabel new];
|
|
||||||
nameLabel.text = NSLocalizedString(@"Display Name", @"");
|
|
||||||
nameLabel.textColor = Theme.primaryColor;
|
|
||||||
nameLabel.font = [UIFont ows_regularFontWithSize:fontSizePoints];
|
|
||||||
[nameRow addSubview:nameLabel];
|
|
||||||
[nameLabel autoPinLeadingToSuperviewMargin];
|
|
||||||
[nameLabel autoPinHeightToSuperviewWithMargin:5.f];
|
|
||||||
|
|
||||||
UITextField *nameTextField;
|
|
||||||
if (UIDevice.currentDevice.isShorterThanIPhone5) {
|
|
||||||
nameTextField = [DismissableTextField new];
|
|
||||||
} else {
|
|
||||||
nameTextField = [OWSTextField new];
|
|
||||||
}
|
|
||||||
_nameTextField = nameTextField;
|
|
||||||
nameTextField.font = [UIFont ows_regularFontWithSize:18.f];
|
|
||||||
nameTextField.textColor = UIColor.whiteColor;
|
|
||||||
nameTextField.tintColor = UIColor.lokiGreen;
|
|
||||||
nameTextField.placeholder = NSLocalizedString(
|
|
||||||
@"PROFILE_VIEW_NAME_DEFAULT_TEXT", @"Default text for the profile name field of the profile view.");
|
|
||||||
nameTextField.delegate = self;
|
|
||||||
nameTextField.text = [OWSProfileManager.sharedManager localProfileName];
|
|
||||||
nameTextField.textAlignment = NSTextAlignmentRight;
|
|
||||||
nameTextField.font = [UIFont ows_regularFontWithSize:fontSizePoints];
|
|
||||||
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, nameTextField);
|
|
||||||
[nameTextField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
|
|
||||||
[nameRow addSubview:nameTextField];
|
|
||||||
[nameTextField autoPinLeadingToTrailingEdgeOfView:nameLabel offset:10.f];
|
|
||||||
[nameTextField autoPinTrailingToSuperviewMargin];
|
|
||||||
[nameTextField autoVCenterInSuperview];
|
|
||||||
|
|
||||||
// Avatar
|
|
||||||
|
|
||||||
UIView *avatarRow = [UIView containerView];
|
|
||||||
avatarRow.userInteractionEnabled = YES;
|
|
||||||
[avatarRow
|
|
||||||
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(avatarRowTapped:)]];
|
|
||||||
avatarRow.accessibilityIdentifier = ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"avatarRow");
|
|
||||||
|
|
||||||
[rows addObject:avatarRow];
|
|
||||||
|
|
||||||
UILabel *avatarLabel = [UILabel new];
|
|
||||||
avatarLabel.text = NSLocalizedString(@"Profile Picture", @"");
|
|
||||||
avatarLabel.textColor = Theme.primaryColor;
|
|
||||||
avatarLabel.font = [UIFont ows_regularFontWithSize:fontSizePoints];
|
|
||||||
[avatarRow addSubview:avatarLabel];
|
|
||||||
[avatarLabel autoPinLeadingToSuperviewMargin];
|
|
||||||
[avatarLabel autoVCenterInSuperview];
|
|
||||||
|
|
||||||
self.avatarView = [AvatarImageView new];
|
|
||||||
self.avatarView.accessibilityIdentifier = ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"avatarView");
|
|
||||||
|
|
||||||
UIImage *cameraImage = [UIImage imageNamed:@"settings-avatar-camera"];
|
|
||||||
self.cameraImageView = [[UIImageView alloc] initWithImage:cameraImage];
|
|
||||||
|
|
||||||
[avatarRow addSubview:self.avatarView];
|
|
||||||
[avatarRow addSubview:self.cameraImageView];
|
|
||||||
[self updateAvatarView];
|
|
||||||
[self.avatarView autoPinTrailingToSuperviewMargin];
|
|
||||||
[self.avatarView autoPinLeadingToTrailingEdgeOfView:avatarLabel offset:10.f];
|
|
||||||
const CGFloat kAvatarVMargin = 4.f;
|
|
||||||
[self.avatarView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:kAvatarVMargin];
|
|
||||||
[self.avatarView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:kAvatarVMargin];
|
|
||||||
[self.avatarView autoSetDimension:ALDimensionWidth toSize:self.avatarSize];
|
|
||||||
[self.avatarView autoSetDimension:ALDimensionHeight toSize:self.avatarSize];
|
|
||||||
[self.cameraImageView autoPinTrailingToEdgeOfView:self.avatarView];
|
|
||||||
[self.cameraImageView autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:self.avatarView];
|
|
||||||
|
|
||||||
// Information
|
|
||||||
|
|
||||||
// Loki: Original code
|
|
||||||
// ========
|
|
||||||
// UIView *infoRow = [UIView containerView];
|
|
||||||
// infoRow.userInteractionEnabled = YES;
|
|
||||||
// [infoRow
|
|
||||||
// addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(infoRowTapped:)]];
|
|
||||||
// infoRow.accessibilityIdentifier = ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"infoRow");
|
|
||||||
// [rows addObject:infoRow];
|
|
||||||
//
|
|
||||||
// UILabel *infoLabel = [UILabel new];
|
|
||||||
// infoLabel.textColor = Theme.secondaryColor;
|
|
||||||
// infoLabel.font = [UIFont ows_regularFontWithSize:11.f];
|
|
||||||
// infoLabel.textAlignment = NSTextAlignmentCenter;
|
|
||||||
// NSMutableAttributedString *text = [NSMutableAttributedString new];
|
|
||||||
// [text appendAttributedString:[[NSAttributedString alloc]
|
|
||||||
// initWithString:NSLocalizedString(@"PROFILE_VIEW_PROFILE_DESCRIPTION",
|
|
||||||
// @"Description of the user profile.")
|
|
||||||
// attributes:@{}]];
|
|
||||||
// [text appendAttributedString:[[NSAttributedString alloc] initWithString:@" " attributes:@{}]];
|
|
||||||
// [text appendAttributedString:[[NSAttributedString alloc]
|
|
||||||
// initWithString:NSLocalizedString(@"PROFILE_VIEW_PROFILE_DESCRIPTION_LINK",
|
|
||||||
// @"Link to more information about the user profile.")
|
|
||||||
// attributes:@{
|
|
||||||
// NSUnderlineStyleAttributeName :
|
|
||||||
// @(NSUnderlineStyleSingle | NSUnderlinePatternSolid),
|
|
||||||
// NSForegroundColorAttributeName : [UIColor ows_materialBlueColor],
|
|
||||||
// }]];
|
|
||||||
// infoLabel.attributedText = text;
|
|
||||||
// infoLabel.numberOfLines = 0;
|
|
||||||
// infoLabel.lineBreakMode = NSLineBreakByWordWrapping;
|
|
||||||
// [infoRow addSubview:infoLabel];
|
|
||||||
// [infoLabel autoPinLeadingToSuperviewMargin];
|
|
||||||
// [infoLabel autoPinTrailingToSuperviewMargin];
|
|
||||||
// [infoLabel autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:10.f];
|
|
||||||
// [infoLabel autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:10.f];
|
|
||||||
// ========
|
|
||||||
|
|
||||||
// Big Button
|
|
||||||
|
|
||||||
if (self.profileViewMode == ProfileViewMode_Registration || self.profileViewMode == ProfileViewMode_UpgradeOrNag) {
|
|
||||||
UIView *buttonRow = [UIView containerView];
|
|
||||||
[rows addObject:buttonRow];
|
|
||||||
|
|
||||||
const CGFloat kButtonHeight = 47.f;
|
|
||||||
// NOTE: We use ows_signalBrandBlueColor instead of ows_materialBlueColor
|
|
||||||
// throughout the onboarding flow to be consistent with the headers.
|
|
||||||
OWSFlatButton *saveButton =
|
|
||||||
[OWSFlatButton buttonWithTitle:NSLocalizedString(@"PROFILE_VIEW_SAVE_BUTTON",
|
|
||||||
@"Button to save the profile view in the profile view.")
|
|
||||||
font:[OWSFlatButton fontForHeight:kButtonHeight]
|
|
||||||
titleColor:[UIColor whiteColor]
|
|
||||||
backgroundColor:[UIColor ows_signalBrandBlueColor]
|
|
||||||
target:self
|
|
||||||
selector:@selector(saveButtonPressed)];
|
|
||||||
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, saveButton);
|
|
||||||
self.saveButton = saveButton;
|
|
||||||
[buttonRow addSubview:saveButton];
|
|
||||||
[saveButton autoPinLeadingAndTrailingToSuperviewMargin];
|
|
||||||
[saveButton autoPinHeightToSuperview];
|
|
||||||
[saveButton autoSetDimension:ALDimensionHeight toSize:47.f];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Row Layout
|
|
||||||
|
|
||||||
UIView *_Nullable lastRow = nil;
|
|
||||||
for (UIView *row in rows) {
|
|
||||||
[contentView addSubview:row];
|
|
||||||
if (lastRow) {
|
|
||||||
[row autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:lastRow withOffset:5.f];
|
|
||||||
} else {
|
|
||||||
[row autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:15.f];
|
|
||||||
}
|
|
||||||
[row autoPinLeadingToSuperviewMarginWithInset:18.f];
|
|
||||||
[row autoPinTrailingToSuperviewMarginWithInset:18.f];
|
|
||||||
lastRow = row;
|
|
||||||
|
|
||||||
if (lastRow == nameRow || lastRow == avatarRow) {
|
|
||||||
UIView *separator = [UIView containerView];
|
|
||||||
separator.backgroundColor = Theme.cellSeparatorColor;
|
|
||||||
[contentView addSubview:separator];
|
|
||||||
[separator autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:lastRow withOffset:5.f];
|
|
||||||
[separator autoPinLeadingToSuperviewMarginWithInset:18.f];
|
|
||||||
[separator autoPinTrailingToSuperviewMarginWithInset:18.f];
|
|
||||||
[separator autoSetDimension:ALDimensionHeight toSize:CGHairlineWidth()];
|
|
||||||
lastRow = separator;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[lastRow autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:10.f];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)viewWillAppear:(BOOL)animated
|
|
||||||
{
|
|
||||||
[super viewWillAppear:animated];
|
|
||||||
|
|
||||||
[self.nameTextField becomeFirstResponder];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Event Handling
|
|
||||||
|
|
||||||
- (void)backOrSkipButtonPressed
|
|
||||||
{
|
|
||||||
[self leaveViewCheckingForUnsavedChanges];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)leaveViewCheckingForUnsavedChanges
|
|
||||||
{
|
|
||||||
[self.nameTextField resignFirstResponder];
|
|
||||||
|
|
||||||
if (!self.hasUnsavedChanges) {
|
|
||||||
// If user made no changes, return to conversation settings view.
|
|
||||||
[self profileCompletedOrSkipped];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
__weak ProfileViewController *weakSelf = self;
|
|
||||||
UIAlertController *alert = [UIAlertController
|
|
||||||
alertControllerWithTitle:
|
|
||||||
NSLocalizedString(@"NEW_GROUP_VIEW_UNSAVED_CHANGES_TITLE",
|
|
||||||
@"The alert title if user tries to exit the new group view without saving changes.")
|
|
||||||
message:
|
|
||||||
NSLocalizedString(@"NEW_GROUP_VIEW_UNSAVED_CHANGES_MESSAGE",
|
|
||||||
@"The alert message if user tries to exit the new group view without saving changes.")
|
|
||||||
preferredStyle:UIAlertControllerStyleAlert];
|
|
||||||
UIAlertAction *discardAction =
|
|
||||||
[UIAlertAction actionWithTitle:NSLocalizedString(@"ALERT_DISCARD_BUTTON",
|
|
||||||
@"The label for the 'discard' button in alerts and action sheets.")
|
|
||||||
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"discard")
|
|
||||||
style:UIAlertActionStyleDestructive
|
|
||||||
handler:^(UIAlertAction *action) {
|
|
||||||
[weakSelf profileCompletedOrSkipped];
|
|
||||||
}];
|
|
||||||
[alert addAction:discardAction];
|
|
||||||
|
|
||||||
[alert addAction:[OWSAlerts cancelAction]];
|
|
||||||
[self presentAlert:alert];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)avatarTapped
|
|
||||||
{
|
|
||||||
[self.avatarViewHelper showChangeAvatarUI];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setHasUnsavedChanges:(BOOL)hasUnsavedChanges
|
|
||||||
{
|
|
||||||
_hasUnsavedChanges = hasUnsavedChanges;
|
|
||||||
|
|
||||||
[self updateNavigationItem];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateNavigationItem
|
|
||||||
{
|
|
||||||
// The navigation bar is hidden in the registration workflow.
|
|
||||||
if (self.navigationController.navigationBarHidden) {
|
|
||||||
[self.navigationController setNavigationBarHidden:NO animated:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always display a left item to leave the view without making changes.
|
|
||||||
// This might be a "back", "skip" or "cancel" button depending on the
|
|
||||||
// context.
|
|
||||||
switch (self.profileViewMode) {
|
|
||||||
case ProfileViewMode_AppSettings:
|
|
||||||
if (self.hasUnsavedChanges) {
|
|
||||||
// If we have a unsaved changes, right item should be a "save" button.
|
|
||||||
UIBarButtonItem *saveButton =
|
|
||||||
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave
|
|
||||||
target:self
|
|
||||||
action:@selector(updatePressed)];
|
|
||||||
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, saveButton);
|
|
||||||
self.navigationItem.rightBarButtonItem = saveButton;
|
|
||||||
} else {
|
|
||||||
self.navigationItem.rightBarButtonItem = nil;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ProfileViewMode_UpgradeOrNag:
|
|
||||||
case ProfileViewMode_Registration:
|
|
||||||
self.navigationItem.hidesBackButton = YES;
|
|
||||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
|
|
||||||
initWithTitle:NSLocalizedString(@"NAVIGATION_ITEM_SKIP_BUTTON", @"A button to skip a view.")
|
|
||||||
style:UIBarButtonItemStylePlain
|
|
||||||
target:self
|
|
||||||
action:@selector(backOrSkipButtonPressed)];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The save button is only used in "registration" and "upgrade or nag" modes.
|
|
||||||
if (self.hasUnsavedChanges) {
|
|
||||||
self.saveButton.enabled = YES;
|
|
||||||
[self.saveButton setBackgroundColorsWithUpColor:[UIColor ows_signalBrandBlueColor]];
|
|
||||||
} else {
|
|
||||||
self.saveButton.enabled = NO;
|
|
||||||
[self.saveButton
|
|
||||||
setBackgroundColorsWithUpColor:[[UIColor ows_signalBrandBlueColor] blendWithColor:Theme.backgroundColor
|
|
||||||
alpha:0.5f]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updatePressed
|
|
||||||
{
|
|
||||||
[self updateProfile];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateProfile
|
|
||||||
{
|
|
||||||
__weak ProfileViewController *weakSelf = self;
|
|
||||||
|
|
||||||
NSString *normalizedProfileName = [self normalizedProfileName];
|
|
||||||
|
|
||||||
if (normalizedProfileName.length == 0) {
|
|
||||||
return [OWSAlerts showErrorAlertWithMessage:NSLocalizedString(@"Please pick a display name", @"")];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSCharacterSet *allowedCharacters = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_ "];
|
|
||||||
if ([normalizedProfileName rangeOfCharacterFromSet:allowedCharacters.invertedSet].location != NSNotFound) {
|
|
||||||
return [OWSAlerts showErrorAlertWithMessage:NSLocalizedString(@"Please pick a display name that consists of only a-z, A-Z, 0-9 and _ characters", @"")];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([OWSProfileManager.sharedManager isProfileNameTooLong:normalizedProfileName]) {
|
|
||||||
return [OWSAlerts showErrorAlertWithMessage:NSLocalizedString(@"Please pick a shorter display name", @"")];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show an activity indicator to block the UI during the profile upload.
|
|
||||||
[ModalActivityIndicatorViewController
|
|
||||||
presentFromViewController:self
|
|
||||||
canCancel:NO
|
|
||||||
backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) {
|
|
||||||
[OWSProfileManager.sharedManager updateLocalProfileName:normalizedProfileName
|
|
||||||
avatarImage:weakSelf.avatar
|
|
||||||
success:^{
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
[modalActivityIndicator dismissWithCompletion:^{
|
|
||||||
[weakSelf updateProfileCompleted];
|
|
||||||
}];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
failure:^(NSError *error) {
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
[modalActivityIndicator dismissWithCompletion:^{
|
|
||||||
[OWSAlerts showErrorAlertWithMessage:NSLocalizedString(
|
|
||||||
@"PROFILE_VIEW_ERROR_UPDATE_FAILED",
|
|
||||||
@"Error message shown when a "
|
|
||||||
@"profile update fails.")];
|
|
||||||
}];
|
|
||||||
});
|
|
||||||
} requiresSync:NO];
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)normalizedProfileName
|
|
||||||
{
|
|
||||||
return [self.nameTextField.text ows_stripped];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateProfileCompleted
|
|
||||||
{
|
|
||||||
OWSLogVerbose(@"");
|
|
||||||
|
|
||||||
[self profileCompletedOrSkipped];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)profileCompletedOrSkipped
|
|
||||||
{
|
|
||||||
OWSLogVerbose(@"");
|
|
||||||
|
|
||||||
// Dismiss this view.
|
|
||||||
switch (self.profileViewMode) {
|
|
||||||
case ProfileViewMode_AppSettings:
|
|
||||||
[self.navigationController popViewControllerAnimated:YES];
|
|
||||||
break;
|
|
||||||
case ProfileViewMode_Registration:
|
|
||||||
[self showHomeView];
|
|
||||||
break;
|
|
||||||
case ProfileViewMode_UpgradeOrNag:
|
|
||||||
[self dismissViewControllerAnimated:YES completion:nil];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)showHomeView
|
|
||||||
{
|
|
||||||
OWSAssertIsOnMainThread();
|
|
||||||
OWSLogVerbose(@"");
|
|
||||||
|
|
||||||
[SignalApp.sharedApp showHomeView];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - UITextFieldDelegate
|
|
||||||
|
|
||||||
- (BOOL)textField:(UITextField *)textField
|
|
||||||
shouldChangeCharactersInRange:(NSRange)editingRange
|
|
||||||
replacementString:(NSString *)insertionText
|
|
||||||
{
|
|
||||||
// TODO: Possibly filter invalid input.
|
|
||||||
return [TextFieldHelper textField:textField
|
|
||||||
shouldChangeCharactersInRange:editingRange
|
|
||||||
replacementString:insertionText
|
|
||||||
byteLimit:kOWSProfileManager_NameDataLength];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)textFieldShouldReturn:(UITextField *)textField
|
|
||||||
{
|
|
||||||
[self updateProfile];
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)textFieldDidChange:(id)sender
|
|
||||||
{
|
|
||||||
self.hasUnsavedChanges = YES;
|
|
||||||
|
|
||||||
// TODO: Update length warning.
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Avatar
|
|
||||||
|
|
||||||
- (void)setAvatar:(nullable UIImage *)avatar
|
|
||||||
{
|
|
||||||
OWSAssertIsOnMainThread();
|
|
||||||
|
|
||||||
_avatar = avatar;
|
|
||||||
|
|
||||||
self.hasUnsavedChanges = YES;
|
|
||||||
|
|
||||||
[self updateAvatarView];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSUInteger)avatarSize
|
|
||||||
{
|
|
||||||
return 48;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateAvatarView
|
|
||||||
{
|
|
||||||
self.avatarView.image = (self.avatar
|
|
||||||
?: [[[OWSContactAvatarBuilder alloc] initForLocalUserWithDiameter:self.avatarSize] buildDefaultImage]);
|
|
||||||
self.cameraImageView.hidden = self.avatar != nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)nameRowTapped:(UIGestureRecognizer *)sender
|
|
||||||
{
|
|
||||||
if (sender.state == UIGestureRecognizerStateRecognized) {
|
|
||||||
[self.nameTextField becomeFirstResponder];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)avatarRowTapped:(UIGestureRecognizer *)sender
|
|
||||||
{
|
|
||||||
if (sender.state == UIGestureRecognizerStateRecognized) {
|
|
||||||
[self avatarTapped];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)infoRowTapped:(UIGestureRecognizer *)sender
|
|
||||||
{
|
|
||||||
if (sender.state == UIGestureRecognizerStateRecognized) {
|
|
||||||
[UIApplication.sharedApplication
|
|
||||||
openURL:[NSURL URLWithString:@"https://support.signal.org/hc/en-us/articles/115001110511"]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)saveButtonPressed
|
|
||||||
{
|
|
||||||
[self updatePressed];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - AvatarViewHelperDelegate
|
|
||||||
|
|
||||||
+ (BOOL)shouldDisplayProfileViewOnLaunch
|
|
||||||
{
|
|
||||||
// Only nag until the user sets a profile _name_. Profile names are
|
|
||||||
// recommended; profile avatars are optional.
|
|
||||||
if ([OWSProfileManager sharedManager].localProfileName.length > 0) {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the OWSPrimaryStorage.dbReadWriteConnection for consistency with the writes above.
|
|
||||||
NSTimeInterval kProfileNagFrequency = kDayInterval * 30;
|
|
||||||
NSDate *_Nullable lastPresentedDate =
|
|
||||||
[[[OWSPrimaryStorage sharedManager] dbReadWriteConnection] dateForKey:kProfileView_LastPresentedDate
|
|
||||||
inCollection:kProfileView_Collection];
|
|
||||||
return (!lastPresentedDate || fabs([lastPresentedDate timeIntervalSinceNow]) > kProfileNagFrequency);
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (void)presentForAppSettings:(UINavigationController *)navigationController
|
|
||||||
{
|
|
||||||
OWSAssertDebug(navigationController);
|
|
||||||
OWSAssertDebug([navigationController isKindOfClass:[OWSNavigationController class]]);
|
|
||||||
|
|
||||||
ProfileViewController *vc = [[ProfileViewController alloc] initWithMode:ProfileViewMode_AppSettings];
|
|
||||||
[navigationController pushViewController:vc animated:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (void)presentForRegistration:(UINavigationController *)navigationController
|
|
||||||
{
|
|
||||||
OWSAssertDebug(navigationController);
|
|
||||||
OWSAssertDebug([navigationController isKindOfClass:[OWSNavigationController class]]);
|
|
||||||
|
|
||||||
ProfileViewController *vc = [[ProfileViewController alloc] initWithMode:ProfileViewMode_Registration];
|
|
||||||
[navigationController pushViewController:vc animated:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (void)presentForUpgradeOrNag:(HomeViewController *)fromViewController
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - AvatarViewHelperDelegate
|
|
||||||
|
|
||||||
- (nullable NSString *)avatarActionSheetTitle
|
|
||||||
{
|
|
||||||
return NSLocalizedString(@"Set Profile Picture", @"");
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)avatarDidChange:(UIImage *)image
|
|
||||||
{
|
|
||||||
OWSAssertIsOnMainThread();
|
|
||||||
OWSAssertDebug(image);
|
|
||||||
|
|
||||||
self.avatar = [image resizedImageToFillPixelSize:CGSizeMake(kOWSProfileManager_MaxAvatarDiameter,
|
|
||||||
kOWSProfileManager_MaxAvatarDiameter)];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (UIViewController *)fromViewController
|
|
||||||
{
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)hasClearAvatarAction
|
|
||||||
{
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)clearAvatarActionLabel
|
|
||||||
{
|
|
||||||
return NSLocalizedString(@"Clear Profile Picture", @"");
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)clearAvatar
|
|
||||||
{
|
|
||||||
self.avatar = nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - OWSNavigationView
|
|
||||||
|
|
||||||
- (BOOL)shouldCancelNavigationBack
|
|
||||||
{
|
|
||||||
BOOL result = self.hasUnsavedChanges;
|
|
||||||
if (result) {
|
|
||||||
[self backOrSkipButtonPressed];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Orientation
|
|
||||||
|
|
||||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
|
|
||||||
{
|
|
||||||
return (self.profileViewMode == ProfileViewMode_Registration ? UIInterfaceOrientationMaskPortrait
|
|
||||||
: DefaultUIInterfaceOrientationMask());
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
@ -1,111 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
/*
|
|
||||||
import UIKit
|
|
||||||
import PromiseKit
|
|
||||||
import Contacts
|
|
||||||
|
|
||||||
@objc
|
|
||||||
public class OnboardingPermissionsViewController: OnboardingBaseViewController {
|
|
||||||
|
|
||||||
override public func loadView() {
|
|
||||||
super.loadView()
|
|
||||||
|
|
||||||
view.backgroundColor = Theme.backgroundColor
|
|
||||||
view.layoutMargins = .zero
|
|
||||||
|
|
||||||
navigationItem.rightBarButtonItem = UIBarButtonItem(title: NSLocalizedString("NAVIGATION_ITEM_SKIP_BUTTON", comment: "A button to skip a view."),
|
|
||||||
style: .plain,
|
|
||||||
target: self,
|
|
||||||
action: #selector(skipWasPressed))
|
|
||||||
|
|
||||||
let titleLabel = self.createTitleLabel(text: NSLocalizedString("Loki Messenger can let you know when you get a message (and who it is from)", comment: ""))
|
|
||||||
titleLabel.accessibilityIdentifier = "onboarding.permissions." + "titleLabel"
|
|
||||||
|
|
||||||
let explanationLabel = self.createExplanationLabel(text: NSLocalizedString("ONBOARDING_PERMISSIONS_EXPLANATION",
|
|
||||||
comment: "Explanation in the 'onboarding permissions' view."))
|
|
||||||
explanationLabel.accessibilityIdentifier = "onboarding.permissions." + "explanationLabel"
|
|
||||||
|
|
||||||
let giveAccessButton = self.createButton(title: NSLocalizedString("ONBOARDING_PERMISSIONS_ENABLE_PERMISSIONS_BUTTON",
|
|
||||||
comment: "Label for the 'give access' button in the 'onboarding permissions' view."),
|
|
||||||
selector: #selector(giveAccessPressed))
|
|
||||||
giveAccessButton.accessibilityIdentifier = "onboarding.permissions." + "giveAccessButton"
|
|
||||||
|
|
||||||
let notNowButton = self.createLinkButton(title: NSLocalizedString("ONBOARDING_PERMISSIONS_NOT_NOW_BUTTON",
|
|
||||||
comment: "Label for the 'not now' button in the 'onboarding permissions' view."),
|
|
||||||
selector: #selector(notNowPressed))
|
|
||||||
notNowButton.accessibilityIdentifier = "onboarding.permissions." + "notNowButton"
|
|
||||||
|
|
||||||
let stackView = UIStackView(arrangedSubviews: [
|
|
||||||
titleLabel,
|
|
||||||
UIView.spacer(withHeight: 20),
|
|
||||||
explanationLabel,
|
|
||||||
UIView.vStretchingSpacer(),
|
|
||||||
notNowButton,
|
|
||||||
UIView.spacer(withHeight: 12),
|
|
||||||
giveAccessButton
|
|
||||||
])
|
|
||||||
stackView.axis = .vertical
|
|
||||||
stackView.alignment = .fill
|
|
||||||
stackView.layoutMargins = UIEdgeInsets(top: 32, left: 32, bottom: 32, right: 32)
|
|
||||||
stackView.isLayoutMarginsRelativeArrangement = true
|
|
||||||
view.addSubview(stackView)
|
|
||||||
stackView.autoPinWidthToSuperview()
|
|
||||||
stackView.autoPinEdge(.top, to: .top, of: view)
|
|
||||||
stackView.autoPinEdge(.bottom, to: .bottom, of: view)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Request Access
|
|
||||||
|
|
||||||
private func requestAccess() {
|
|
||||||
Logger.info("")
|
|
||||||
|
|
||||||
requestContactsAccess().then { _ in
|
|
||||||
return PushRegistrationManager.shared.registerUserNotificationSettings()
|
|
||||||
}.done { [weak self] in
|
|
||||||
guard let self = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.onboardingController.onboardingPermissionsDidComplete(viewController: self)
|
|
||||||
}.retainUntilComplete()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func requestContactsAccess() -> Promise<Void> {
|
|
||||||
Logger.info("")
|
|
||||||
|
|
||||||
let (promise, resolver) = Promise<Void>.pending()
|
|
||||||
CNContactStore().requestAccess(for: CNEntityType.contacts) { (granted, error) -> Void in
|
|
||||||
if granted {
|
|
||||||
Logger.info("Granted.")
|
|
||||||
} else {
|
|
||||||
Logger.error("Error: \(String(describing: error)).")
|
|
||||||
}
|
|
||||||
// Always fulfill.
|
|
||||||
resolver.fulfill(())
|
|
||||||
}
|
|
||||||
return promise
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Events
|
|
||||||
|
|
||||||
@objc func skipWasPressed() {
|
|
||||||
Logger.info("")
|
|
||||||
|
|
||||||
onboardingController.onboardingPermissionsWasSkipped(viewController: self)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func giveAccessPressed() {
|
|
||||||
Logger.info("")
|
|
||||||
|
|
||||||
requestAccess()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func notNowPressed() {
|
|
||||||
Logger.info("")
|
|
||||||
|
|
||||||
onboardingController.onboardingPermissionsWasSkipped(viewController: self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
Loading…
Reference in New Issue