diff --git a/Ring/Ring.xcodeproj/project.pbxproj b/Ring/Ring.xcodeproj/project.pbxproj index b6db618c71f2a5c171f031185b68cd6a8a1089e4..aa7323ab83504d3ce29af76d6e64215f726151dd 100644 --- a/Ring/Ring.xcodeproj/project.pbxproj +++ b/Ring/Ring.xcodeproj/project.pbxproj @@ -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 */, diff --git a/Ring/Ring/Account/VCardUtils.swift b/Ring/Ring/Account/VCardUtils.swift index 6d74d2def472971cd78bdfd01e7f1fffcb532ff3..e20e3b0ce9ab83869abbe262652a4a002f12b23e 100644 --- a/Ring/Ring/Account/VCardUtils.swift +++ b/Ring/Ring/Account/VCardUtils.swift @@ -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> { diff --git a/Ring/Ring/Coordinators/AppCoordinator.swift b/Ring/Ring/Coordinators/AppCoordinator.swift index b189714c29f9f2fc9902650b1fd3734de65118d4..000c0bcf361f50b70bb449c0f6839b0e6592b50e 100644 --- a/Ring/Ring/Coordinators/AppCoordinator.swift +++ b/Ring/Ring/Coordinators/AppCoordinator.swift @@ -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) diff --git a/Ring/Ring/EditProfileViewController.swift b/Ring/Ring/EditProfileViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..11efd7994514cf7158cde9f6d3776fdbb31b218b --- /dev/null +++ b/Ring/Ring/EditProfileViewController.swift @@ -0,0 +1,132 @@ +/* + * 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 +// } +} diff --git a/Ring/Ring/EditProfileViewModel.swift b/Ring/Ring/EditProfileViewModel.swift new file mode 100644 index 0000000000000000000000000000000000000000..c3ebad5a1ce3a737703e995a85bf2a3989d85d75 --- /dev/null +++ b/Ring/Ring/EditProfileViewModel.swift @@ -0,0 +1,64 @@ +/* + * 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() + } +} diff --git a/Ring/Ring/Extensions/UIViewController+Rx.swift b/Ring/Ring/Extensions/UIViewController+Rx.swift index bfcad388db5e5fc3c1daec9ba6ba4c4ac3097700..e616a1e04f62f15abda129ee8115f161ba10dc0a 100644 --- a/Ring/Ring/Extensions/UIViewController+Rx.swift +++ b/Ring/Ring/Extensions/UIViewController+Rx.swift @@ -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) + } } diff --git a/Ring/Ring/Features/Conversations/Conversation/ConversationViewModel.swift b/Ring/Ring/Features/Conversations/Conversation/ConversationViewModel.swift index 16b7bd75655ecdad5e9259a61bf284260fbe71a8..9d5a84430a61adda594a8548b01f48d2996bd971 100644 --- a/Ring/Ring/Features/Conversations/Conversation/ConversationViewModel.swift +++ b/Ring/Ring/Features/Conversations/Conversation/ConversationViewModel.swift @@ -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: { diff --git a/Ring/Ring/Features/Me/Me/MeViewController.storyboard b/Ring/Ring/Features/Me/Me/MeViewController.storyboard index cc22d1cc7437135b1a3151d450d8786d6d9d91d3..becfd28b94628cab9c6ac944da8fdf979c03bb6a 100644 --- a/Ring/Ring/Features/Me/Me/MeViewController.storyboard +++ b/Ring/Ring/Features/Me/Me/MeViewController.storyboard @@ -22,79 +22,99 @@ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> - <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="PERSON PLACEHOLDER" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="w8x-Sv-T50"> - <rect key="frame" x="16" y="30" width="343" height="26.5"/> - <fontDescription key="fontDescription" type="system" pointSize="22"/> - <color key="textColor" red="0.20000000300000001" green="0.20000000300000001" blue="0.20000000300000001" alpha="1" colorSpace="calibratedRGB"/> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Ring id" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="11" translatesAutoresizingMaskIntoConstraints="NO" id="rC6-Ga-QjY" userLabel="RingId Label"> + <rect key="frame" x="21" y="189" width="333" height="18"/> + <constraints> + <constraint firstAttribute="height" constant="18" id="UsR-G7-bkE"/> + </constraints> + <fontDescription key="fontDescription" type="system" pointSize="13"/> + <color key="textColor" red="0.29803922770000002" green="0.29803922770000002" blue="0.29803922770000002" alpha="1" colorSpace="calibratedRGB"/> <nil key="highlightedColor"/> </label> - <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Ring id" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="11" translatesAutoresizingMaskIntoConstraints="NO" id="rC6-Ga-QjY" userLabel="RingId Label"> - <rect key="frame" x="16" y="66.5" width="343" height="18"/> - <fontDescription key="fontDescription" type="system" pointSize="15"/> - <color key="textColor" red="0.29803922770000002" green="0.29803922770000002" blue="0.29803922770000002" alpha="1" colorSpace="calibratedRGB"/> + <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Enter name" textAlignment="center" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="GCe-jT-OCG"> + <rect key="frame" x="36" y="140" width="303" height="26"/> + <nil key="textColor"/> + <fontDescription key="fontDescription" name="HelveticaNeue-Bold" family="Helvetica Neue" pointSize="21"/> + <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="tS0-P7-fYT"> + <rect key="frame" x="20" y="167" width="335" height="1"/> + <constraints> + <constraint firstAttribute="height" constant="1" id="f7M-6A-gFC"/> + </constraints> + <fontDescription key="fontDescription" type="system" pointSize="17"/> + <nil key="textColor"/> <nil key="highlightedColor"/> + <userDefinedRuntimeAttributes> + <userDefinedRuntimeAttribute type="number" keyPath="borderWidth"> + <real key="value" value="2"/> + </userDefinedRuntimeAttribute> + <userDefinedRuntimeAttribute type="color" keyPath="borderColor"> + <color key="value" name="gridColor" catalog="System" colorSpace="catalog"/> + </userDefinedRuntimeAttribute> + <userDefinedRuntimeAttribute type="color" keyPath="gradientStartColor"> + <color key="value" name="gridColor" catalog="System" colorSpace="catalog"/> + </userDefinedRuntimeAttribute> + <userDefinedRuntimeAttribute type="color" keyPath="gradientEndColor"> + <color key="value" name="gridColor" catalog="System" colorSpace="catalog"/> + </userDefinedRuntimeAttribute> + </userDefinedRuntimeAttributes> </label> - <stackView opaque="NO" contentMode="center" distribution="fillEqually" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="reK-Om-ReA"> - <rect key="frame" x="16" y="232.5" width="343" height="30"/> - <subviews> - <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Rfv-Ak-7NM"> - <rect key="frame" x="0.0" y="0.0" width="171.5" height="30"/> - <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/> - <color key="tintColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/> - <state key="normal" title="Take a picture"> - <color key="titleColor" red="0.40000000600000002" green="0.40000000600000002" blue="0.40000000600000002" alpha="1" colorSpace="calibratedRGB"/> - </state> - <connections> - <action selector="takePicture:" destination="MXJ-Bb-hGu" eventType="touchDown" id="M3f-W6-cfc"/> - </connections> - </button> - <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hq2-0j-DsW"> - <rect key="frame" x="171.5" y="0.0" width="171.5" height="30"/> - <state key="normal" title="Import a picture"> - <color key="titleColor" red="0.40000000600000002" green="0.40000000600000002" blue="0.40000000600000002" alpha="1" colorSpace="calibratedRGB"/> - </state> - <connections> - <action selector="importPicture:" destination="MXJ-Bb-hGu" eventType="touchDown" id="Cgc-Uj-vJE"/> - </connections> - </button> - </subviews> - </stackView> <imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_contact_picture" translatesAutoresizingMaskIntoConstraints="NO" id="8HY-Zl-94u"> - <rect key="frame" x="16" y="104.5" width="343" height="128"/> + <rect key="frame" x="137" y="10" width="100" height="100"/> + <constraints> + <constraint firstAttribute="height" constant="100" id="3Ik-A7-Cyx"/> + <constraint firstAttribute="width" constant="100" id="xE6-h0-miE"/> + </constraints> </imageView> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="PERSON PLACEHOLDER" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="w8x-Sv-T50"> + <rect key="frame" x="21" y="212" width="333" height="23"/> + <fontDescription key="fontDescription" type="system" pointSize="19"/> + <color key="textColor" red="0.20000000300000001" green="0.20000000300000001" blue="0.20000000300000001" alpha="1" colorSpace="calibratedRGB"/> + <nil key="highlightedColor"/> + </label> </subviews> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <constraints> - <constraint firstAttribute="trailingMargin" secondItem="8HY-Zl-94u" secondAttribute="trailing" id="8Uy-Pp-lgo"/> - <constraint firstItem="rC6-Ga-QjY" firstAttribute="leading" secondItem="nkh-py-Uar" secondAttribute="leadingMargin" id="IQi-1b-isC"/> - <constraint firstItem="rC6-Ga-QjY" firstAttribute="trailing" secondItem="nkh-py-Uar" secondAttribute="trailingMargin" id="Rkf-1X-wgP"/> - <constraint firstItem="8HY-Zl-94u" firstAttribute="top" secondItem="rC6-Ga-QjY" secondAttribute="bottom" constant="20" id="ZZS-Wh-23Z"/> - <constraint firstItem="8HY-Zl-94u" firstAttribute="leading" secondItem="nkh-py-Uar" secondAttribute="leadingMargin" id="ceB-3O-BOQ"/> - <constraint firstItem="rC6-Ga-QjY" firstAttribute="top" secondItem="w8x-Sv-T50" secondAttribute="bottom" constant="10" id="lnm-Mq-ocY"/> - <constraint firstItem="w8x-Sv-T50" firstAttribute="leading" secondItem="nkh-py-Uar" secondAttribute="leadingMargin" id="nMK-tO-WU0"/> - <constraint firstItem="w8x-Sv-T50" firstAttribute="top" secondItem="SYf-gf-IKh" secondAttribute="bottom" constant="10" id="rUH-Jk-nMV"/> - <constraint firstAttribute="trailingMargin" secondItem="reK-Om-ReA" secondAttribute="trailing" id="tUZ-Io-Jbc"/> - <constraint firstItem="w8x-Sv-T50" firstAttribute="trailing" secondItem="nkh-py-Uar" secondAttribute="trailingMargin" id="uYH-if-S88"/> - <constraint firstItem="reK-Om-ReA" firstAttribute="leading" secondItem="nkh-py-Uar" secondAttribute="leadingMargin" id="w47-Gi-x8l"/> - <constraint firstItem="reK-Om-ReA" firstAttribute="top" secondItem="8HY-Zl-94u" secondAttribute="bottom" id="zEx-uZ-4SL"/> + <constraint firstAttribute="trailingMargin" secondItem="rC6-Ga-QjY" secondAttribute="trailing" constant="5" id="4mU-no-jxy"/> + <constraint firstItem="tS0-P7-fYT" firstAttribute="leading" secondItem="nkh-py-Uar" secondAttribute="leading" constant="20" id="6OH-dg-fT7"/> + <constraint firstAttribute="trailingMargin" secondItem="w8x-Sv-T50" secondAttribute="trailing" constant="5" id="Bm3-S8-KXr"/> + <constraint firstItem="GCe-jT-OCG" firstAttribute="top" secondItem="8HY-Zl-94u" secondAttribute="bottom" constant="30" id="E9u-9R-kVg"/> + <constraint firstItem="8HY-Zl-94u" firstAttribute="top" secondItem="nkh-py-Uar" secondAttribute="topMargin" constant="10" id="FBb-kO-4nV"/> + <constraint firstItem="tS0-P7-fYT" firstAttribute="top" secondItem="GCe-jT-OCG" secondAttribute="bottom" constant="1" id="FGU-ae-3wK"/> + <constraint firstItem="8HY-Zl-94u" firstAttribute="centerX" secondItem="nkh-py-Uar" secondAttribute="centerX" id="H4W-Vc-cHV"/> + <constraint firstAttribute="trailingMargin" secondItem="GCe-jT-OCG" secondAttribute="trailing" constant="20" id="I2l-64-P0x"/> + <constraint firstItem="tS0-P7-fYT" firstAttribute="centerX" secondItem="nkh-py-Uar" secondAttribute="centerX" id="JBF-Pu-rcp"/> + <constraint firstItem="GCe-jT-OCG" firstAttribute="leading" secondItem="nkh-py-Uar" secondAttribute="leadingMargin" constant="20" id="Sp4-fa-qoR"/> + <constraint firstAttribute="trailing" secondItem="tS0-P7-fYT" secondAttribute="trailing" constant="20" id="Uvb-ZR-7rf"/> + <constraint firstItem="GCe-jT-OCG" firstAttribute="centerX" secondItem="nkh-py-Uar" secondAttribute="centerX" id="WO3-X1-ObR"/> + <constraint firstItem="rC6-Ga-QjY" firstAttribute="leading" secondItem="nkh-py-Uar" secondAttribute="leadingMargin" constant="5" id="g4K-vz-fbY"/> + <constraint firstItem="w8x-Sv-T50" firstAttribute="top" secondItem="rC6-Ga-QjY" secondAttribute="bottom" constant="5" id="kSt-bN-hIG"/> + <constraint firstItem="w8x-Sv-T50" firstAttribute="leading" secondItem="rC6-Ga-QjY" secondAttribute="leading" id="t75-yu-OEC"/> + <constraint firstItem="rC6-Ga-QjY" firstAttribute="top" secondItem="tS0-P7-fYT" secondAttribute="bottom" constant="21" id="yve-5B-6tl"/> </constraints> </view> <extendedEdge key="edgesForExtendedLayout" bottom="YES"/> <tabBarItem key="tabBarItem" title="Me" id="AKc-Hb-EHr"/> - <navigationItem key="navigationItem" id="vC8-Ti-TTd"> - <barButtonItem key="rightBarButtonItem" systemItem="edit" id="NqP-iv-qav"/> - </navigationItem> + <navigationItem key="navigationItem" id="vC8-Ti-TTd"/> <connections> - <outlet property="importButton" destination="hq2-0j-DsW" id="TJA-fa-k8g"/> - <outlet property="nameLabel" destination="w8x-Sv-T50" id="OAT-b7-q3U"/> - <outlet property="photoButton" destination="Rfv-Ak-7NM" id="Hl5-bN-0CV"/> - <outlet property="profileImageView" destination="8HY-Zl-94u" id="RQx-NQ-Ezs"/> - <outlet property="ringIdLabel" destination="rC6-Ga-QjY" id="McC-Ah-cfF"/> + <outlet property="nameLabel" destination="w8x-Sv-T50" id="IgU-9F-Fkh"/> + <outlet property="profileImageView" destination="8HY-Zl-94u" id="cY6-cO-WEq"/> + <outlet property="profileName" destination="GCe-jT-OCG" id="FDX-fX-Fye"/> + <outlet property="ringIdLabel" destination="rC6-Ga-QjY" id="nsg-Jw-OTS"/> </connections> </viewController> <placeholder placeholderIdentifier="IBFirstResponder" id="MXJ-Bb-hGu" userLabel="First Responder" sceneMemberID="firstResponder"/> </objects> - <point key="canvasLocation" x="-94" y="-438"/> + <point key="canvasLocation" x="-186.40000000000001" y="-483.50824587706148"/> </scene> </scenes> <resources> diff --git a/Ring/Ring/Features/Me/Me/MeViewController.swift b/Ring/Ring/Features/Me/Me/MeViewController.swift index 5b8c003ee92f9c7ea98f44bde3bff0e62709acfd..7617198a93d70db855a4c1ffe3d63eb1da81b269 100644 --- a/Ring/Ring/Features/Me/Me/MeViewController.swift +++ b/Ring/Ring/Features/Me/Me/MeViewController.swift @@ -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) -// } - } diff --git a/Ring/Ring/Features/Me/Me/MeViewModel.swift b/Ring/Ring/Features/Me/Me/MeViewModel.swift index 4f95ec1c49d91765116942178f1443c5313b3b4b..e6ba41fad208ba3f5086c4387b3f8eaf2ce881a1 100644 --- a/Ring/Ring/Features/Me/Me/MeViewModel.swift +++ b/Ring/Ring/Features/Me/Me/MeViewModel.swift @@ -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))) } } diff --git a/Ring/Ring/Features/Walkthrough/CreateProfile/CreateProfileViewController.storyboard b/Ring/Ring/Features/Walkthrough/CreateProfile/CreateProfileViewController.storyboard index c931a93387ac39c0f8470b9077f06cea37859aa1..8aec9ab1563192e24c0bb60aeff31b96add9711a 100644 --- a/Ring/Ring/Features/Walkthrough/CreateProfile/CreateProfileViewController.storyboard +++ b/Ring/Ring/Features/Walkthrough/CreateProfile/CreateProfileViewController.storyboard @@ -1,11 +1,12 @@ <?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> diff --git a/Ring/Ring/Features/Walkthrough/CreateProfile/CreateProfileViewController.swift b/Ring/Ring/Features/Walkthrough/CreateProfile/CreateProfileViewController.swift index bab3f5a8170048c0175183625d3da905af2433f5..50b2045117dc2336025b35d3eb74ae24dd0e0a12 100644 --- a/Ring/Ring/Features/Walkthrough/CreateProfile/CreateProfileViewController.swift +++ b/Ring/Ring/Features/Walkthrough/CreateProfile/CreateProfileViewController.swift @@ -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) } - } diff --git a/Ring/Ring/Features/Walkthrough/CreateProfile/CreateProfileViewModel.swift b/Ring/Ring/Features/Walkthrough/CreateProfile/CreateProfileViewModel.swift index e0ec8421c8333710cf2b4eb0a1bc6cf73afa6822..64289acb59a138b840c7c63853232066910c57f6 100644 --- a/Ring/Ring/Features/Walkthrough/CreateProfile/CreateProfileViewModel.swift +++ b/Ring/Ring/Features/Walkthrough/CreateProfile/CreateProfileViewModel.swift @@ -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() required init (with injectionBag: InjectionBag) { + } func proceedWithAccountCreationOrDeviceLink() { diff --git a/Ring/Ring/Features/Walkthrough/WalkthroughCoordinator.swift b/Ring/Ring/Features/Walkthrough/WalkthroughCoordinator.swift index 00383f49bbc2cf51ebea6e94375461b262cb29b7..09fcad41e48d66a95a89fdf97df82c14ffcabc64 100644 --- a/Ring/Ring/Features/Walkthrough/WalkthroughCoordinator.swift +++ b/Ring/Ring/Features/Walkthrough/WalkthroughCoordinator.swift @@ -80,7 +80,7 @@ class WalkthroughCoordinator: Coordinator, StateableResponsive { func start () { let welcomeViewController = WelcomeViewController.instantiate(with: self.injectionBag) - self.present(viewController: welcomeViewController, withStyle: .show, withAnimation: true, withStateable: welcomeViewController.viewModel) + self.present(viewController: welcomeViewController, withStyle: .show, withAnimation: false, withStateable: welcomeViewController.viewModel) } private func showCreateProfile (with walkthroughType: WalkthroughType) { diff --git a/Ring/Ring/Features/Walkthrough/Welcome/WelcomeViewController.storyboard b/Ring/Ring/Features/Walkthrough/Welcome/WelcomeViewController.storyboard index 1b3784e74efb8aceab1997bfe815dcd5baed4d67..c08ebb757abb0dc15c07375d9812951a1bcbf12c 100644 --- a/Ring/Ring/Features/Walkthrough/Welcome/WelcomeViewController.storyboard +++ b/Ring/Ring/Features/Walkthrough/Welcome/WelcomeViewController.storyboard @@ -1,5 +1,5 @@ <?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="ILs-xb-iKr"> +<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="ILs-xb-iKr"> <device id="retina4_7" orientation="portrait"> <adaptation id="fullscreen"/> </device> @@ -51,7 +51,7 @@ <nil key="highlightedColor"/> </label> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="Welcome to Ring" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Lbn-cd-UXk"> - <rect key="frame" x="97" y="319" width="180.5" height="29"/> + <rect key="frame" x="97.5" y="319" width="180.5" height="29"/> <fontDescription key="fontDescription" type="system" pointSize="24"/> <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/> <nil key="highlightedColor"/> diff --git a/Ring/Ring/Resources/en.lproj/Localizable.strings b/Ring/Ring/Resources/en.lproj/Localizable.strings index 1735727532d37a56ea8ef42b307ad288c09fb1f6..7b475e5ae8f4d1bd0013f62f614cb21d9034a2ce 100644 --- a/Ring/Ring/Resources/en.lproj/Localizable.strings +++ b/Ring/Ring/Resources/en.lproj/Localizable.strings @@ -41,6 +41,7 @@ //Creation Profile Screen "createProfile.createAccount" = "Skip to Create Account"; +"createProfile.createAccountWithProfile" = "Next"; "createProfile.linkDevice" = "Skip to Link Device"; //Create Account form @@ -65,3 +66,6 @@ "alerts.accountNoNetworkMessage" = "Could not add account because Ring couldn't connect to the distributed network. Check your device connectivity."; "alerts.accountDefaultErrorTitle" = "Unknown error"; "alerts.accountDefaultErrorMessage" = "The account couldn't be created."; +"alerts.profileTakePhoto" = "Take photo"; +"alerts.profileUploadPhoto" = "Upload photo"; +"alerts.profileCancelPhoto" = "Cancel"; diff --git a/Ring/Ring/Services/AccountsService.swift b/Ring/Ring/Services/AccountsService.swift index 2b76d2b2d2c0fd2534f3ca1c692f91dba64a8a0b..994bfd8b56d244f96349b9be391dc7310df9bd20 100644 --- a/Ring/Ring/Services/AccountsService.swift +++ b/Ring/Ring/Services/AccountsService.swift @@ -378,16 +378,4 @@ class AccountsService: AccountAdapterDelegate { self.responseStream.onNext(event) } - // MARK: - profile - - func saveVCard(vCard: CNContact, forAccounr account: AccountModel) -> Observable<Void> { - let vCardSaved = VCardUtils.saveVCard(vCard: vCard, withName: account.id, inFolder: VCardFolders.profile.rawValue) - return vCardSaved - } - - func loadVCard(forAccounr account: AccountModel) -> Single<CNContact> { - let vCardSaved = VCardUtils.loadVCard(named: account.id, inFolder: VCardFolders.profile.rawValue) - return vCardSaved - } - }