Commit d8b60072 authored by Kateryna Kostiuk's avatar Kateryna Kostiuk

Profile: create during initial walkthrough

Create profile when app is launched for first time

Change-Id: Ia81fec3ca9ad79c7e55548b9cdd3ad0f1223ff73
Reviewed-by: Andreas Traczyk's avatarAndreas Traczyk <andreas.traczyk@savoirfairelinux.com>
parent 00695c69
......@@ -82,6 +82,8 @@
0586C94B1F684DF600613517 /* UIImage+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0586C94A1F684DF600613517 /* UIImage+Helpers.swift */; };
0E403F811F7D797300C80BC2 /* MessageCellGenerated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E403F801F7D797300C80BC2 /* MessageCellGenerated.swift */; };
0E403F831F7D79B000C80BC2 /* MessageCellGenerated.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0E403F821F7D79B000C80BC2 /* MessageCellGenerated.xib */; };
0EDE34C71F868E1200FFA15C /* EditProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE34C61F868E1200FFA15C /* EditProfileViewController.swift */; };
0EDE34C91F8691BB00FFA15C /* EditProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE34C81F8691BB00FFA15C /* EditProfileViewModel.swift */; };
0EE1B54E1F75ACDE00BA98EE /* CNContactVCardSerialization+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE1B54D1F75ACDE00BA98EE /* CNContactVCardSerialization+Helpers.swift */; };
0EE1B5501F75AD4700BA98EE /* VCardUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE1B54F1F75AD4700BA98EE /* VCardUtils.swift */; };
0EEFBA3C1F83DA21000EDBAD /* libsecp256k1.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0EEFBA3B1F83DA21000EDBAD /* libsecp256k1.a */; };
......@@ -300,6 +302,8 @@
0586C94A1F684DF600613517 /* UIImage+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Helpers.swift"; sourceTree = "<group>"; };
0E403F801F7D797300C80BC2 /* MessageCellGenerated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCellGenerated.swift; sourceTree = "<group>"; };
0E403F821F7D79B000C80BC2 /* MessageCellGenerated.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MessageCellGenerated.xib; sourceTree = "<group>"; };
0EDE34C61F868E1200FFA15C /* EditProfileViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditProfileViewController.swift; sourceTree = "<group>"; };
0EDE34C81F8691BB00FFA15C /* EditProfileViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditProfileViewModel.swift; sourceTree = "<group>"; };
0EE1B54D1F75ACDE00BA98EE /* CNContactVCardSerialization+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CNContactVCardSerialization+Helpers.swift"; sourceTree = "<group>"; };
0EE1B54F1F75AD4700BA98EE /* VCardUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VCardUtils.swift; sourceTree = "<group>"; };
0EEFBA3B1F83DA21000EDBAD /* libsecp256k1.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libsecp256k1.a; path = ../DEPS/arm64/lib/libsecp256k1.a; sourceTree = "<group>"; };
......@@ -769,9 +773,19 @@
name = SYS_DEPS;
sourceTree = "<group>";
};
0EDE34C51F868D2D00FFA15C /* Shared */ = {
isa = PBXGroup;
children = (
0EDE34C61F868E1200FFA15C /* EditProfileViewController.swift */,
0EDE34C81F8691BB00FFA15C /* EditProfileViewModel.swift */,
);
name = Shared;
sourceTree = "<group>";
};
1A0C4EBC1F1D48AA00550433 /* Features */ = {
isa = PBXGroup;
children = (
0EDE34C51F868D2D00FFA15C /* Shared */,
1A0C4EBD1F1D48DD00550433 /* Walkthrough */,
1A2D18A71F290FAA00B2C785 /* Conversations */,
1A5DC0331F3567080075E8EF /* ContactRequests */,
......@@ -1288,6 +1302,7 @@
1A3D28A71F0EB9DB00B524EE /* Bool+String.swift in Sources */,
5516C29F1E71CEFF009D3D2D /* AccountModelHelper.swift in Sources */,
1ABE07D31F0D8FE800D36361 /* Storyboards.swift in Sources */,
0EDE34C71F868E1200FFA15C /* EditProfileViewController.swift in Sources */,
62A88D3B1F6C3ACC00F8AB18 /* PresenceService.swift in Sources */,
1A2D18E51F29197100B2C785 /* MessageAccessoryView.swift in Sources */,
1A2D18C61F29180700B2C785 /* ConversationModel.swift in Sources */,
......@@ -1333,6 +1348,7 @@
564C44621E943DE6000F92B1 /* NameService.swift in Sources */,
1A5DC02C1F3565250075E8EF /* MeViewController.swift in Sources */,
1A2041801F1E903B00C08435 /* CreateProfileViewController.swift in Sources */,
0EDE34C91F8691BB00FFA15C /* EditProfileViewModel.swift in Sources */,
04399AAD1D1C304300E99CD9 /* DRingAdapter.mm in Sources */,
1A2D18D11F29182500B2C785 /* ConversationViewController.swift in Sources */,
0EE1B5501F75AD4700BA98EE /* VCardUtils.swift in Sources */,
......
......@@ -26,6 +26,10 @@ enum VCardFolders: String {
case contacts
case profile
}
enum VCardFiles: String {
case myProfile
}
class VCardUtils {
class func saveVCard(vCard: CNContact, withName name: String, inFolder folder: String) -> Observable<Void> {
......
......@@ -85,10 +85,10 @@ class AppCoordinator: Coordinator, StateableResponsive {
let walkthroughCoordinator = WalkthroughCoordinator(with: self.injectionBag)
self.addChildCoordinator(childCoordinator: walkthroughCoordinator)
let walkthroughViewController = walkthroughCoordinator.rootViewController
self.present(viewController: walkthroughViewController, withStyle: .popup, withAnimation: true)
self.present(viewController: walkthroughViewController, withStyle: .popup, withAnimation: false)
walkthroughCoordinator.start()
walkthroughViewController.rx.viewDidDisappear.subscribe(onNext: { [weak self, weak walkthroughCoordinator] (_) in
walkthroughViewController.rx.controllerWasDismissed.subscribe(onNext: { [weak self, weak walkthroughCoordinator] (_) in
walkthroughCoordinator?.stateSubject.dispose()
self?.removeChildCoordinator(childCoordinator: walkthroughCoordinator)
}).disposed(by: self.disposeBag)
......
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import UIKit
import Reusable
import RxSwift
class EditProfileViewController: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
// MARK: - outlets
@IBOutlet weak var profileImageView: UIImageView!
@IBOutlet weak var profileName: UITextField!
// MARK: - members
var model: EditProfileViewModel!
fileprivate let disposeBag = DisposeBag()
// MARK: - functions
override func viewDidLoad() {
super.viewDidLoad()
self.model = EditProfileViewModel()
self.setupUI()
}
func setupUI() {
self.model.image.asObservable()
.bind(to: self.profileImageView.rx.image)
.disposed(by: disposeBag)
self.model.profileName.asObservable()
.bind(to: self.profileName.rx.text)
.disposed(by: disposeBag)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageTapped(tapGestureRecognizer:)))
profileImageView.isUserInteractionEnabled = true
profileImageView.addGestureRecognizer(tapGestureRecognizer)
//Binds the keyboard Send button action to the ViewModel
self.profileName.rx.controlEvent(.editingDidEndOnExit).subscribe(onNext: { [unowned self] _ in
self.model.updateName(self.profileName.text!)
}).disposed(by: disposeBag)
}
func imageTapped(tapGestureRecognizer: UITapGestureRecognizer) {
let alert = UIAlertController.init(title: nil,
message: nil,
preferredStyle: .actionSheet)
let cameraAction = UIAlertAction(title: L10n.Alerts.profileTakePhoto, style: UIAlertActionStyle.default) { _ in
self.takePicture()
}
let pictureAction = UIAlertAction(title: L10n.Alerts.profileUploadPhoto, style: UIAlertActionStyle.default) { _ in
self.importPicture()
}
let cancelAction = UIAlertAction(title: L10n.Alerts.profileCancelPhoto, style: UIAlertActionStyle.cancel)
alert.addAction(cameraAction)
alert.addAction(pictureAction)
alert.addAction(cancelAction)
alert.popoverPresentationController?.sourceView = self.view
alert.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection()
alert.popoverPresentationController?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.maxX, width: 0, height: 0)
self.present(alert, animated: true, completion: nil)
}
func takePicture() {
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera) {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = UIImagePickerControllerSourceType.camera
imagePicker.cameraDevice = UIImagePickerControllerCameraDevice.front
imagePicker.allowsEditing = true
imagePicker.modalPresentationStyle = .overFullScreen
self.present(imagePicker, animated: true, completion: nil)
}
}
func importPicture() {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.allowsEditing = true
imagePicker.sourceType = UIImagePickerControllerSourceType.photoLibrary
imagePicker.modalPresentationStyle = .overFullScreen
self.present(imagePicker, animated: true, completion: nil)
}
// MARK: - Delegates
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String: Any]) {
var image: UIImage!
if let img = info[UIImagePickerControllerEditedImage] as? UIImage {
image = img
} else if let img = info[UIImagePickerControllerOriginalImage] as? UIImage {
image = img
}
image = image.convert(toSize: CGSize(width: 100.0, height: 100.0), scale: UIScreen.main.scale)
self.model.updateImage(image)
profileImageView.contentMode = .scaleAspectFit
profileImageView.image = image.circleMasked
dismiss(animated: true, completion: nil)
}
//hide keyboard when touch outside of text field
// override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// self.profileName.resignFirstResponder()
// self.profileName.text = self.model.profileName.value
// }
}
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import Foundation
import RxSwift
import Contacts
class EditProfileViewModel {
let disposeBag = DisposeBag()
let defaultImage = Image(named: "ic_contact_picture")
var image = Variable<Image?>(nil)
var profileName = Variable<String>("")
init() {
self.image.value = defaultImage
VCardUtils.loadVCard(named: VCardFiles.myProfile.rawValue, inFolder: VCardFolders.profile.rawValue) .subscribe(onSuccess: { [unowned self]card in
self.profileName.value = card.familyName
if let data = card.imageData {
self.image.value = UIImage(data: data)?.convert(toSize: CGSize(width: 100.0, height: 100.0), scale: UIScreen.main.scale).circleMasked
}
}).disposed(by: disposeBag)
}
func saveProfile() {
let vcard = CNMutableContact()
if let image = self.image.value, !image.isEqual(defaultImage) {
vcard.imageData = UIImagePNGRepresentation(image)
}
vcard.familyName = self.profileName.value
_ = VCardUtils.saveVCard(vCard: vcard, withName: VCardFiles.myProfile.rawValue, inFolder: VCardFolders.profile.rawValue).subscribe()
}
func updateImage(_ image: Image) {
self.image.value = image
self.saveProfile()
}
func updateName(_ name: String) {
self.profileName.value = name
self.saveProfile()
}
}
......@@ -72,4 +72,13 @@ extension Reactive where Base : UIViewController {
let source = self.sentMessage(#selector(Base.didReceiveMemoryWarning)).map { _ in }
return ControlEvent(events: source)
}
public var controllerWasDismissed: ControlEvent<Bool> {
let source = self.sentMessage(#selector(Base.viewWillDisappear)).filter { _ in
return self.base.isBeingDismissed
}.map { $0.first as? Bool ?? false }
return ControlEvent(events: source)
}
}
......@@ -253,9 +253,8 @@ class ConversationViewModel: ViewModel {
}
func sendContactRequest() {
let contactExists = self.contactsService.contact(withRingId: self.conversation.recipientRingId) != nil ? true : false
self.accountService.loadVCard(forAccounr: self.accountService.currentAccount!)
VCardUtils.loadVCard(named: VCardFiles.myProfile.rawValue, inFolder: VCardFolders.profile.rawValue)
.subscribe(onSuccess: { [unowned self] (card) in
self.contactsService.sendContactRequest(toContactRingId: self.conversation.recipientRingId, vCard: card, withAccount: self.accountService.currentAccount!)
.subscribe(onCompleted: {
......
......@@ -22,14 +22,11 @@ import UIKit
import Reusable
import RxSwift
class MeViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, StoryboardBased, ViewModelBased {
class MeViewController: EditProfileViewController, StoryboardBased, ViewModelBased {
// MARK: - outlets
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var ringIdLabel: UILabel!
@IBOutlet weak var profileImageView: UIImageView!
@IBOutlet weak var importButton: UIButton!
@IBOutlet weak var photoButton: UIButton!
// MARK: - members
var viewModel: MeViewModel!
......@@ -38,14 +35,12 @@ class MeViewController: UIViewController, UIImagePickerControllerDelegate, UINav
// MARK: - functions
override func viewDidLoad() {
super.viewDidLoad()
self.title = L10n.Global.meTabBarTitle
self.navigationItem.title = L10n.Global.meTabBarTitle
self.setupUI()
}
func setupUI() {
override func setupUI() {
self.viewModel.userName.asObservable()
.bind(to: self.nameLabel.rx.text)
.disposed(by: disposeBag)
......@@ -54,81 +49,6 @@ class MeViewController: UIViewController, UIImagePickerControllerDelegate, UINav
.bind(to: self.ringIdLabel.rx.text)
.disposed(by: disposeBag)
self.viewModel.image?.asObservable()
.bind(to: self.profileImageView.rx.image)
.disposed(by: disposeBag)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageTapped(tapGestureRecognizer:)))
profileImageView.isUserInteractionEnabled = true
profileImageView.addGestureRecognizer(tapGestureRecognizer)
photoButton.rx.tap.subscribe(onNext: {
self.takePicture()
}).disposed(by: self.disposeBag)
photoButton.backgroundColor = UIColor(white: 1, alpha: 0)
importButton.rx.tap.subscribe(onNext: {
self.importPicture()
}).disposed(by: self.disposeBag)
importButton.backgroundColor = UIColor(white: 1, alpha: 0)
}
func imageTapped(tapGestureRecognizer: UITapGestureRecognizer) {
}
func takePicture() {
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera) {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = UIImagePickerControllerSourceType.camera
imagePicker.cameraDevice = UIImagePickerControllerCameraDevice.front
imagePicker.allowsEditing = true
self.present(imagePicker, animated: true, completion: nil)
}
}
func importPicture() {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.allowsEditing = true
imagePicker.sourceType = UIImagePickerControllerSourceType.photoLibrary
self.present(imagePicker, animated: true, completion: nil)
}
// MARK: - Delegates
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
var image: UIImage!
if let img = info[UIImagePickerControllerEditedImage] as? UIImage {
image = img
} else if let img = info[UIImagePickerControllerOriginalImage] as? UIImage {
image = img
}
image = image.convert(toSize:CGSize(width:100.0, height:100.0), scale: UIScreen.main.scale)
self.viewModel.saveProfile(withImage: image)
profileImageView.contentMode = .scaleAspectFit
profileImageView.image = image.circleMasked
dismiss(animated:true, completion: nil)
super.setupUI()
}
// MARK: - QRCode
// func createQRFromString(_ str: String) {
//
// let data = str.data(using: String.Encoding.isoLatin1, allowLossyConversion: false)
//
// let filter = CIFilter(name: "CIQRCodeGenerator")
// filter!.setValue(data, forKey: "inputMessage")
//
// let qrImage: CIImage = filter!.outputImage!
//
// let scaleX = qrImageView.frame.size.width / qrImage.extent.size.width
// let scaleY = qrImageView.frame.size.height / qrImage.extent.size.height
//
// let resultQrImage = qrImage.applying(CGAffineTransform(scaleX: scaleX, y: scaleY))
// qrImageView.image = UIImage(ciImage: resultQrImage)
// }
}
......@@ -21,49 +21,21 @@
import Foundation
import RxSwift
class MeViewModel: Stateable, ViewModel {
class MeViewModel: ViewModel, Stateable {
// MARK: - Rx Stateable
private let stateSubject = PublishSubject<State>()
let accountService: AccountsService
var userName: Single<String?>
let ringId: Single<String?>
var image: Single<Image?>?
lazy var state: Observable<State> = {
return self.stateSubject.asObservable()
}()
required init(with injectionBag: InjectionBag) {
self.accountService = injectionBag.accountService
self.userName = Single.just(self.accountService.currentAccount?.volatileDetails?.get(withConfigKeyModel: ConfigKeyModel(withKey: ConfigKey.accountRegisteredName)))
self.ringId = Single.just(self.accountService.currentAccount?.details?.get(withConfigKeyModel: ConfigKeyModel(withKey: .accountUsername)))
guard let account = self.accountService.currentAccount else {
return
}
let disposebag = DisposeBag()
self.accountService.loadVCard(forAccounr: account)
.subscribe(onSuccess: { card in
if let data = card.imageData {
self.image = Single.just(UIImage(data: data)?.convert(toSize:CGSize(width:100.0, height:100.0), scale: UIScreen.main.scale).circleMasked)
} else {
self.image = Single.just(nil)
}
}).disposed(by: disposebag)
}
func saveProfile(withImage image: UIImage) {
let vcard = CNMutableContact()
vcard.imageData = UIImagePNGRepresentation(image)
vcard.familyName = (self.accountService.currentAccount!
.volatileDetails!.get(withConfigKeyModel: ConfigKeyModel(withKey: ConfigKey.accountRegisteredName)))
self.accountService
.saveVCard(vCard: vcard, forAccounr: self.accountService.currentAccount!)
.subscribe()
let accountService = injectionBag.accountService
self.userName = Single.just(accountService.currentAccount?.volatileDetails?.get(withConfigKeyModel: ConfigKeyModel(withKey: ConfigKey.accountRegisteredName)))
self.ringId = Single.just(accountService.currentAccount?.details?.get(withConfigKeyModel: ConfigKeyModel(withKey: .accountUsername)))
}
}
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="1yn-Mj-8Ek">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="1yn-Mj-8Ek">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
......@@ -36,8 +37,49 @@
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</view>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_contact_picture" translatesAutoresizingMaskIntoConstraints="NO" id="Wss-Rm-aKz">
<rect key="frame" x="127" y="70" width="120" height="120"/>
<constraints>
<constraint firstAttribute="width" constant="120" id="MYf-Pf-9Xj"/>
<constraint firstAttribute="height" constant="120" id="wi5-sQ-QNa"/>
</constraints>
</imageView>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Enter name" textAlignment="center" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="Ebb-h3-1X3">
<rect key="frame" x="36" y="220" width="303" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="dAQ-UM-9wY"/>
</constraints>
<nil key="textColor"/>
<fontDescription key="fontDescription" name="HelveticaNeue" family="Helvetica Neue" pointSize="25"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" returnKeyType="done"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="color" keyPath="borderColor">
<color key="value" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
<real key="value" value="0.0"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</textField>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rb7-PD-nlD">
<rect key="frame" x="20" y="271" width="335" height="1"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="agY-Gs-xpt"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
<real key="value" value="4"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="color" keyPath="borderColor">
<color key="value" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="5Po-6e-k14" customClass="DesignableButton" customModule="Ring" customModuleProvider="target">
<rect key="frame" x="87.5" y="313.5" width="200" height="40"/>
<rect key="frame" x="87.5" y="312" width="200" height="40"/>
<color key="backgroundColor" red="0.0" green="0.29803921570000003" blue="0.37647058820000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="200" id="m9R-vQ-tPe"/>
......@@ -54,17 +96,29 @@
<constraints>
<constraint firstAttribute="trailing" secondItem="Gv4-18-FVt" secondAttribute="trailing" id="1Ev-WL-COb"/>
<constraint firstAttribute="trailing" secondItem="kur-G7-4Nq" secondAttribute="trailing" id="1U8-xt-ygk"/>
<constraint firstAttribute="trailingMargin" secondItem="Ebb-h3-1X3" secondAttribute="trailing" constant="20" id="44H-1a-Kml"/>
<constraint firstItem="5Po-6e-k14" firstAttribute="centerX" secondItem="N1T-Xh-FH1" secondAttribute="centerX" id="4eR-3k-6Z2"/>
<constraint firstItem="Gv4-18-FVt" firstAttribute="top" secondItem="N1T-Xh-FH1" secondAttribute="top" id="5PT-u8-86G"/>
<constraint firstItem="rb7-PD-nlD" firstAttribute="top" secondItem="Ebb-h3-1X3" secondAttribute="bottom" constant="1" id="93f-Xt-9nz"/>
<constraint firstItem="rb7-PD-nlD" firstAttribute="leading" secondItem="N1T-Xh-FH1" secondAttribute="leading" constant="20" id="Bv7-sh-iaU"/>
<constraint firstItem="GVt-PH-FqG" firstAttribute="top" secondItem="Gv4-18-FVt" secondAttribute="bottom" id="DHv-Q6-GhU"/>
<constraint firstItem="Ebb-h3-1X3" firstAttribute="centerX" secondItem="N1T-Xh-FH1" secondAttribute="centerX" id="GWJ-Ar-qem"/>
<constraint firstItem="Ebb-h3-1X3" firstAttribute="top" secondItem="Wss-Rm-aKz" secondAttribute="bottom" constant="30" id="IDm-de-if3"/>
<constraint firstItem="GVt-PH-FqG" firstAttribute="top" secondItem="kur-G7-4Nq" secondAttribute="bottom" id="Jvj-VY-Nb7"/>
<constraint firstItem="Gv4-18-FVt" firstAttribute="leading" secondItem="N1T-Xh-FH1" secondAttribute="leading" id="LiW-7Y-wcc"/>
<constraint firstItem="5Po-6e-k14" firstAttribute="top" secondItem="rb7-PD-nlD" secondAttribute="bottom" constant="40" id="RJ0-y3-Fsz"/>
<constraint firstItem="kur-G7-4Nq" firstAttribute="top" secondItem="N1T-Xh-FH1" secondAttribute="top" id="hSt-o1-S41"/>
<constraint firstItem="5Po-6e-k14" firstAttribute="centerY" secondItem="N1T-Xh-FH1" secondAttribute="centerY" id="lQW-P9-Vmv"/>
<constraint firstItem="rb7-PD-nlD" firstAttribute="centerX" secondItem="N1T-Xh-FH1" secondAttribute="centerX" id="lqC-uZ-05b"/>
<constraint firstItem="Wss-Rm-aKz" firstAttribute="top" secondItem="jiD-fm-HFk" secondAttribute="bottom" constant="50" id="mcf-Sl-c4z"/>
<constraint firstItem="kur-G7-4Nq" firstAttribute="leading" secondItem="N1T-Xh-FH1" secondAttribute="leading" id="r5d-rQ-Kg3"/>
<constraint firstItem="Wss-Rm-aKz" firstAttribute="centerX" secondItem="N1T-Xh-FH1" secondAttribute="centerX" id="xAq-Dn-KVD"/>
<constraint firstItem="Ebb-h3-1X3" firstAttribute="leading" secondItem="N1T-Xh-FH1" secondAttribute="leadingMargin" constant="20" id="zA8-Dy-xtb"/>
<constraint firstAttribute="trailing" secondItem="rb7-PD-nlD" secondAttribute="trailing" constant="20" id="zeR-9G-YTG"/>
</constraints>
</view>
<connections>
<outlet property="profileImageView" destination="Wss-Rm-aKz" id="Q3Z-ch-BNJ"/>
<outlet property="profileName" destination="Ebb-h3-1X3" id="UGL-Fz-ZaN"/>
<outlet property="skipButton" destination="5Po-6e-k14" id="DUG-Am-Mwn"/>
</connections>
</viewController>
......@@ -75,5 +129,6 @@
</scenes>
<resources>
<image name="background_ring" width="750" height="1334"/>
<image name="ic_contact_picture" width="128" height="128"/>
</resources>
</document>
......@@ -10,7 +10,7 @@ import UIKit
import Reusable
import RxSwift
class CreateProfileViewController: UIViewController, StoryboardBased, ViewModelBased {
class CreateProfileViewController: EditProfileViewController, StoryboardBased, ViewModelBased {
// MARK: outlets
@IBOutlet weak var skipButton: DesignableButton!
......@@ -24,12 +24,24 @@ class CreateProfileViewController: UIViewController, StoryboardBased, ViewModelB
super.viewDidLoad()
// Bind ViewModel to View
self.viewModel.skipButtonTitle.bind(to: self.skipButton.rx.title(for: .normal)).disposed(by: self.disposeBag)
self.viewModel.skipButtonTitle.asObservable().bind(to: self.skipButton.rx.title(for: .normal)).disposed(by: self.disposeBag)
// Bind View to ViewModel
self.profileName.rx.text.orEmpty.bind(to: self.viewModel.profileName).disposed(by: self.disposeBag)
if self.profileImageView.image != nil {
let imageObs: Observable<UIImage?> = self.profileImageView
.rx.observe(UIImage.self, "image")
imageObs.bind(to: self.viewModel.profilePhoto).disposed(by: self.disposeBag)
}
// Bind View Actions to ViewModel
self.skipButton.rx.tap.subscribe(onNext: { [unowned self] in
if let name = self.profileName.text {
self.model.updateName(name)
}
self.viewModel.proceedWithAccountCreationOrDeviceLink()
}).disposed(by: self.disposeBag)
}
}
......@@ -28,20 +28,52 @@ class CreateProfileViewModel: Stateable, ViewModel {
lazy var state: Observable<State> = {
return self.stateSubject.asObservable()
}()
var profileName = Variable<String>("")
var profilePhoto = Variable<UIImage?>(nil)
// MARK: - Rx Singles for L10n
lazy var skipButtonTitle: Observable<String> = {
if self.walkthroughType == .createAccount {
return Observable<String>.of(L10n.Createprofile.createAccount)
} else {
return Observable<String>.of(L10n.Createprofile.linkDevice)
}
lazy var profileExists: Observable<Bool> = {
return Observable.combineLatest(self.profileName.asObservable(),
self.profilePhoto.asObservable()) {(username, image) -> Bool in
if !username.isEmpty {
return true
}
let defaultImage = UIImage(named: "ic_contact_picture")
if let image = image, !defaultImage!.isEqual(image) {
return true
}
return false
}
}()
var walkthroughType: WalkthroughType!
var skipButtonTitle = Variable<String>("")
var walkthroughType: WalkthroughType! {
didSet {
if self.walkthroughType == .createAccount {
self.skipButtonTitle.value = L10n.Createprofile.createAccount
} else {
self.skipButtonTitle.value = L10n.Createprofile.linkDevice
}
profileExists.subscribe(onNext: { [unowned self] (state) in
if state {
self.skipButtonTitle.value = L10n.Createprofile.createAccountWithProfile
} else if self.walkthroughType == .createAccount {
self.skipButtonTitle.value = L10n.Createprofile.createAccount
} else {
self.skipButtonTitle.value = L10n.Createprofile.linkDevice
}
}).disposed(by: self.disposeBag)
}
}
let disposeBag = DisposeBag()