session-ios/Signal/src/ViewControllers/ProfileViewController.m

672 lines
25 KiB
Mathematica
Raw Normal View History

//
2018-03-01 20:42:54 +01:00
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "ProfileViewController.h"
#import "AppDelegate.h"
#import "AvatarViewHelper.h"
2017-09-06 19:59:39 +02:00
#import "HomeViewController.h"
#import "OWSNavigationController.h"
#import "Signal-Swift.h"
#import "SignalsNavigationController.h"
#import "UIColor+OWS.h"
#import "UIFont+OWS.h"
#import "UIView+OWS.h"
2018-09-21 21:41:10 +02:00
#import <SignalCoreKit/NSDate+OWS.h>
2017-12-19 03:50:51 +01:00
#import <SignalMessaging/NSString+OWS.h>
#import <SignalMessaging/OWSNavigationController.h>
#import <SignalMessaging/OWSProfileManager.h>
2018-03-01 20:42:54 +01:00
#import <SignalMessaging/UIViewController+OWS.h>
#import <SignalServiceKit/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;
2017-08-21 21:00:27 +02:00
@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
{
2018-08-08 15:47:54 +02:00
self.view.backgroundColor = Theme.offBackgroundColor;
2017-08-21 21:00:27 +02:00
UIView *contentView = [UIView containerView];
2018-08-08 15:37:23 +02:00
contentView.backgroundColor = Theme.backgroundColor;
2017-08-21 21:00:27 +02:00
[self.view addSubview:contentView];
[contentView autoPinToTopLayoutGuideOfViewController:self withInset:0];
2017-08-21 21:00:27 +02:00
[contentView autoPinWidthToSuperview];
2017-08-21 21:00:27 +02:00
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:)]];
[rows addObject:nameRow];
UILabel *nameLabel = [UILabel new];
nameLabel.text = NSLocalizedString(
@"PROFILE_VIEW_PROFILE_NAME_FIELD", @"Label for the profile name field of the profile view.");
2018-08-08 15:37:23 +02:00
nameLabel.textColor = Theme.primaryColor;
2017-08-21 21:00:27 +02:00
nameLabel.font = [UIFont ows_mediumFontWithSize:fontSizePoints];
[nameRow addSubview:nameLabel];
[nameLabel autoPinLeadingToSuperviewMargin];
2017-08-21 21:00:27 +02:00
[nameLabel autoPinHeightToSuperviewWithMargin:5.f];
UITextField *nameTextField;
if (UIDevice.currentDevice.isShorterThanIPhone5) {
nameTextField = [DismissableTextField new];
} else {
2018-08-15 23:09:59 +02:00
nameTextField = [OWSTextField new];
}
2017-08-21 21:00:27 +02:00
_nameTextField = nameTextField;
nameTextField.font = [UIFont ows_mediumFontWithSize:18.f];
nameTextField.textColor = [UIColor ows_materialBlueColor];
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_mediumFontWithSize:fontSizePoints];
[nameTextField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
[nameRow addSubview:nameTextField];
[nameTextField autoPinLeadingToTrailingEdgeOfView:nameLabel offset:10.f];
[nameTextField autoPinTrailingToSuperviewMargin];
2017-08-21 21:00:27 +02:00
[nameTextField autoVCenterInSuperview];
// Avatar
UIView *avatarRow = [UIView containerView];
2017-08-21 21:09:53 +02:00
avatarRow.userInteractionEnabled = YES;
[avatarRow
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(avatarRowTapped:)]];
2017-08-21 21:00:27 +02:00
[rows addObject:avatarRow];
UILabel *avatarLabel = [UILabel new];
avatarLabel.text = NSLocalizedString(
@"PROFILE_VIEW_PROFILE_AVATAR_FIELD", @"Label for the profile avatar field of the profile view.");
2018-08-08 15:37:23 +02:00
avatarLabel.textColor = Theme.primaryColor;
2017-08-21 21:00:27 +02:00
avatarLabel.font = [UIFont ows_mediumFontWithSize:fontSizePoints];
[avatarRow addSubview:avatarLabel];
[avatarLabel autoPinLeadingToSuperviewMargin];
2017-08-21 21:00:27 +02:00
[avatarLabel autoVCenterInSuperview];
self.avatarView = [AvatarImageView new];
UIImage *cameraImage = [UIImage imageNamed:@"settings-avatar-camera"];
2017-08-21 21:00:27 +02:00
self.cameraImageView = [[UIImageView alloc] initWithImage:cameraImage];
2017-08-21 18:22:12 +02:00
2017-08-21 21:00:27 +02:00
[avatarRow addSubview:self.avatarView];
[avatarRow addSubview:self.cameraImageView];
[self updateAvatarView];
[self.avatarView autoPinTrailingToSuperviewMargin];
[self.avatarView autoPinLeadingToTrailingEdgeOfView:avatarLabel offset:10.f];
2017-08-21 18:22:12 +02:00
const CGFloat kAvatarVMargin = 4.f;
2017-08-21 21:00:27 +02:00
[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];
2017-08-21 21:00:27 +02:00
[self.cameraImageView autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:self.avatarView];
// Information
UIView *infoRow = [UIView containerView];
infoRow.userInteractionEnabled = YES;
[infoRow
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(infoRowTapped:)]];
[rows addObject:infoRow];
UILabel *infoLabel = [UILabel new];
2018-08-08 15:37:23 +02:00
infoLabel.textColor = Theme.secondaryColor;
2018-04-09 22:13:48 +02:00
infoLabel.font = [UIFont ows_regularFontWithSize:11.f];
2017-08-21 21:00:27 +02:00
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];
2017-08-21 21:00:27 +02:00
[infoLabel autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:10.f];
[infoLabel autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:10.f];
// Big Button
if (self.profileViewMode == ProfileViewMode_Registration || self.profileViewMode == ProfileViewMode_UpgradeOrNag) {
2017-08-21 21:00:27 +02:00
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)];
self.saveButton = saveButton;
[buttonRow addSubview:saveButton];
[saveButton autoPinLeadingAndTrailingToSuperviewMargin];
[saveButton autoPinHeightToSuperview];
[saveButton autoSetDimension:ALDimensionHeight toSize:47.f];
2017-08-21 18:22:12 +02:00
}
2017-08-21 21:00:27 +02:00
// 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];
2017-08-21 21:00:27 +02:00
lastRow = row;
if (lastRow == nameRow || lastRow == avatarRow) {
UIView *separator = [UIView containerView];
2018-08-10 00:43:25 +02:00
separator.backgroundColor = Theme.cellSeparatorColor;
2018-08-08 15:47:54 +02:00
[contentView addSubview:separator];
2017-08-21 21:00:27 +02:00
[separator autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:lastRow withOffset:5.f];
[separator autoPinLeadingToSuperviewMarginWithInset:18.f];
[separator autoPinTrailingToSuperviewMarginWithInset:18.f];
2018-08-10 00:43:25 +02:00
[separator autoSetDimension:ALDimensionHeight toSize:CGHairlineWidth()];
2017-08-21 21:00:27 +02:00
lastRow = separator;
}
}
2017-08-21 21:00:27 +02:00
[lastRow autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:10.f];
}
2017-08-21 21:00:27 +02:00
- (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;
}
UIAlertController *controller = [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];
[controller
addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ALERT_DISCARD_BUTTON",
@"The label for the 'discard' button in alerts and action sheets.")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *action) {
[self profileCompletedOrSkipped];
}]];
[controller addAction:[OWSAlerts cancelAction]];
[self presentViewController:controller animated:YES completion:nil];
}
2017-08-21 18:22:12 +02:00
- (void)avatarTapped
{
2017-08-21 18:22:12 +02:00
[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.
self.navigationItem.rightBarButtonItem =
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave
target:self
action:@selector(updatePressed)];
} 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;
}
2017-08-21 21:03:26 +02:00
// The save button is only used in "registration" and "upgrade or nag" modes.
if (self.hasUnsavedChanges) {
self.saveButton.enabled = YES;
2017-09-18 21:47:59 +02:00
[self.saveButton setBackgroundColorsWithUpColor:[UIColor ows_signalBrandBlueColor]];
2017-08-21 21:03:26 +02:00
} else {
self.saveButton.enabled = NO;
[self.saveButton
2018-08-08 15:37:23 +02:00
setBackgroundColorsWithUpColor:[[UIColor ows_signalBrandBlueColor] blendWithColor:Theme.backgroundColor
2017-09-18 21:47:59 +02:00
alpha:0.5f]];
}
}
- (void)updatePressed
{
[self updateProfile];
}
- (void)updateProfile
{
__weak ProfileViewController *weakSelf = self;
NSString *normalizedProfileName = [self normalizedProfileName];
if ([OWSProfileManager.sharedManager isProfileNameTooLong:normalizedProfileName]) {
2018-03-06 14:29:25 +01:00
[OWSAlerts
showErrorAlertWithMessage:NSLocalizedString(@"PROFILE_VIEW_ERROR_PROFILE_NAME_TOO_LONG",
@"Error message shown when user tries to update profile with a profile name "
@"that is too long.")];
return;
}
// Show an activity indicator to block the UI during the profile upload.
2017-08-16 22:10:07 +02:00
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:NSLocalizedString(@"PROFILE_VIEW_SAVING",
@"Alert title that indicates the user's profile view is being saved.")
message:nil
preferredStyle:UIAlertControllerStyleAlert];
[self presentViewController:alertController
animated:YES
completion:^{
[OWSProfileManager.sharedManager updateLocalProfileName:normalizedProfileName
avatarImage:self.avatar
success:^{
2017-08-16 22:10:07 +02:00
[alertController dismissViewControllerAnimated:NO
completion:^{
[weakSelf updateProfileCompleted];
2017-08-16 22:10:07 +02:00
}];
}
failure:^{
2017-08-16 22:10:07 +02:00
[alertController
dismissViewControllerAnimated:NO
completion:^{
2018-03-06 14:29:25 +01:00
[OWSAlerts showErrorAlertWithMessage:
NSLocalizedString(
@"PROFILE_VIEW_ERROR_UPDATE_FAILED",
@"Error message shown when a "
@"profile update fails.")];
2017-08-16 22:10:07 +02:00
}];
}];
}];
}
- (NSString *)normalizedProfileName
{
2017-10-18 20:53:31 +02:00
return [self.nameTextField.text ows_stripped];
}
- (void)updateProfileCompleted
{
[self profileCompletedOrSkipped];
}
- (void)profileCompletedOrSkipped
{
// Dismiss this view.
switch (self.profileViewMode) {
case ProfileViewMode_AppSettings:
[self.navigationController popViewControllerAnimated:YES];
break;
case ProfileViewMode_Registration:
if (![TSAccountManager sharedInstance].isReregistering) {
[self checkCanImportBackup];
return;
}
[self showHomeView];
break;
case ProfileViewMode_UpgradeOrNag:
[self dismissViewControllerAnimated:YES completion:nil];
break;
}
}
- (void)showHomeView
{
2018-11-19 23:35:35 +01:00
[SignalApp.sharedApp showHomeView];
}
- (void)showBackupRestoreView
{
2018-11-19 23:35:35 +01:00
BackupRestoreViewController *restoreView = [BackupRestoreViewController new];
[self.navigationController setViewControllers:@[
restoreView,
]
animated:YES];
}
- (void)checkCanImportBackup
{
[OWSBackup.sharedManager
checkCanImportBackup:^(BOOL value) {
OWSLogInfo(@"has backup available for import? %d", value);
if (value) {
[self showBackupRestoreView];
} else {
[self showHomeView];
}
}
failure:^(NSError *error) {
UIAlertController *controller = [UIAlertController
alertControllerWithTitle:
NSLocalizedString(@"CHECK_FOR_BACKUP_FAILED_TITLE",
@"Title for alert shown when the app failed to check for an existing backup.")
message:NSLocalizedString(@"CHECK_FOR_BACKUP_FAILED_MESSAGE",
@"Message for alert shown when the app failed to check for an existing "
@"backup.")
preferredStyle:UIAlertControllerStyleAlert];
[controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"REGISTER_FAILED_TRY_AGAIN", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[self checkCanImportBackup];
}]];
[controller
addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"CHECK_FOR_BACKUP_DO_NOT_RESTORE",
@"The label for the 'do not restore backup' button.")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *action) {
[self showHomeView];
}]];
[self presentViewController:controller animated:YES completion:nil];
}];
}
#pragma mark - UITextFieldDelegate
- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)editingRange
replacementString:(NSString *)insertionText
{
2017-08-01 17:34:23 +02:00
// TODO: Possibly filter invalid input.
return [TextFieldHelper textField:textField
shouldChangeCharactersInRange:editingRange
replacementString:insertionText
byteLimit:kOWSProfileManager_NameDataLength];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
2017-08-01 17:34:23 +02:00
[self updateProfile];
return NO;
}
- (void)textFieldDidChange:(id)sender
{
self.hasUnsavedChanges = YES;
2017-08-01 17:34:23 +02:00
// TODO: Update length warning.
}
#pragma mark - Avatar
- (void)setAvatar:(nullable UIImage *)avatar
{
2017-12-19 17:38:25 +01:00
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;
}
2017-08-21 21:00:27 +02:00
- (void)nameRowTapped:(UIGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateRecognized) {
[self.nameTextField becomeFirstResponder];
}
}
2017-08-21 21:09:53 +02:00
- (void)avatarRowTapped:(UIGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateRecognized) {
[self avatarTapped];
}
}
2017-08-21 21:00:27 +02:00
- (void)infoRowTapped:(UIGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateRecognized) {
[UIApplication.sharedApplication
2017-09-08 18:56:53 +02:00
openURL:[NSURL URLWithString:@"https://support.signal.org/hc/en-us/articles/115001110511"]];
2017-08-21 21:00:27 +02:00
}
}
- (void)saveButtonPressed
2017-08-21 21:00:27 +02:00
{
[self updatePressed];
2017-08-21 21:00:27 +02:00
}
#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];
}
Faster conversation presentation. There are multiple places in the codebase we present a conversation. We used to have some very conservative machinery around how this was done, for fear of failing to present the call view controller, which would have left a hidden call in the background. We've since addressed that concern more thoroughly via the separate calling UIWindow. As such, the remaining presentation machinery is overly complex and inflexible for what we need. Sometimes we want to animate-push the conversation. (tap on home, tap on "send message" in contact card/group members) Sometimes we want to dismiss a modal, to reveal the conversation behind it (contact picker, group creation) Sometimes we want to present the conversation with no animation (becoming active from a notification) We also want to ensure that we're never pushing more than one conversation view controller, which was previously a problem since we were "pushing" a newly constructed VC in response to these myriad actions. It turned out there were certain code paths that caused multiple actions to be fired in rapid succession which pushed multiple ConversationVC's. The built-in method: `setViewControllers:animated` easily ensures we only have one ConversationVC on the stack, while being composable enough to faciliate the various more efficient animations we desire. The only thing lost with the complex methods is that the naive `presentViewController:` can fail, e.g. if another view is already presented. E.g. if an alert appears *just* before the user taps compose, the contact picker will fail to present. Since we no longer depend on this for presenting the CallViewController, this isn't catostrophic, and in fact, arguable preferable, since we want the user to read and dismiss any alert explicitly. // FREEBIE
2018-08-18 22:54:35 +02:00
+ (void)presentForUpgradeOrNag:(HomeViewController *)fromViewController
{
OWSAssertDebug(fromViewController);
ProfileViewController *vc = [[ProfileViewController alloc] initWithMode:ProfileViewMode_UpgradeOrNag];
OWSNavigationController *navigationController = [[OWSNavigationController alloc] initWithRootViewController:vc];
Faster conversation presentation. There are multiple places in the codebase we present a conversation. We used to have some very conservative machinery around how this was done, for fear of failing to present the call view controller, which would have left a hidden call in the background. We've since addressed that concern more thoroughly via the separate calling UIWindow. As such, the remaining presentation machinery is overly complex and inflexible for what we need. Sometimes we want to animate-push the conversation. (tap on home, tap on "send message" in contact card/group members) Sometimes we want to dismiss a modal, to reveal the conversation behind it (contact picker, group creation) Sometimes we want to present the conversation with no animation (becoming active from a notification) We also want to ensure that we're never pushing more than one conversation view controller, which was previously a problem since we were "pushing" a newly constructed VC in response to these myriad actions. It turned out there were certain code paths that caused multiple actions to be fired in rapid succession which pushed multiple ConversationVC's. The built-in method: `setViewControllers:animated` easily ensures we only have one ConversationVC on the stack, while being composable enough to faciliate the various more efficient animations we desire. The only thing lost with the complex methods is that the naive `presentViewController:` can fail, e.g. if another view is already presented. E.g. if an alert appears *just* before the user taps compose, the contact picker will fail to present. Since we no longer depend on this for presenting the CallViewController, this isn't catostrophic, and in fact, arguable preferable, since we want the user to read and dismiss any alert explicitly. // FREEBIE
2018-08-18 22:54:35 +02:00
[fromViewController presentViewController:navigationController animated:YES completion:nil];
}
#pragma mark - AvatarViewHelperDelegate
- (NSString *)avatarActionSheetTitle
{
return NSLocalizedString(
@"PROFILE_VIEW_AVATAR_ACTIONSHEET_TITLE", @"Action Sheet title prompting the user for a profile avatar");
}
- (void)avatarDidChange:(UIImage *)image
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread();
OWSAssertDebug(image);
self.avatar = [image resizedImageToFillPixelSize:CGSizeMake(kOWSProfileManager_MaxAvatarDiameter,
kOWSProfileManager_MaxAvatarDiameter)];
}
- (UIViewController *)fromViewController
{
return self;
}
- (BOOL)hasClearAvatarAction
{
return YES;
}
- (NSString *)clearAvatarActionLabel
{
return NSLocalizedString(@"PROFILE_VIEW_CLEAR_AVATAR", @"Label for action that clear's the user's profile avatar");
}
- (void)clearAvatar
{
self.avatar = nil;
}
#pragma mark - OWSNavigationView
- (BOOL)shouldCancelNavigationBack
{
BOOL result = self.hasUnsavedChanges;
if (result) {
[self backOrSkipButtonPressed];
}
return result;
}
@end
NS_ASSUME_NONNULL_END