Merge branch 'charlesmchen/onboardingProfile'

pull/2/head
Matthew Chen 6 years ago
commit 9d0813d7b9

@ -166,6 +166,7 @@
3496957421A301A100DCFE74 /* OWSBackupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3496956B21A301A100DCFE74 /* OWSBackupAPI.swift */; };
349EA07C2162AEA800F7B17F /* OWS111UDAttributesMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349EA07B2162AEA700F7B17F /* OWS111UDAttributesMigration.swift */; };
34A4C61E221613D00042EF2E /* OnboardingVerificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A4C61D221613D00042EF2E /* OnboardingVerificationViewController.swift */; };
34A4C62022175C5C0042EF2E /* OnboardingProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A4C61F22175C5C0042EF2E /* OnboardingProfileViewController.swift */; };
34A55F3720485465002CC6DE /* OWS2FARegistrationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A55F3520485464002CC6DE /* OWS2FARegistrationViewController.m */; };
34A6C28021E503E700B5B12E /* OWSImagePickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A6C27F21E503E600B5B12E /* OWSImagePickerController.swift */; };
34A8B3512190A40E00218A25 /* MediaAlbumCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A8B3502190A40E00218A25 /* MediaAlbumCellView.swift */; };
@ -848,6 +849,7 @@
3496956D21A301A100DCFE74 /* OWSBackupIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupIO.h; sourceTree = "<group>"; };
349EA07B2162AEA700F7B17F /* OWS111UDAttributesMigration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWS111UDAttributesMigration.swift; sourceTree = "<group>"; };
34A4C61D221613D00042EF2E /* OnboardingVerificationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingVerificationViewController.swift; sourceTree = "<group>"; };
34A4C61F22175C5C0042EF2E /* OnboardingProfileViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingProfileViewController.swift; sourceTree = "<group>"; };
34A55F3520485464002CC6DE /* OWS2FARegistrationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS2FARegistrationViewController.m; sourceTree = "<group>"; };
34A55F3620485464002CC6DE /* OWS2FARegistrationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS2FARegistrationViewController.h; sourceTree = "<group>"; };
34A6C27F21E503E600B5B12E /* OWSImagePickerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSImagePickerController.swift; sourceTree = "<group>"; };
@ -1474,6 +1476,7 @@
3448E15D221333F5004B052E /* OnboardingController.swift */,
3448E15B22133274004B052E /* OnboardingPermissionsViewController.swift */,
3448E16322135FFA004B052E /* OnboardingPhoneNumberViewController.swift */,
34A4C61F22175C5C0042EF2E /* OnboardingProfileViewController.swift */,
3448E15F22134C88004B052E /* OnboardingSplashViewController.swift */,
34A4C61D221613D00042EF2E /* OnboardingVerificationViewController.swift */,
346E9D5321B040B600562252 /* RegistrationController.swift */,
@ -3491,6 +3494,7 @@
343A65981FC4CFE7000477A1 /* ConversationScrollButton.m in Sources */,
34386A51207D0C01009F5D9C /* HomeViewController.m in Sources */,
34D1F0A91F867BFC0066283D /* ConversationViewCell.m in Sources */,
34A4C62022175C5C0042EF2E /* OnboardingProfileViewController.swift in Sources */,
4505C2BF1E648EA300CEBF41 /* ExperienceUpgrade.swift in Sources */,
EF764C351DB67CC5000D9A87 /* UIViewController+Permissions.m in Sources */,
45CD81EF1DC030E7004C9430 /* SyncPushTokensJob.swift in Sources */,

@ -8,6 +8,7 @@
// Separate iOS Frameworks from other imports.
#import "AppSettingsViewController.h"
#import "AttachmentUploadView.h"
#import "AvatarViewHelper.h"
#import "ContactCellView.h"
#import "ContactTableViewCell.h"
#import "ConversationViewCell.h"

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import <UIKit/UIKit.h>
@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN
@protocol AvatarViewHelperDelegate <NSObject>
- (NSString *)avatarActionSheetTitle;
- (nullable NSString *)avatarActionSheetTitle;
- (void)avatarDidChange:(UIImage *)image;

@ -612,7 +612,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - AvatarViewHelperDelegate
- (NSString *)avatarActionSheetTitle
- (nullable NSString *)avatarActionSheetTitle
{
return NSLocalizedString(
@"NEW_GROUP_ADD_PHOTO_ACTION", @"Action Sheet title prompting the user for a group avatar");

@ -573,7 +573,7 @@ NSString *const kProfileView_LastPresentedDate = @"kProfileView_LastPresentedDat
#pragma mark - AvatarViewHelperDelegate
- (NSString *)avatarActionSheetTitle
- (nullable NSString *)avatarActionSheetTitle
{
return NSLocalizedString(
@"PROFILE_VIEW_AVATAR_ACTIONSHEET_TITLE", @"Action Sheet title prompting the user for a profile avatar");

@ -247,6 +247,24 @@ public class OnboardingController: NSObject {
// navigationController.pushViewController(view, animated: true)
}
@objc
public func profileWasSkipped(fromView view: UIViewController) {
AssertIsOnMainThread()
Logger.info("")
// TODO:
}
@objc
public func profileDidComplete(fromView view: UIViewController) {
AssertIsOnMainThread()
Logger.info("")
// TODO:
}
// MARK: - State
public private(set) var countryState: OnboardingCountryState = .defaultValue
@ -450,3 +468,21 @@ public class OnboardingController: NSObject {
}
}
}
// MARK: -
public extension UIView {
public func addBottomStroke() -> UIView {
return addBottomStroke(color: Theme.middleGrayColor, strokeWidth: CGHairlineWidth())
}
public func addBottomStroke(color: UIColor, strokeWidth: CGFloat) -> UIView {
let strokeView = UIView()
strokeView.backgroundColor = color
addSubview(strokeView)
strokeView.autoSetDimension(.height, toSize: strokeWidth)
strokeView.autoPinWidthToSuperview()
strokeView.autoPinEdge(toSuperviewEdge: .bottom)
return strokeView
}
}

@ -62,7 +62,7 @@ public class OnboardingPhoneNumberViewController: OnboardingBaseViewController {
countryRow.isUserInteractionEnabled = true
countryRow.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(countryRowTapped)))
countryRow.autoSetDimension(.height, toSize: rowHeight)
_ = addBottomStroke(countryRow)
_ = countryRow.addBottomStroke()
callingCodeLabel.textColor = Theme.primaryColor
callingCodeLabel.font = UIFont.ows_dynamicTypeBodyClamped
@ -70,7 +70,7 @@ public class OnboardingPhoneNumberViewController: OnboardingBaseViewController {
callingCodeLabel.setCompressionResistanceHorizontalHigh()
callingCodeLabel.isUserInteractionEnabled = true
callingCodeLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(countryCodeTapped)))
_ = addBottomStroke(callingCodeLabel)
_ = callingCodeLabel.addBottomStroke()
callingCodeLabel.autoSetDimension(.width, toSize: rowHeight, relation: .greaterThanOrEqual)
phoneNumberTextField.textAlignment = .left
@ -81,8 +81,8 @@ public class OnboardingPhoneNumberViewController: OnboardingBaseViewController {
phoneNumberTextField.setContentHuggingHorizontalLow()
phoneNumberTextField.setCompressionResistanceHorizontalLow()
phoneStrokeNormal = addBottomStroke(phoneNumberTextField)
phoneStrokeError = addBottomStroke(phoneNumberTextField, color: .ows_destructiveRed, strokeWidth: 2)
phoneStrokeNormal = phoneNumberTextField.addBottomStroke()
phoneStrokeError = phoneNumberTextField.addBottomStroke(color: .ows_destructiveRed, strokeWidth: 2)
let phoneNumberRow = UIStackView(arrangedSubviews: [
callingCodeLabel,
@ -140,20 +140,6 @@ public class OnboardingPhoneNumberViewController: OnboardingBaseViewController {
validationWarningLabel.autoPinEdge(.leading, to: .leading, of: phoneNumberTextField)
}
private func addBottomStroke(_ view: UIView) -> UIView {
return addBottomStroke(view, color: Theme.middleGrayColor, strokeWidth: CGHairlineWidth())
}
private func addBottomStroke(_ view: UIView, color: UIColor, strokeWidth: CGFloat) -> UIView {
let strokeView = UIView()
strokeView.backgroundColor = color
view.addSubview(strokeView)
strokeView.autoSetDimension(.height, toSize: strokeWidth)
strokeView.autoPinWidthToSuperview()
strokeView.autoPinEdge(toSuperviewEdge: .bottom)
return strokeView
}
// MARK: - View Lifecycle
public override func viewWillAppear(_ animated: Bool) {

@ -0,0 +1,252 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import UIKit
@objc
public class OnboardingProfileViewController: OnboardingBaseViewController {
// MARK: - Dependencies
var profileManager: OWSProfileManager {
return OWSProfileManager.shared()
}
// MARK: -
private let avatarView = AvatarImageView()
private let nameTextfield = UITextField()
private var avatar: UIImage?
private let cameraCircle = UIView.container()
private let avatarViewHelper = AvatarViewHelper()
override public func loadView() {
super.loadView()
avatarViewHelper.delegate = self
view.backgroundColor = Theme.backgroundColor
view.layoutMargins = .zero
let titleLabel = self.titleLabel(text: NSLocalizedString("ONBOARDING_PROFILE_TITLE", comment: "Title of the 'onboarding profile' view."))
let explanationLabel = self.explanationLabel(explanationText: NSLocalizedString("ONBOARDING_PROFILE_EXPLANATION",
comment: "Explanation in the 'onboarding profile' view."))
let nextButton = self.button(title: NSLocalizedString("BUTTON_NEXT",
comment: "Label for the 'next' button."),
selector: #selector(nextPressed))
avatarView.autoSetDimensions(to: CGSize(width: CGFloat(avatarSize), height: CGFloat(avatarSize)))
let cameraImageView = UIImageView()
cameraImageView.image = UIImage(named: "settings-avatar-camera")
cameraCircle.backgroundColor = Theme.backgroundColor
cameraCircle.addSubview(cameraImageView)
let cameraCircleDiameter: CGFloat = 40
cameraCircle.autoSetDimensions(to: CGSize(width: cameraCircleDiameter, height: cameraCircleDiameter))
cameraCircle.layer.shadowColor = UIColor(white: 0, alpha: 0.15).cgColor
cameraCircle.layer.shadowRadius = 5
cameraCircle.layer.shadowOffset = CGSize(width: 1, height: 1)
cameraCircle.layer.shadowOpacity = 1
cameraCircle.layer.cornerRadius = cameraCircleDiameter * 0.5
cameraCircle.clipsToBounds = false
cameraImageView.autoCenterInSuperview()
let avatarWrapper = UIView.container()
avatarWrapper.isUserInteractionEnabled = true
avatarWrapper.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(avatarTapped)))
avatarWrapper.addSubview(avatarView)
avatarView.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets(top: 4, leading: 4, bottom: 4, trailing: 4))
avatarWrapper.addSubview(cameraCircle)
cameraCircle.autoPinEdge(toSuperviewEdge: .trailing)
cameraCircle.autoPinEdge(toSuperviewEdge: .bottom)
nameTextfield.textAlignment = .left
nameTextfield.delegate = self
nameTextfield.returnKeyType = .done
nameTextfield.textColor = Theme.primaryColor
nameTextfield.font = UIFont.ows_dynamicTypeBodyClamped
nameTextfield.placeholder = NSLocalizedString("ONBOARDING_PROFILE_NAME_PLACEHOLDER",
comment: "Placeholder text for the profile name in the 'onboarding profile' view.")
nameTextfield.setContentHuggingHorizontalLow()
nameTextfield.setCompressionResistanceHorizontalLow()
let nameWrapper = UIView.container()
nameWrapper.setCompressionResistanceHorizontalLow()
nameWrapper.setContentHuggingHorizontalLow()
nameWrapper.addSubview(nameTextfield)
nameTextfield.autoPinWidthToSuperview()
nameTextfield.autoPinEdge(toSuperviewEdge: .top, withInset: 8)
nameTextfield.autoPinEdge(toSuperviewEdge: .bottom, withInset: 8)
_ = nameWrapper.addBottomStroke()
let profileRow = UIStackView(arrangedSubviews: [
avatarWrapper,
nameWrapper
])
profileRow.axis = .horizontal
profileRow.alignment = .center
profileRow.spacing = 8
let topSpacer = UIView.vStretchingSpacer()
let bottomSpacer = UIView.vStretchingSpacer()
let stackView = UIStackView(arrangedSubviews: [
titleLabel,
topSpacer,
profileRow,
UIView.spacer(withHeight: 25),
explanationLabel,
UIView.spacer(withHeight: 20),
nextButton,
bottomSpacer
])
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.autoPin(toTopLayoutGuideOf: self, withInset: 0)
autoPinView(toBottomOfViewControllerOrKeyboard: stackView, avoidNotch: true)
// Ensure whitespace is balanced, so inputs are vertically centered.
topSpacer.autoMatch(.height, to: .height, of: bottomSpacer)
updateAvatarView()
}
private let avatarSize: UInt = 80
private func updateAvatarView() {
if let avatar = avatar {
avatarView.image = avatar
cameraCircle.isHidden = true
return
}
let defaultAvatar = OWSContactAvatarBuilder(forLocalUserWithDiameter: avatarSize).buildDefaultImage()
avatarView.image = defaultAvatar
cameraCircle.isHidden = false
}
// MARK: -
private func normalizedProfileName() -> String? {
return nameTextfield.text?.ows_stripped()
}
private func tryToComplete() {
let profileName = self.normalizedProfileName()
let profileAvatar = self.avatar
if profileName == nil, profileAvatar == nil {
onboardingController.profileWasSkipped(fromView: self)
return
}
if let name = profileName,
profileManager.isProfileNameTooLong(name) {
OWSAlerts.showErrorAlert(message: NSLocalizedString("PROFILE_VIEW_ERROR_PROFILE_NAME_TOO_LONG",
comment: "Error message shown when user tries to update profile with a profile name that is too long."))
return
}
ModalActivityIndicatorViewController.present(fromViewController: self,
canCancel: true) { (modal) in
self.profileManager.updateLocalProfileName(profileName, avatarImage: profileAvatar, success: {
DispatchQueue.main.async {
modal.dismiss(completion: {
self.onboardingController.profileDidComplete(fromView: self)
})
}
}, failure: {
DispatchQueue.main.async {
modal.dismiss(completion: {
OWSAlerts.showErrorAlert(message: NSLocalizedString("PROFILE_VIEW_ERROR_UPDATE_FAILED",
comment: "Error message shown when a profile update fails."))
})
}
})
}
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
_ = nameTextfield.becomeFirstResponder()
}
// MARK: - Events
@objc func avatarTapped(sender: UIGestureRecognizer) {
guard sender.state == .recognized else {
return
}
showAvatarActionSheet()
}
@objc func nextPressed() {
Logger.info("")
tryToComplete()
}
private func showAvatarActionSheet() {
AssertIsOnMainThread()
Logger.info("")
avatarViewHelper.showChangeAvatarUI()
}
}
// MARK: -
extension OnboardingProfileViewController: UITextFieldDelegate {
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
tryToComplete()
return false
}
}
// MARK: -
extension OnboardingProfileViewController: AvatarViewHelperDelegate {
public func avatarActionSheetTitle() -> String? {
return nil
}
public func avatarDidChange(_ image: UIImage) {
AssertIsOnMainThread()
let maxDiameter = CGFloat(kOWSProfileManager_MaxAvatarDiameter)
avatar = image.resizedImage(toFillPixelSize: CGSize(width: maxDiameter,
height: maxDiameter))
updateAvatarView()
}
public func fromViewController() -> UIViewController {
return self
}
public func hasClearAvatarAction() -> Bool {
return avatar != nil
}
public func clearAvatar() {
avatar = nil
updateAvatarView()
}
public func clearAvatarActionLabel() -> String {
return NSLocalizedString("PROFILE_VIEW_CLEAR_AVATAR", comment: "Label for action that clear's the user's profile avatar")
}
}

@ -133,14 +133,8 @@ private class OnboardingCodeView: UIView {
digitView.addSubview(digitLabel)
digitLabel.autoCenterInSuperview()
let strokeView = UIView.container()
if hasStroke {
strokeView.backgroundColor = Theme.primaryColor
digitView.addSubview(strokeView)
strokeView.autoPinWidthToSuperview()
strokeView.autoPinEdge(toSuperviewEdge: .bottom)
strokeView.autoSetDimension(.height, toSize: 1)
}
let strokeColor = (hasStroke ? Theme.primaryColor : UIColor.clear)
let strokeView = digitView.addBottomStroke(color: strokeColor, strokeWidth: 1)
let vMargin: CGFloat = 4
let cellHeight: CGFloat = digitLabel.font.lineHeight + vMargin * 2

@ -485,7 +485,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - AvatarViewHelperDelegate
- (NSString *)avatarActionSheetTitle
- (nullable NSString *)avatarActionSheetTitle
{
return NSLocalizedString(
@"NEW_GROUP_ADD_PHOTO_ACTION", @"Action Sheet title prompting the user for a group avatar");

@ -1529,6 +1529,15 @@
/* Label indicating that the phone number is invalid in the 'onboarding phone number' view. */
"ONBOARDING_PHONE_NUMBER_VALIDATION_WARNING" = "Invalid number";
/* Explanation in the 'onboarding profile' view. */
"ONBOARDING_PROFILE_EXPLANATION" = "Signal profiles are end-to-end encrypted and the Signal service never has access to this information.";
/* Placeholder text for the profile name in the 'onboarding profile' view. */
"ONBOARDING_PROFILE_NAME_PLACEHOLDER" = "Your Name";
/* Title of the 'onboarding profile' view. */
"ONBOARDING_PROFILE_TITLE" = "Set up your profile";
/* Link to the 'terms and privacy policy' in the 'onboarding splash' view. */
"ONBOARDING_SPLASH_TERM_AND_PRIVACY_POLICY" = "Terms & Privacy Policy";

Loading…
Cancel
Save