Implement profile updating redesign

pull/68/head
Niels Andriesse 5 years ago
parent 83fe454b07
commit 7ea5e5bd46

@ -1,7 +1,9 @@
final class TextField : UITextField { final class TextField : UITextField {
private let usesDefaultHeight: Bool
init(placeholder: String) { init(placeholder: String, usesDefaultHeight: Bool = true) {
self.usesDefaultHeight = usesDefaultHeight
super.init(frame: CGRect.zero) super.init(frame: CGRect.zero)
self.placeholder = placeholder self.placeholder = placeholder
setUpStyle() setUpStyle()
@ -24,17 +26,27 @@ final class TextField : UITextField {
attributedPlaceholder = placeholder attributedPlaceholder = placeholder
tintColor = Colors.accent tintColor = Colors.accent
keyboardAppearance = .dark keyboardAppearance = .dark
set(.height, to: Values.textFieldHeight) if usesDefaultHeight {
set(.height, to: Values.textFieldHeight)
}
layer.borderColor = Colors.border.withAlphaComponent(Values.textFieldBorderOpacity).cgColor layer.borderColor = Colors.border.withAlphaComponent(Values.textFieldBorderOpacity).cgColor
layer.borderWidth = Values.borderThickness layer.borderWidth = Values.borderThickness
layer.cornerRadius = Values.textFieldCornerRadius layer.cornerRadius = Values.textFieldCornerRadius
} }
override func textRect(forBounds bounds: CGRect) -> CGRect { override func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: Values.largeSpacing, dy: Values.largeSpacing) if usesDefaultHeight {
return bounds.insetBy(dx: Values.largeSpacing, dy: Values.largeSpacing)
} else {
return bounds.insetBy(dx: Values.mediumSpacing, dy: Values.smallSpacing)
}
} }
override func editingRect(forBounds bounds: CGRect) -> CGRect { override func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: Values.largeSpacing, dy: Values.largeSpacing) if usesDefaultHeight {
return bounds.insetBy(dx: Values.largeSpacing, dy: Values.largeSpacing)
} else {
return bounds.insetBy(dx: Values.mediumSpacing, dy: Values.smallSpacing)
}
} }
} }

