Commit e208253a authored by Quentin Muret's avatar Quentin Muret Committed by Kateryna Kostiuk

UI / UX: refactor profile creation page

- refactor UI / UX
- name edition: automatic capitalization for words
- add an animation that incites the users to add an avatar
- we can now dismiss the keyboard when tipping outside
- the UI texts are now changing dynamically regarding the
  language of the device

Change-Id: Ibd7b96bad37d952a297f2d1605295cac83edcc47
Reviewed-by: Kateryna Kostiuk<kateryna.kostiuk@savoirfairelinux.com>
parent 74b6fec4
......@@ -328,7 +328,7 @@
</scene>
</scenes>
<resources>
<image name="ic_contact_picture" width="256" height="256"/>
<image name="ic_contact_picture" width="512" height="512"/>
<image name="left_arrow" width="138.24000549316406" height="138.24000549316406"/>
</resources>
</document>
......@@ -19,6 +19,7 @@
// swiftlint:disable identifier_name line_length nesting type_body_length type_name
internal enum Asset {
internal static let accountIcon = ImageAsset(name: "account_icon")
internal static let addAvatar = ImageAsset(name: "add_avatar")
internal static let addPerson = ImageAsset(name: "add_person")
internal static let attachmentIcon = ImageAsset(name: "attachment_icon")
internal static let audioMuted = ImageAsset(name: "audio_muted")
......@@ -40,6 +41,7 @@ internal enum Asset {
internal static let enableSpeakerphone = ImageAsset(name: "enable_speakerphone")
internal static let fallbackAvatar = ImageAsset(name: "fallback_avatar")
internal static let icContactPicture = ImageAsset(name: "ic_contact_picture")
internal static let infoArrow = ImageAsset(name: "info_arrow")
internal static let jamiIcon = ImageAsset(name: "jamiIcon")
internal static let leftArrow = ImageAsset(name: "left_arrow")
internal static let moreSettings = ImageAsset(name: "more_settings")
......
......@@ -165,10 +165,20 @@ internal enum L10n {
}
internal enum CreateProfile {
/// Create your avatar
internal static let createYourAvatar = L10n.tr("Localizable", "createProfile.createYourAvatar")
/// Enter a display name
internal static let enterNameLabel = L10n.tr("Localizable", "createProfile.enterNameLabel")
/// Enter name
internal static let enterNamePlaceholder = L10n.tr("Localizable", "createProfile.enterNamePlaceholder")
/// Next
internal static let profileCreated = L10n.tr("Localizable", "createProfile.profileCreated")
/// Skip
internal static let skipCreateProfile = L10n.tr("Localizable", "createProfile.skipCreateProfile")
/// Your profile will be shared with your contacts. You can change it at any time.
internal static let subtitle = L10n.tr("Localizable", "createProfile.subtitle")
/// Personalise your profile
internal static let title = L10n.tr("Localizable", "createProfile.title")
}
internal enum DataTransfer {
......
......@@ -25,7 +25,7 @@ import Contacts
class EditProfileViewModel {
let disposeBag = DisposeBag()
let defaultImage = UIImage(named: "ic_contact_picture")
let defaultImage = UIImage(named: "add_avatar")
var image = Variable<UIImage?>(nil)
var profileName = Variable<String>("")
......
......@@ -34,7 +34,7 @@ extension UIColor {
self.init(red: (hex >> 16) & 0xff, green: (hex >> 8) & 0xff, blue: hex & 0xff, alpha: alpha)
}
static let ringMain = UIColor(hex: 0x017CBD, alpha: 1.0) // jami style
static let ringMain = UIColor(hex: 0x3F6DA7, alpha: 1.0) // jami style
static let ringSecondary = UIColor(hex: 0x1F4971, alpha: 1.0) // jami style
static let ringMainLight = UIColor(red: 0, green: 76, blue: 96, alpha: 1.0)
static let ringMsgCellEmoji = UIColor(red: 0, green: 0, blue: 0, alpha: 0)
......
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.14"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
......@@ -21,6 +21,7 @@
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="bD3-jT-xLr">
<rect key="frame" x="137.5" y="80" width="100" height="100"/>
<color key="tintColor" red="0.24705882352941178" green="0.42745098039215684" blue="0.65490196078431373" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
<constraints>
<constraint firstAttribute="height" constant="100" id="2mb-uU-dHb"/>
<constraint firstAttribute="width" constant="100" id="WSb-4Z-9t2"/>
......
......@@ -265,7 +265,7 @@ class CreateAccountViewController: UIViewController, StoryboardBased, ViewModelB
}
private func showAccountCreationInProgress() {
HUD.show(.labeledProgress(title: L10n.CreateAccount.waitCreateAccountTitle, subtitle: nil))
HUD.show(.labeledProgress(title: L10n.CreateAccount.loading, subtitle: nil))
}
private func showAccountCreationSuccess() {
......
......@@ -9,19 +9,71 @@
import UIKit
import Reusable
import RxSwift
import AMPopTip
class CreateProfileViewController: EditProfileViewController, StoryboardBased, ViewModelBased {
// MARK: outlets
@IBOutlet weak var createYourAvatarLabel: UILabel!
@IBOutlet weak var infoView: UIView!
@IBOutlet weak var subtitle: UILabel!
@IBOutlet weak var arrow: UIImageView!
@IBOutlet weak var arrowHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var enterNameLabel: UILabel!
@IBOutlet weak var arrowYConstraint: NSLayoutConstraint!
@IBOutlet weak var createProfilAccountTitle: UILabel!
@IBOutlet weak var skipButton: DesignableButton!
@IBOutlet weak var profileImageViewHeightConstraint: NSLayoutConstraint!
// MARK: members
private let disposeBag = DisposeBag()
var viewModel: CreateProfileViewModel!
let popTip = PopTip()
var keyboardDismissTapRecognizer: UITapGestureRecognizer!
let tapGesture = UITapGestureRecognizer()
// MARK: functions
override func viewDidLoad() {
super.viewDidLoad()
self.view.layoutIfNeeded()
// Style
self.skipButton.applyGradient(with: [UIColor.jamiButtonLight, UIColor.jamiButtonDark], gradient: .horizontal)
self.profileImageView.layer.shadowColor = UIColor.gray.cgColor
self.profileImageView.layer.shadowOpacity = 0.5
self.profileImageView.layer.shadowOffset = CGSize.zero
self.profileImageView.layer.shadowRadius = 4
self.profileName.tintColor = UIColor.ringSecondary
// Animations
DispatchQueue.global(qos: .background).async {
sleep(1)
DispatchQueue.main.async { [weak self] in
self?.infoView.isHidden = false
UIView.animate(withDuration: 0.3, animations: {
self?.infoView.alpha = 1
self?.arrowHeightConstraint.constant = 100
})
self?.arrow.tintColor = UIColor.white
UIView.animate(withDuration: 5, animations: {
self?.arrowYConstraint.constant = 70
self?.view.layoutIfNeeded()
})
self?.setShadowAnimation()
}
usleep(400000)
DispatchQueue.main.async {
self.setShadowAnimation()
}
}
self.infoView.addGestureRecognizer(tapGesture)
self.applyL10n()
//bind view model to view
tapGesture.rx.event.bind(onNext: { [weak self] recognizer in
self?.dismissInfoView()
}).disposed(by: disposeBag)
// Bind ViewModel to View
self.viewModel.skipButtonTitle.asObservable().bind(to: self.skipButton.rx.title(for: .normal)).disposed(by: self.disposeBag)
......@@ -42,11 +94,87 @@ class CreateProfileViewController: EditProfileViewController, StoryboardBased, V
}
self.viewModel.proceedWithAccountCreationOrDeviceLink()
}).disposed(by: self.disposeBag)
// handle keyboard
keyboardDismissTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
}
func dismissInfoView() {
UIView.animate(withDuration: 0.3, animations: { [weak self] in
self?.infoView.alpha = 0
},completion: { _ in self.infoView.isHidden = true })
}
override func imageTapped(tapGestureRecognizer: UITapGestureRecognizer) {
super.imageTapped(tapGestureRecognizer: tapGestureRecognizer)
self.dismissInfoView()
}
@objc func dismissKeyboard() {
self.becomeFirstResponder()
view.removeGestureRecognizer(keyboardDismissTapRecognizer)
}
@objc func keyboardWillAppear(withNotification: NSNotification){
self.view.addGestureRecognizer(keyboardDismissTapRecognizer)
}
@objc func keyboardWillDisappear(withNotification: NSNotification){
view.removeGestureRecognizer(keyboardDismissTapRecognizer)
}
override var canBecomeFirstResponder: Bool {
return true
}
func applyL10n() {
self.createProfilAccountTitle.text = L10n.CreateProfile.title
self.enterNameLabel.text = L10n.CreateProfile.enterNameLabel
self.profileName.placeholder = L10n.CreateProfile.enterNamePlaceholder
self.subtitle.text = L10n.CreateProfile.subtitle
self.createYourAvatarLabel.text = L10n.CreateProfile.createYourAvatar
}
func setShadowAnimation() {
let shadow1Animation = CABasicAnimation(keyPath: "shadowOpacity")
shadow1Animation.fromValue = 0.5
shadow1Animation.toValue = 1
shadow1Animation.duration = 0.2
let shadow2Animation = CABasicAnimation(keyPath: "shadowOpacity")
shadow2Animation.fromValue = 1
shadow2Animation.toValue = 0.5
shadow2Animation.duration = 0.2
self.profileImageView.layer.add(shadow1Animation, forKey: shadow1Animation.keyPath)
DispatchQueue.global(qos: .background).async {
DispatchQueue.main.async { [weak self] in
self?.profileImageView.layer.shadowOpacity = 1
UIView.animate(withDuration: 0.2, animations: {
self?.profileImageViewHeightConstraint.constant = 114
self?.view.layoutIfNeeded()
})
}
usleep(200000)
DispatchQueue.main.async {
self.profileImageView.layer.removeAllAnimations()
self.profileImageView.layer.add(shadow2Animation, forKey: shadow2Animation.keyPath)
self.profileImageView.layer.shadowOpacity = 0.5
UIView.animate(withDuration: 0.2, animations: {
self.profileImageViewHeightConstraint.constant = 120
self.view.layoutIfNeeded()
})
}
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.isNavigationBarHidden = true
UIApplication.shared.statusBarStyle = .default
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillAppear(withNotification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillDisappear(withNotification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
}
......@@ -60,7 +60,6 @@ class CreateProfileViewModel: Stateable, ViewModel {
let disposeBag = DisposeBag()
required init (with injectionBag: InjectionBag) {
}
func proceedWithAccountCreationOrDeviceLink() {
......
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_contact_avatar@1.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ic_contact_avatar@2.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "ic_contact_avatar@3.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}
\ No newline at end of file
......@@ -2,7 +2,7 @@
"images" : [
{
"idiom" : "universal",
"filename" : "ic_contact_avatar.png",
"filename" : "ic_contact_avatar@1.png",
"scale" : "1x"
},
{
......@@ -12,11 +12,15 @@
},
{
"idiom" : "universal",
"filename" : "ic_contact_avatar@3.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}
\ No newline at end of file
{
"images" : [
{
"idiom" : "universal",
"filename" : "info_arrow@1.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "info_arrow@2.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "info_arrow@3.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}
\ No newline at end of file
......@@ -48,6 +48,11 @@
//Creation Profile Screen
"createProfile.skipCreateProfile" = "Skip";
"createProfile.profileCreated" = "Next";
"createProfile.title" = "Personalise your profile";
"createProfile.enterNameLabel" = "Enter a display name";
"createProfile.enterNamePlaceholder" = "Enter name";
"createProfile.subtitle" = "Your profile will be shared with your contacts. You can change it at any time.";
"createProfile.createYourAvatar" = "Create your avatar";
//Create Account form
"createAccount.createAccountFormTitle" = "Create your account";
......
......@@ -48,6 +48,11 @@
//Creation Profile Screen
"createProfile.skipCreateProfile" = "Skip";
"createProfile.profileCreated" = "Next";
"createProfile.title" = "Personnalisez votre profil";
"createProfile.enterNameLabel" = "Entrez un nom d'affichage";
"createProfile.enterNamePlaceholder" = "Entrez un nom";
"createProfile.subtitle" = "Votre profil sera partagé avec vos contacts. Vous pourrez le modifier à tout moment.";
"createProfile.createYourAvatar" = "Créez votre avatar";
//Create Account form
"createAccount.createAccountFormTitle" = "Create your Ring account";
......
......@@ -46,8 +46,13 @@
"welcome.createAccount" = "Créer un compte Jami";
//Creation Profile Screen
"createProfile.skipCreateProfile" = "Skip";
"createProfile.skipCreateProfile" = "Passer";
"createProfile.profileCreated" = "Suivant";
"createProfile.title" = "Personnalisez votre profil";
"createProfile.enterNameLabel" = "Entrez un nom d'affichage";
"createProfile.enterNamePlaceholder" = "Entrez un nom";
"createProfile.subtitle" = "Votre profil sera partagé avec vos contacts. Vous pourrez le modifier à tout moment.";
"createProfile.createYourAvatar" = "Créez votre avatar";
//Create Account form
"createAccount.createAccountFormTitle" = "Créer votre compte";
......
......@@ -48,6 +48,11 @@
//Creation Profile Screen
"createProfile.skipCreateProfile" = "Skip";
"createProfile.profileCreated" = "Next";
"createProfile.title" = "Personnalisez votre profil";
"createProfile.enterNameLabel" = "Entrez un nom d'affichage";
"createProfile.enterNamePlaceholder" = "Entrez un nom";
"createProfile.subtitle" = "Votre profil sera partagé avec vos contacts. Vous pourrez le modifier à tout moment.";
"createProfile.createYourAvatar" = "Créez votre avatar";
//Create Account form
"createAccount.createAccountFormTitle" = "Create your Ring account";
......
......@@ -46,8 +46,13 @@
"welcome.createAccount" = "Créer un compte Jami";
//Creation Profile Screen
"createProfile.skipCreateProfile" = "Skip";
"createProfile.skipCreateProfile" = "Passer";
"createProfile.profileCreated" = "Suivant";
"createProfile.title" = "Personnalisez votre profil";
"createProfile.enterNameLabel" = "Entrez un nom d'affichage";
"createProfile.enterNamePlaceholder" = "Entrez un nom";
"createProfile.subtitle" = "Votre profil sera partagé avec vos contacts. Vous pourrez le modifier à tout moment.";
"createProfile.createYourAvatar" = "Créez votre avatar";
//Create Account form
"createAccount.createAccountFormTitle" = "Créer votre compte";
......
......@@ -48,6 +48,14 @@
//Creation Profile Screen
"createProfile.skipCreateProfile" = "Passez";
"createProfile.profileCreated" = "Suivant";
"createProfile.title" = "Personnalisez votre profil";
"createProfile.enterNameLabel" = "Entrez un nom d'affichage";
"createProfile.enterNamePlaceholder" = "Entrez un nom";
"createProfile.subtitle" = "Votre profil sera partagé avec vos contacts. Vous pourrez le modifier à tout moment.";
"createProfile.createYourAvatar" = "Créez votre avatar";
//Create Account form
"createAccount.createAccountFormTitle" = "Créer votre compte";
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment