session-ios/Signal/src/ViewControllers/Registration/OnboardingProfileViewContro...

258 lines
9.9 KiB
Swift

//
// 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.createTitleLabel(text: NSLocalizedString("ONBOARDING_PROFILE_TITLE", comment: "Title of the 'onboarding profile' view."))
titleLabel.accessibilityIdentifier = "onboarding.profile." + "titleLabel"
let explanationLabel = self.createExplanationLabel(text: NSLocalizedString("ONBOARDING_PROFILE_EXPLANATION",
comment: "Explanation in the 'onboarding profile' view."))
explanationLabel.accessibilityIdentifier = "onboarding.profile." + "explanationLabel"
let nextButton = self.createButton(title: NSLocalizedString("BUTTON_NEXT",
comment: "Label for the 'next' button."),
selector: #selector(nextPressed))
nextButton.accessibilityIdentifier = "onboarding.profile." + "nextButton"
avatarView.autoSetDimensions(to: CGSize(width: CGFloat(avatarSize), height: CGFloat(avatarSize)))
let cameraImageView = UIImageView()
cameraImageView.image = UIImage(named: "settings-avatar-camera-2")?.withRenderingMode(.alwaysTemplate)
cameraImageView.tintColor = Theme.secondaryColor
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)
avatarWrapper.accessibilityIdentifier = "onboarding.profile." + "avatarWrapper"
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()
nameTextfield.accessibilityIdentifier = "onboarding.profile." + "nameTextfield"
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,
bottomSpacer,
nextButton
])
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)
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: { _ in
DispatchQueue.main.async {
modal.dismiss(completion: {
OWSAlerts.showErrorAlert(message: NSLocalizedString("PROFILE_VIEW_ERROR_UPDATE_FAILED",
comment: "Error message shown when a profile update fails."))
})
}
}, requiresSync: false)
}
}
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")
}
}