@ -1,5 +1,8 @@
final class SettingsVC : UIViewController { final class SettingsVC : UIViewController, AvatarViewHelperDelegate {
private var profilePictureToBeUploaded: UIImage?
private var displayNameToBeUploaded: String?
private var isEditingDisplayName = false { didSet { handleIsEditingDisplayNameChanged() } }
private lazy var userHexEncodedPublicKey: String = { private lazy var userHexEncodedPublicKey: String = {
if let masterHexEncodedPublicKey = UserDefaults.standard.string(forKey: "masterDeviceHexEncodedPublicKey") { if let masterHexEncodedPublicKey = UserDefaults.standard.string(forKey: "masterDeviceHexEncodedPublicKey") {
@ -22,11 +25,24 @@ final class SettingsVC : UIViewController {
return result return result
}() }()
private lazy var profilePictureUtilities: AvatarViewHelper = {
let result = AvatarViewHelper()
result.delegate = self
return result
}()
private lazy var displayNameLabel: UILabel = { private lazy var displayNameLabel: UILabel = {
let result = UILabel() let result = UILabel()
result.textColor = Colors.text result.textColor = Colors.text
result.font = .boldSystemFont(ofSize: Values.veryLargeFontSize) result.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
result.lineBreakMode = .byTruncatingTail result.lineBreakMode = .byTruncatingTail
result.textAlignment = .center
return result
}()
private lazy var displayNameTextField: TextField = {
let result = TextField(placeholder: NSLocalizedString("Enter a display name", comment: ""), usesDefaultHeight: false)
result.textAlignment = .center
return result return result
}() }()
@ -50,15 +66,10 @@ final class SettingsVC : UIViewController {
navigationBar.isTranslucent = false navigationBar.isTranslucent = false
navigationBar.barTintColor = Colors.navigationBarBackground navigationBar.barTintColor = Colors.navigationBarBackground
// Set up navigation bar buttons // Set up navigation bar buttons
let closeButton = UIBarButtonItem(image: #imageLiteral(resourceName: "X"), style: .plain, target: self, action: #selector(close))
closeButton.tintColor = Colors.text
navigationItem.leftBarButtonItem = closeButton
let backButton = UIBarButtonItem(title: NSLocalizedString("Back", comment: ""), style: .plain, target: nil, action: nil) let backButton = UIBarButtonItem(title: NSLocalizedString("Back", comment: ""), style: .plain, target: nil, action: nil)
backButton.tintColor = Colors.text backButton.tintColor = Colors.text
navigationItem.backBarButtonItem = backButton navigationItem.backBarButtonItem = backButton
let qrCodeButton = UIBarButtonItem(image: #imageLiteral(resourceName: "QRCodeFilled"), style: .plain, target: self, action: #selector(showQRCode)) updateNavigationBarButtons()
qrCodeButton.tintColor = Colors.text
navigationItem.rightBarButtonItem = qrCodeButton
// Customize title // Customize title
let titleLabel = UILabel() let titleLabel = UILabel()
titleLabel.text = NSLocalizedString("Settings", comment: "") titleLabel.text = NSLocalizedString("Settings", comment: "")
@ -66,12 +77,24 @@ final class SettingsVC : UIViewController {
titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize) titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
navigationItem.titleView = titleLabel navigationItem.titleView = titleLabel
// Set up profile picture view // Set up profile picture view
let profilePictureTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(showEditProfilePictureUI))
profilePictureView.addGestureRecognizer(profilePictureTapGestureRecognizer)
profilePictureView.hexEncodedPublicKey = userHexEncodedPublicKey profilePictureView.hexEncodedPublicKey = userHexEncodedPublicKey
profilePictureView.update() profilePictureView.update()
// Set up display name label // Set up display name label
displayNameLabel.text = OWSProfileManager.shared().profileName(forRecipientId: userHexEncodedPublicKey) displayNameLabel.text = OWSProfileManager.shared().profileName(forRecipientId: userHexEncodedPublicKey)
// Set up display name container
let displayNameContainer = UIView()
displayNameContainer.addSubview(displayNameLabel)
displayNameLabel.pin(to: displayNameContainer)
displayNameContainer.addSubview(displayNameTextField)
displayNameTextField.pin(to: displayNameContainer)
displayNameContainer.set(.height, to: 40)
displayNameTextField.alpha = 0
let displayNameLabelTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(showEditDisplayNameUI))
displayNameContainer.addGestureRecognizer(displayNameLabelTapGestureRecognizer)
// Set up header view // Set up header view
let headerStackView = UIStackView(arrangedSubviews: [ profilePictureView, displayNameLabel ]) let headerStackView = UIStackView(arrangedSubviews: [ profilePictureView, displayNameContainer ])
headerStackView.axis = .vertical headerStackView.axis = .vertical
headerStackView.spacing = Values.smallSpacing headerStackView.spacing = Values.smallSpacing
headerStackView.alignment = .center headerStackView.alignment = .center
@ -164,12 +187,6 @@ final class SettingsVC : UIViewController {
return result return result
} }
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
profilePictureView.update()
displayNameLabel.text = OWSProfileManager.shared().profileName(forRecipientId: userHexEncodedPublicKey)
}
// MARK: General // MARK: General
@objc private func enableCopyButton() { @objc private func enableCopyButton() {
copyButton.isUserInteractionEnabled = true copyButton.isUserInteractionEnabled = true
@ -178,6 +195,91 @@ final class SettingsVC : UIViewController {
}, completion: nil) }, completion: nil)
} }
func avatarActionSheetTitle() -> String? {
return NSLocalizedString("Update Profile Picture", comment: "")
}
func fromViewController() -> UIViewController {
return self
}
func hasClearAvatarAction() -> Bool {
return true
}
func clearAvatarActionLabel() -> String {
return NSLocalizedString("Clear", comment: "")
}
// MARK: Updating
private func handleIsEditingDisplayNameChanged() {
updateNavigationBarButtons()
UIView.animate(withDuration: 0.25) {
self.displayNameLabel.alpha = self.isEditingDisplayName ? 0 : 1
self.displayNameTextField.alpha = self.isEditingDisplayName ? 1 : 0
}
if isEditingDisplayName {
displayNameTextField.becomeFirstResponder()
} else {
displayNameTextField.resignFirstResponder()
}
}
private func updateNavigationBarButtons() {
if isEditingDisplayName {
let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(handleCancelDisplayNameEditingButtonTapped))
cancelButton.tintColor = Colors.text
navigationItem.leftBarButtonItem = cancelButton
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(handleSaveDisplayNameButtonTapped))
doneButton.tintColor = Colors.text
navigationItem.rightBarButtonItem = doneButton
} else {
let closeButton = UIBarButtonItem(image: #imageLiteral(resourceName: "X"), style: .plain, target: self, action: #selector(close))
closeButton.tintColor = Colors.text
navigationItem.leftBarButtonItem = closeButton
let qrCodeButton = UIBarButtonItem(image: #imageLiteral(resourceName: "QRCodeFilled"), style: .plain, target: self, action: #selector(showQRCode))
qrCodeButton.tintColor = Colors.text
navigationItem.rightBarButtonItem = qrCodeButton
}
}
func avatarDidChange(_ image: UIImage) {
let maxSize = Int(kOWSProfileManager_MaxAvatarDiameter)
profilePictureToBeUploaded = image.resizedImage(toFillPixelSize: CGSize(width: maxSize, height: maxSize))
updateProfile(isUpdatingDisplayName: false, isUpdatingProfilePicture: true)
}
func clearAvatar() {
profilePictureToBeUploaded = nil
updateProfile(isUpdatingDisplayName: false, isUpdatingProfilePicture: true)
}
private func updateProfile(isUpdatingDisplayName: Bool, isUpdatingProfilePicture: Bool) {
let displayName = displayNameToBeUploaded ?? OWSProfileManager.shared().profileName(forRecipientId: userHexEncodedPublicKey)
let profilePicture = profilePictureToBeUploaded ?? OWSProfileManager.shared().profileAvatar(forRecipientId: userHexEncodedPublicKey)
ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self] modalActivityIndicator in
OWSProfileManager.shared().updateLocalProfileName(displayName, avatarImage: profilePicture, success: {
DispatchQueue.main.async {
modalActivityIndicator.dismiss {
guard let self = self else { return }
self.profilePictureView.update()
self.displayNameLabel.text = displayName
self.profilePictureToBeUploaded = nil
self.displayNameToBeUploaded = nil
}
}
}, failure: {
DispatchQueue.main.async {
modalActivityIndicator.dismiss {
let alert = UIAlertController(title: NSLocalizedString("Couldn't Update Profile", comment: ""), message: NSLocalizedString("Please check your internet connection and try again", comment: ""), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
self?.present(alert, animated: true, completion: nil)
}
}
})
}
}
// MARK: Interaction // MARK: Interaction
@objc private func close() { @objc private func close() {
dismiss(animated: true, completion: nil) dismiss(animated: true, completion: nil)
@ -188,6 +290,41 @@ final class SettingsVC : UIViewController {
navigationController!.pushViewController(qrCodeVC, animated: true) navigationController!.pushViewController(qrCodeVC, animated: true)
} }
@objc private func handleCancelDisplayNameEditingButtonTapped() {
isEditingDisplayName = false
}
@objc private func handleSaveDisplayNameButtonTapped() {
func showError(title: String, message: String = "") {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
presentAlert(alert)
}
let displayName = displayNameTextField.text!.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
guard !displayName.isEmpty else {
return showError(title: NSLocalizedString("Please pick a display name", comment: ""))
}
let allowedCharacters = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_ ")
let hasInvalidCharacters = !displayName.allSatisfy { $0.unicodeScalars.allSatisfy { allowedCharacters.contains($0) } }
guard !hasInvalidCharacters else {
return showError(title: NSLocalizedString("Please pick a display name that consists of only a-z, A-Z, 0-9 and _ characters", comment: ""))
}
guard !OWSProfileManager.shared().isProfileNameTooLong(displayName) else {
return showError(title: NSLocalizedString("Please pick a shorter display name", comment: ""))
}
isEditingDisplayName = false
displayNameToBeUploaded = displayName
updateProfile(isUpdatingDisplayName: true, isUpdatingProfilePicture: false)
}
@objc private func showEditProfilePictureUI() {
profilePictureUtilities.showChangeAvatarUI()
}
@objc private func showEditDisplayNameUI() {
isEditingDisplayName = true
}
@objc private func copyPublicKey() { @objc private func copyPublicKey() {
UIPasteboard.general.string = userHexEncodedPublicKey UIPasteboard.general.string = userHexEncodedPublicKey
copyButton.isUserInteractionEnabled = false copyButton.isUserInteractionEnabled = false

@ -146,10 +146,10 @@ import SignalMessaging
private func createViews() { private func createViews() {
view.backgroundColor = UIColor.black view.backgroundColor = .black
let contentView = UIView() let contentView = UIView()
contentView.backgroundColor = UIColor.black contentView.backgroundColor = .black
self.view.addSubview(contentView) self.view.addSubview(contentView)
contentView.autoPinEdgesToSuperviewEdges() contentView.autoPinEdgesToSuperviewEdges()
@ -186,14 +186,14 @@ import SignalMessaging
layer.path = path.cgPath layer.path = path.cgPath
layer.fillRule = .evenOdd layer.fillRule = .evenOdd
layer.fillColor = UIColor.black.cgColor layer.fillColor = UIColor.black.cgColor
layer.opacity = 0.7 layer.opacity = 0.75
} }
maskingView.autoPinEdgesToSuperviewEdges() maskingView.autoPinEdgesToSuperviewEdges()
let titleLabel = UILabel() let titleLabel = UILabel()
titleLabel.textColor = UIColor.white titleLabel.textColor = Colors.text
titleLabel.textAlignment = .center titleLabel.textAlignment = .center
titleLabel.font = UIFont.ows_mediumFont(withSize: ScaleFromIPhone5(16)) titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
titleLabel.text = NSLocalizedString("CROP_SCALE_IMAGE_VIEW_TITLE", titleLabel.text = NSLocalizedString("CROP_SCALE_IMAGE_VIEW_TITLE",
comment: "Title for the 'crop/scale image' dialog.") comment: "Title for the 'crop/scale image' dialog.")
contentView.addSubview(titleLabel) contentView.addSubview(titleLabel)
@ -439,6 +439,7 @@ import SignalMessaging
let cancelButton = createButton(title: CommonStrings.cancelButton, let cancelButton = createButton(title: CommonStrings.cancelButton,
action: #selector(cancelPressed)) action: #selector(cancelPressed))
cancelButton.titleLabel!.font = .systemFont(ofSize: 18) // Match iOS UI
buttonRow.addSubview(cancelButton) buttonRow.addSubview(cancelButton)
cancelButton.autoPinEdge(toSuperviewEdge: .top) cancelButton.autoPinEdge(toSuperviewEdge: .top)
cancelButton.autoPinEdge(toSuperviewEdge: .bottom) cancelButton.autoPinEdge(toSuperviewEdge: .bottom)
@ -446,6 +447,7 @@ import SignalMessaging
let doneButton = createButton(title: CommonStrings.doneButton, let doneButton = createButton(title: CommonStrings.doneButton,
action: #selector(donePressed)) action: #selector(donePressed))
doneButton.titleLabel!.font = .systemFont(ofSize: 18) // Match iOS UI
buttonRow.addSubview(doneButton) buttonRow.addSubview(doneButton)
doneButton.autoPinEdge(toSuperviewEdge: .top) doneButton.autoPinEdge(toSuperviewEdge: .top)
doneButton.autoPinEdge(toSuperviewEdge: .bottom) doneButton.autoPinEdge(toSuperviewEdge: .bottom)

@ -2720,3 +2720,7 @@
"Unlock Loki Messenger's screen using Touch ID, Face ID, or your iOS device passcode. You can still receive message notifications while Screen Lock is enabled. Loki Messenger's notification settings allow you to customize the information that is displayed." = "Unlock Loki Messenger's screen using Touch ID, Face ID, or your iOS device passcode. You can still receive message notifications while Screen Lock is enabled. Loki Messenger's notification settings allow you to customize the information that is displayed."; "Unlock Loki Messenger's screen using Touch ID, Face ID, or your iOS device passcode. You can still receive message notifications while Screen Lock is enabled. Loki Messenger's notification settings allow you to customize the information that is displayed." = "Unlock Loki Messenger's screen using Touch ID, Face ID, or your iOS device passcode. You can still receive message notifications while Screen Lock is enabled. Loki Messenger's notification settings allow you to customize the information that is displayed.";
"Sound" = "Sound"; "Sound" = "Sound";
"Content" = "Content"; "Content" = "Content";
"Update Profile Picture" = "Update Profile Picture";
"Couldn't Update Profile Picture" = "Couldn't Update Profile Picture";
"Clear" = "Clear";
"Enter a display name" = "Enter a display name";

@ -27,12 +27,12 @@
+ (void)setupSignalAppearence + (void)setupSignalAppearence
{ {
UINavigationBar.appearance.barTintColor = Theme.navbarBackgroundColor; UINavigationBar.appearance.barTintColor = [UIColor colorWithRGBHex:0x161616]; // Colors.navigationBarBackground
UINavigationBar.appearance.tintColor = Theme.navbarIconColor; UINavigationBar.appearance.tintColor = [UIColor colorWithRGBHex:0xFFFFFF]; // Colors.text
UIToolbar.appearance.barTintColor = Theme.navbarBackgroundColor; UIToolbar.appearance.barTintColor = Theme.navbarBackgroundColor;
UIToolbar.appearance.tintColor = Theme.navbarIconColor; UIToolbar.appearance.tintColor = Theme.navbarIconColor;
UIBarButtonItem.appearance.tintColor = Theme.navbarIconColor; UIBarButtonItem.appearance.tintColor = [UIColor colorWithRGBHex:0xFFFFFF]; // Colors.text
// Using the keyboardAppearance causes crashes due to a bug in UIKit. // Using the keyboardAppearance causes crashes due to a bug in UIKit.
// UITextField.appearance.keyboardAppearance = (Theme.isDarkThemeEnabled // UITextField.appearance.keyboardAppearance = (Theme.isDarkThemeEnabled
@ -42,11 +42,11 @@
// ? UIKeyboardAppearanceDark // ? UIKeyboardAppearanceDark
// : UIKeyboardAppearanceDefault); // : UIKeyboardAppearanceDefault);
[[UISwitch appearance] setOnTintColor:[UIColor ows_materialBlueColor]]; [[UISwitch appearance] setOnTintColor:[UIColor colorWithRGBHex:0x00F782]]; // Colors.accent
[[UIToolbar appearance] setTintColor:[UIColor ows_materialBlueColor]]; [[UIToolbar appearance] setTintColor:[UIColor ows_materialBlueColor]];
// If we set NSShadowAttributeName, the NSForegroundColorAttributeName value is ignored. // If we set NSShadowAttributeName, the NSForegroundColorAttributeName value is ignored.
UINavigationBar.appearance.titleTextAttributes = @{ NSForegroundColorAttributeName : Theme.navbarTitleColor }; UINavigationBar.appearance.titleTextAttributes = @{ NSForegroundColorAttributeName : [UIColor colorWithRGBHex:0xFFFFFF] }; // Colors.text
} }
@end @end

Loading…
Cancel
Save