diff --git a/Ring/Cartfile b/Ring/Cartfile index 71113d6da73a6a23bc79672859f07d23848c311a..53cd434b9f013fc7a63ad980a9d2c1bf4b4ce1d6 100644 --- a/Ring/Cartfile +++ b/Ring/Cartfile @@ -4,3 +4,4 @@ github "pkluz/PKHUD" github "AliSoftware/Reusable" ~> 4.0 github "SwiftyBeaver/SwiftyBeaver" github "ViccAlexander/Chameleon" +github "andreamazz/AMPopTip" diff --git a/Ring/Cartfile.resolved b/Ring/Cartfile.resolved index a286e0b595c40252ded864aea904c156d575932c..6c11cfdb3944f140ac9167d7f556a5e9193b4bfc 100644 --- a/Ring/Cartfile.resolved +++ b/Ring/Cartfile.resolved @@ -2,7 +2,8 @@ github "AliSoftware/Reusable" "4.0.1" github "ReactiveX/RxSwift" "3.5.0" github "RxSwiftCommunity/RxDataSources" "1.0.4" github "RxSwiftCommunity/RxRealm" "0.6.0" -github "SwiftyBeaver/SwiftyBeaver" "1.3.0" +github "SwiftyBeaver/SwiftyBeaver" "1.4.2" github "ViccAlexander/Chameleon" "2.2.0" +github "andreamazz/AMPopTip" "3.0.2" github "pkluz/PKHUD" "4.2.3" github "realm/realm-cocoa" "v2.8.3" diff --git a/Ring/Ring.xcodeproj/project.pbxproj b/Ring/Ring.xcodeproj/project.pbxproj index 44a53462ba7c8a9319953ad1570333d9b643b31e..ab3813784e6d8e0eaef0d31f65b69fd105f75b62 100644 --- a/Ring/Ring.xcodeproj/project.pbxproj +++ b/Ring/Ring.xcodeproj/project.pbxproj @@ -89,6 +89,7 @@ 0ED2B6FA1F96A075001572F0 /* LinkNewDeviceViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0ED2B6F91F96A075001572F0 /* LinkNewDeviceViewController.storyboard */; }; 0ED2B6FC1F96A158001572F0 /* LinkNewDeviceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED2B6FB1F96A158001572F0 /* LinkNewDeviceViewController.swift */; }; 0ED2B6FE1F96A16C001572F0 /* LinkNewDeviceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED2B6FD1F96A16C001572F0 /* LinkNewDeviceViewModel.swift */; }; + 0ED666101F9FED1C00743D42 /* AMPopTip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0ED6660F1F9FED1C00743D42 /* AMPopTip.framework */; }; 0EDCC8601F98150500B121D7 /* UIView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDCC85F1F98150500B121D7 /* UIView+Rx.swift */; }; 0EDE34C71F868E1200FFA15C /* EditProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE34C61F868E1200FFA15C /* EditProfileViewController.swift */; }; 0EDE34C91F8691BB00FFA15C /* EditProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE34C81F8691BB00FFA15C /* EditProfileViewModel.swift */; }; @@ -319,6 +320,7 @@ 0ED2B6F91F96A075001572F0 /* LinkNewDeviceViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LinkNewDeviceViewController.storyboard; sourceTree = "<group>"; }; 0ED2B6FB1F96A158001572F0 /* LinkNewDeviceViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceViewController.swift; sourceTree = "<group>"; }; 0ED2B6FD1F96A16C001572F0 /* LinkNewDeviceViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceViewModel.swift; sourceTree = "<group>"; }; + 0ED6660F1F9FED1C00743D42 /* AMPopTip.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AMPopTip.framework; path = Carthage/Build/iOS/AMPopTip.framework; sourceTree = "<group>"; }; 0EDCC85F1F98150500B121D7 /* UIView+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Rx.swift"; 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>"; }; @@ -450,6 +452,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 0ED666101F9FED1C00743D42 /* AMPopTip.framework in Frameworks */, 0EEFBA3C1F83DA21000EDBAD /* libsecp256k1.a in Frameworks */, 1A3CA32B1F102BB700283748 /* Chameleon.framework in Frameworks */, 1A1E476F1F0E894600EA9A36 /* SwiftyBeaver.framework in Frameworks */, @@ -557,6 +560,7 @@ 02AED8171DD4C4B000F740BA /* Frameworks */ = { isa = PBXGroup; children = ( + 0ED6660F1F9FED1C00743D42 /* AMPopTip.framework */, 0EEFBA3B1F83DA21000EDBAD /* libsecp256k1.a */, 1A3CA32A1F102BB700283748 /* Chameleon.framework */, 1A1E476E1F0E894600EA9A36 /* SwiftyBeaver.framework */, @@ -1177,6 +1181,7 @@ CreatedOnToolsVersion = 7.3.1; DevelopmentTeam = KM95526DS8; LastSwiftMigration = 0810; + ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.BackgroundModes = { enabled = 0; @@ -1276,6 +1281,7 @@ "$(SRCROOT)/Carthage/Build/iOS/Reusable.framework", "$(SRCROOT)/Carthage/Build/iOS/SwiftyBeaver.framework", "$(SRCROOT)/Carthage/Build/iOS/Chameleon.framework", + "$(SRCROOT)/Carthage/build/iOS/AMPopTip.framework", ); name = "⚙️ Copy Frameworks"; outputPaths = ( @@ -1289,6 +1295,7 @@ "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/Reusable.framework", "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/SwiftyBeaver.framework", "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/Chameleon.framework", + "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/AMPopTip.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -1582,6 +1589,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = KM95526DS8; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -1597,6 +1605,7 @@ PRODUCT_BUNDLE_IDENTIFIER = cx.ring; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Ring/Bridging/Ring-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 3.0; @@ -1612,6 +1621,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = KM95526DS8; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -1627,6 +1637,7 @@ PRODUCT_BUNDLE_IDENTIFIER = cx.ring; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Ring/Bridging/Ring-Bridging-Header.h"; SWIFT_VERSION = 3.0; }; diff --git a/Ring/Ring.xcodeproj/xcshareddata/xcschemes/Ring.xcscheme b/Ring/Ring.xcodeproj/xcshareddata/xcschemes/Ring.xcscheme index 13001f5549320ac44f4ed3c01ee865558a7f02f8..2bfbde28adfcd264fa04d9ec2ef8e1b5d8d65c53 100644 --- a/Ring/Ring.xcodeproj/xcshareddata/xcschemes/Ring.xcscheme +++ b/Ring/Ring.xcodeproj/xcshareddata/xcschemes/Ring.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" shouldUseLaunchSchemeArgsEnv = "YES"> <Testables> <TestableReference @@ -65,6 +66,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/Ring/Ring/Constants/Generated/Images.swift b/Ring/Ring/Constants/Generated/Images.swift index f261f3f9f0f7158b1f3f0872f94d0d15144aaf1f..a1305fc567077e411d87d73922d7032760f92103 100644 --- a/Ring/Ring/Constants/Generated/Images.swift +++ b/Ring/Ring/Constants/Generated/Images.swift @@ -10,6 +10,7 @@ typealias Image = UIImage #endif +// swiftlint:disable superfluous_disable_command // swiftlint:disable file_length @available(*, deprecated, renamed: "ImageAsset") diff --git a/Ring/Ring/Constants/Generated/Storyboards.swift b/Ring/Ring/Constants/Generated/Storyboards.swift index 1e43a4ed299406ea5d97871a1acfb571764ce697..a9d66dbb1e8a99cb48e79d7010f9417059dca15f 100644 --- a/Ring/Ring/Constants/Generated/Storyboards.swift +++ b/Ring/Ring/Constants/Generated/Storyboards.swift @@ -4,6 +4,7 @@ import Foundation import UIKit +// swiftlint:disable superfluous_disable_command // swiftlint:disable file_length protocol StoryboardType { diff --git a/Ring/Ring/Constants/Generated/Strings.swift b/Ring/Ring/Constants/Generated/Strings.swift index 27e95dc31fceadf87b0a9e4e8a614009f9caaec8..8a0a35e977dd0e6457a027c29e12f5fa014610f7 100644 --- a/Ring/Ring/Constants/Generated/Strings.swift +++ b/Ring/Ring/Constants/Generated/Strings.swift @@ -2,6 +2,7 @@ import Foundation +// swiftlint:disable superfluous_disable_command // swiftlint:disable file_length // swiftlint:disable explicit_type_interface identifier_name line_length nesting type_body_length type_name @@ -15,18 +16,20 @@ enum L10n { enum Alerts { /// Account Added static let accountAddedTitle = L10n.tr("Localizable", "alerts.accountAddedTitle") + /// Account couldn't be found on the Ring network. Make sure it was exported on Ring from an existing device, and that provided credentials are correct. + static let accountCannotBeFoundMessage = L10n.tr("Localizable", "alerts.accountCannotBeFoundMessage") /// Can't find account static let accountCannotBeFoundTitle = L10n.tr("Localizable", "alerts.accountCannotBeFoundTitle") /// The account couldn't be created. static let accountDefaultErrorMessage = L10n.tr("Localizable", "alerts.accountDefaultErrorMessage") /// Unknown error static let accountDefaultErrorTitle = L10n.tr("Localizable", "alerts.accountDefaultErrorTitle") + /// Linking account + static let accountLinkedTitle = L10n.tr("Localizable", "alerts.accountLinkedTitle") /// Could not add account because Ring couldn't connect to the distributed network. Check your device connectivity. static let accountNoNetworkMessage = L10n.tr("Localizable", "alerts.accountNoNetworkMessage") /// Can't connect to the network static let accountNoNetworkTitle = L10n.tr("Localizable", "alerts.accountNoNetworkTitle") - /// Account couldn't be found on the Ring network. Make sure it was exported on Ring from an existing device, and that provided credentials are correct. - static let acountCannotBeFoundMessage = L10n.tr("Localizable", "alerts.acountCannotBeFoundMessage") /// Cancel static let profileCancelPhoto = L10n.tr("Localizable", "alerts.profileCancelPhoto") /// Take photo @@ -97,6 +100,23 @@ enum L10n { static let title = L10n.tr("Localizable", "linkDevice.title") } + enum Linktoaccount { + /// To generate the PIN code, go to the account managment settings on device that contain account you want to use. In devices settings Select "Link another device to this account". You will get the necessary PIN to complete this form. The PIN is only valid for 10 minutes. + static let explanationPinMessage = L10n.tr("Localizable", "linkToAccount.explanationPinMessage") + /// Link device + static let linkButtonTitle = L10n.tr("Localizable", "linkToAccount.linkButtonTitle") + /// Enter Password + static let passwordLabel = L10n.tr("Localizable", "linkToAccount.passwordLabel") + /// password + static let passwordPlaceholder = L10n.tr("Localizable", "linkToAccount.passwordPlaceholder") + /// Enter PIN + static let pinLabel = L10n.tr("Localizable", "linkToAccount.pinLabel") + /// PIN + static let pinPlaceholder = L10n.tr("Localizable", "linkToAccount.pinPlaceholder") + /// Account linking + static let waitLinkToAccountTitle = L10n.tr("Localizable", "linkToAccount.waitLinkToAccountTitle") + } + enum Smartlist { /// Conversations static let conversations = L10n.tr("Localizable", "smartlist.conversations") diff --git a/Ring/Ring/Features/Walkthrough/CreateAccount/CreateAccountViewModel.swift b/Ring/Ring/Features/Walkthrough/CreateAccount/CreateAccountViewModel.swift index 7adedbcfa07b85eaea07deb90714bde0ca2754c5..196e4f3a258bf79b01165afad18961e8f2f635cd 100644 --- a/Ring/Ring/Features/Walkthrough/CreateAccount/CreateAccountViewModel.swift +++ b/Ring/Ring/Features/Walkthrough/CreateAccount/CreateAccountViewModel.swift @@ -103,6 +103,7 @@ enum AccountCreationError: Error { case generic case network case unknown + case linkError } extension AccountCreationError: LocalizedError { @@ -113,6 +114,8 @@ extension AccountCreationError: LocalizedError { return L10n.Alerts.accountCannotBeFoundTitle case .network: return L10n.Alerts.accountNoNetworkTitle + case .linkError: + return L10n.Alerts.accountCannotBeFoundTitle default: return L10n.Alerts.accountDefaultErrorTitle } @@ -124,6 +127,8 @@ extension AccountCreationError: LocalizedError { return L10n.Alerts.accountDefaultErrorMessage case .network: return L10n.Alerts.accountNoNetworkMessage + case .linkError: + return L10n.Alerts.accountCannotBeFoundMessage default: return L10n.Alerts.accountDefaultErrorMessage } diff --git a/Ring/Ring/Features/Walkthrough/LinkDevice/LinkDeviceViewController.storyboard b/Ring/Ring/Features/Walkthrough/LinkDevice/LinkDeviceViewController.storyboard index 92385380446b885d53abeb4e216f0397479669a2..0db2216352dc8be45d39df091e4a28cadcd69b8d 100644 --- a/Ring/Ring/Features/Walkthrough/LinkDevice/LinkDeviceViewController.storyboard +++ b/Ring/Ring/Features/Walkthrough/LinkDevice/LinkDeviceViewController.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="13196" 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"/> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13174"/> + <capability name="Constraints to layout margins" minToolsVersion="6.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> <scenes> @@ -36,12 +37,48 @@ </userDefinedRuntimeAttribute> </userDefinedRuntimeAttributes> </view> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Choose password" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9AD-b5-bun"> + <rect key="frame" x="36" y="165" width="137" height="25"/> + <constraints> + <constraint firstAttribute="height" constant="25" id="g3B-PW-uya"/> + </constraints> + <fontDescription key="fontDescription" type="system" pointSize="17"/> + <nil key="textColor"/> + <nil key="highlightedColor"/> + </label> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enter pin" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VN2-rO-YYy"> + <rect key="frame" x="36" y="40" width="68.5" height="25"/> + <constraints> + <constraint firstAttribute="height" constant="25" id="TAS-dT-bOV"/> + </constraints> + <fontDescription key="fontDescription" type="system" pointSize="17"/> + <nil key="textColor"/> + <nil key="highlightedColor"/> + </label> + <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Password" textAlignment="natural" clearsOnBeginEditing="YES" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="TH9-pF-YYq"> + <rect key="frame" x="36" y="210" width="302" height="30"/> + <constraints> + <constraint firstAttribute="height" constant="30" id="AeX-R9-vlN"/> + </constraints> + <nil key="textColor"/> + <fontDescription key="fontDescription" type="system" pointSize="14"/> + <textInputTraits key="textInputTraits"/> + </textField> + <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Pin" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="Cuu-Tq-miP"> + <rect key="frame" x="36" y="85" width="303" height="30"/> + <nil key="textColor"/> + <fontDescription key="fontDescription" type="system" pointSize="14"/> + <textInputTraits key="textInputTraits"/> + </textField> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="infoLight" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="rtk-w4-zfQ"> + <rect key="frame" x="124.5" y="43" width="22" height="22"/> + </button> <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="290" 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"/> - <constraint firstAttribute="height" constant="40" id="uVC-dY-9Ym"/> + <constraint firstAttribute="height" constant="40" id="aUU-qY-tFB"/> + <constraint firstAttribute="width" constant="200" id="oIL-uO-iFk"/> </constraints> <state key="normal" title="Link Device"> <color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/> @@ -54,18 +91,36 @@ <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 firstItem="5Po-6e-k14" firstAttribute="centerX" secondItem="N1T-Xh-FH1" secondAttribute="centerX" id="4eR-3k-6Z2"/> + <constraint firstItem="rtk-w4-zfQ" firstAttribute="bottom" secondItem="VN2-rO-YYy" secondAttribute="bottom" id="48M-BJ-5M7"/> <constraint firstItem="Gv4-18-FVt" firstAttribute="top" secondItem="N1T-Xh-FH1" secondAttribute="top" id="5PT-u8-86G"/> + <constraint firstItem="rtk-w4-zfQ" firstAttribute="leading" secondItem="VN2-rO-YYy" secondAttribute="trailing" constant="20" id="9tE-eh-oaC"/> + <constraint firstItem="TH9-pF-YYq" firstAttribute="top" secondItem="9AD-b5-bun" secondAttribute="bottom" constant="20" id="Cgg-Sj-c4x"/> <constraint firstItem="GVt-PH-FqG" firstAttribute="top" secondItem="Gv4-18-FVt" secondAttribute="bottom" id="DHv-Q6-GhU"/> + <constraint firstItem="5Po-6e-k14" firstAttribute="centerX" secondItem="N1T-Xh-FH1" secondAttribute="centerX" id="DdK-br-kKZ"/> + <constraint firstItem="5Po-6e-k14" firstAttribute="top" secondItem="TH9-pF-YYq" secondAttribute="bottom" constant="50" id="FZx-BT-87Q"/> <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="Cuu-Tq-miP" firstAttribute="top" secondItem="VN2-rO-YYy" secondAttribute="bottom" constant="20" id="MvO-t6-2LS"/> + <constraint firstItem="Cuu-Tq-miP" firstAttribute="leading" secondItem="N1T-Xh-FH1" secondAttribute="leadingMargin" constant="20" id="PCJ-vm-jbA"/> + <constraint firstItem="TH9-pF-YYq" firstAttribute="leading" secondItem="N1T-Xh-FH1" secondAttribute="leadingMargin" constant="20" id="XhT-uY-a4w"/> + <constraint firstItem="Cuu-Tq-miP" firstAttribute="centerX" secondItem="N1T-Xh-FH1" secondAttribute="centerX" id="Y9x-Wm-hls"/> + <constraint firstItem="9AD-b5-bun" firstAttribute="top" secondItem="Cuu-Tq-miP" secondAttribute="bottom" constant="50" id="ZbK-P2-lTS"/> + <constraint firstItem="5Po-6e-k14" firstAttribute="centerX" secondItem="N1T-Xh-FH1" secondAttribute="centerX" id="a6Y-pC-oVh"/> <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="VN2-rO-YYy" firstAttribute="leading" secondItem="N1T-Xh-FH1" secondAttribute="leadingMargin" constant="20" id="lir-vx-FR8"/> + <constraint firstItem="9AD-b5-bun" firstAttribute="leading" secondItem="VN2-rO-YYy" secondAttribute="leading" id="pEu-Md-SRd"/> + <constraint firstItem="VN2-rO-YYy" firstAttribute="top" secondItem="jiD-fm-HFk" secondAttribute="bottom" constant="20" id="qQg-eH-gLE"/> <constraint firstItem="kur-G7-4Nq" firstAttribute="leading" secondItem="N1T-Xh-FH1" secondAttribute="leading" id="r5d-rQ-Kg3"/> + <constraint firstItem="TH9-pF-YYq" firstAttribute="centerX" secondItem="N1T-Xh-FH1" secondAttribute="centerX" id="xpu-85-bbk"/> </constraints> </view> <connections> - <outlet property="linkButton" destination="5Po-6e-k14" id="OoP-zB-985"/> + <outlet property="linkButton" destination="5Po-6e-k14" id="qN6-zI-dIS"/> + <outlet property="passwordLabel" destination="9AD-b5-bun" id="Neh-tt-Ui8"/> + <outlet property="passwordTextField" destination="TH9-pF-YYq" id="vYS-Up-eeb"/> + <outlet property="pinInfoButton" destination="rtk-w4-zfQ" id="eo1-z8-7RN"/> + <outlet property="pinLabel" destination="VN2-rO-YYy" id="qaM-sZ-9hX"/> + <outlet property="pinTextField" destination="Cuu-Tq-miP" id="6Jk-h2-tyr"/> </connections> </viewController> <placeholder placeholderIdentifier="IBFirstResponder" id="6ma-i4-SuK" userLabel="First Responder" sceneMemberID="firstResponder"/> diff --git a/Ring/Ring/Features/Walkthrough/LinkDevice/LinkDeviceViewController.swift b/Ring/Ring/Features/Walkthrough/LinkDevice/LinkDeviceViewController.swift index fcfddf3e1b53dce12ab9059f6514730290f7324b..714cc3789d77d12d8966c1ad7c7065a7a8151a8a 100644 --- a/Ring/Ring/Features/Walkthrough/LinkDevice/LinkDeviceViewController.swift +++ b/Ring/Ring/Features/Walkthrough/LinkDevice/LinkDeviceViewController.swift @@ -9,23 +9,108 @@ import UIKit import Reusable import RxSwift +import PKHUD +import AMPopTip class LinkDeviceViewController: UIViewController, StoryboardBased, ViewModelBased { // MARK: outlets @IBOutlet weak var linkButton: DesignableButton! - + @IBOutlet weak var pinTextField: UITextField! + @IBOutlet weak var passwordTextField: UITextField! + @IBOutlet weak var pinInfoButton: UIButton! + @IBOutlet weak var pinLabel: UILabel! + @IBOutlet weak var passwordLabel: UILabel! // MARK: members private let disposeBag = DisposeBag() var viewModel: LinkDeviceViewModel! + let popTip = PopTip() // MARK: functions override func viewDidLoad() { super.viewDidLoad() + self.applyL10n() + + //bind view model to view + self.pinInfoButton.rx.tap.subscribe(onNext: { [unowned self] (_) in + self.showPinInfo() + }).disposed(by: self.disposeBag) + self.linkButton.rx.tap.subscribe(onNext: { [unowned self] (_) in self.viewModel.linkDevice() }).disposed(by: self.disposeBag) + + // handle linking state + self.viewModel.createState + .observeOn(MainScheduler.instance) + .subscribe(onNext: { [weak self] (state) in + switch state { + case .started: + self?.showCreationHUD() + case .success: + self?.hideHud() + self?.showLinkedSuccess() + case .error (let error): + self?.hideHud() + self?.showAccountCreationError(error: error) + default: + self?.hideHud() + } + }, onError: { [weak self] (error) in + self?.hideHud() + + if let error = error as? AccountCreationError { + self?.showAccountCreationError(error: error) + } + }).disposed(by: self.disposeBag) + + self.viewModel.linkButtonEnabledState.bind(to: self.linkButton.rx.isEnabled) + .disposed(by: self.disposeBag) + + // bind view to view model + self.pinTextField.rx.text.orEmpty.bind(to: self.viewModel.pin).disposed(by: self.disposeBag) + self.passwordTextField.rx.text.orEmpty.bind(to: self.viewModel.password).disposed(by: self.disposeBag) + } + + private func applyL10n() { + self.linkButton.setTitle(L10n.Linktoaccount.linkButtonTitle, for: .normal) + self.pinLabel.text = L10n.Linktoaccount.pinLabel + self.passwordLabel.text = L10n.Linktoaccount.passwordLabel + self.pinTextField.placeholder = L10n.Linktoaccount.pinPlaceholder + self.passwordTextField.placeholder = L10n.Linktoaccount.passwordPlaceholder } + private func showCreationHUD() { + HUD.show(.labeledProgress(title: L10n.Linktoaccount.waitLinkToAccountTitle, subtitle: nil)) + } + + private func showLinkedSuccess() { + HUD.flash(.labeledSuccess(title: L10n.Alerts.accountLinkedTitle, subtitle: nil), delay: Durations.alertFlashDuration.value) + } + + private func hideHud() { + HUD.hide() + } + + private func showAccountCreationError(error: AccountCreationError) { + let alert = UIAlertController.init(title: error.title, + message: error.message, + preferredStyle: .alert) + alert.addAction(UIAlertAction.init(title: L10n.Global.ok, style: .default, handler: nil)) + self.present(alert, animated: true, completion: nil) + } + + private func showPinInfo() { + if popTip.isVisible { + popTip.hide() + } else { + popTip.shouldDismissOnTap = true + popTip.entranceAnimation = .scale + popTip.bubbleColor = UIColor.ringSecondary + popTip.textColor = UIColor.white + popTip.show(text: L10n.Linktoaccount.explanationPinMessage, direction: .down, + maxWidth: 250, in: self.view, from: pinInfoButton.frame) + } + } } diff --git a/Ring/Ring/Features/Walkthrough/LinkDevice/LinkDeviceViewModel.swift b/Ring/Ring/Features/Walkthrough/LinkDevice/LinkDeviceViewModel.swift index 71bba384baf915ed99642b81b891988b67c8ef1b..8364598fab89cb0a219a066c7d6f1af0c277e1fa 100644 --- a/Ring/Ring/Features/Walkthrough/LinkDevice/LinkDeviceViewModel.swift +++ b/Ring/Ring/Features/Walkthrough/LinkDevice/LinkDeviceViewModel.swift @@ -28,12 +28,51 @@ class LinkDeviceViewModel: Stateable, ViewModel { lazy var state: Observable<State> = { return self.stateSubject.asObservable() }() + private let accountService: AccountsService + private let accountCreationState = Variable<AccountCreationState>(.unknown) + lazy var createState: Observable<AccountCreationState> = { + return self.accountCreationState.asObservable() + }() + + lazy var linkButtonEnabledState: Observable<Bool> = { + return Observable.combineLatest(self.password.asObservable(), + self.pin.asObservable()) {(password, pin) -> Bool in + if !password.isEmpty && !pin.isEmpty { + return true + } + return false + } + }() + + let pin = Variable<String>("") + let password = Variable<String>("") + let disposeBag = DisposeBag() required init (with injectionBag: InjectionBag) { + self.accountService = injectionBag.accountService + + //Account creation state observer + self.accountService + .sharedResponseStream + .subscribe(onNext: { [unowned self] event in + if event.getEventInput(ServiceEventInput.registrationState) == Registered { + self.accountCreationState.value = .success + Observable<Int>.timer(Durations.alertFlashDuration.value, period: nil, scheduler: MainScheduler.instance).subscribe(onNext: { [unowned self] (_) in + self.stateSubject.onNext(WalkthroughState.deviceLinked) + }).disposed(by: self.disposeBag) + } else if event.getEventInput(ServiceEventInput.registrationState) == ErrorGeneric { + self.accountCreationState.value = .error(error: AccountCreationError.linkError) + } else if event.getEventInput(ServiceEventInput.registrationState) == ErrorNetwork { + self.accountCreationState.value = .error(error: AccountCreationError.network) + } + }, onError: { [unowned self] _ in + self.accountCreationState.value = .error(error: AccountCreationError.unknown) + }).disposed(by: disposeBag) } func linkDevice () { - self.stateSubject.onNext(WalkthroughState.deviceLinked) + self.accountCreationState.value = .started + self.accountService.linkToRingAccount(withPin: self.pin.value, + password: self.password.value) } - } diff --git a/Ring/Ring/Resources/en.lproj/Localizable.strings b/Ring/Ring/Resources/en.lproj/Localizable.strings index a6550930b5223dfdad5c1457e949e5987e58d971..b25c012be9015d47a87b41f7e416110ce21f8d9b 100644 --- a/Ring/Ring/Resources/en.lproj/Localizable.strings +++ b/Ring/Ring/Resources/en.lproj/Localizable.strings @@ -58,9 +58,18 @@ "createAccount.loading" = "Loading"; "createAccount.waitCreateAccountTitle" = "Adding account"; +//Link To Account form +"linkToAccount.waitLinkToAccountTitle" = "Account linking"; +"linkToAccount.linkButtonTitle" = "Link device"; +"linkToAccount.passwordPlaceholder" = "password"; +"linkToAccount.pinPlaceholder" = "PIN"; +"linkToAccount.passwordLabel" = "Enter Password"; +"linkToAccount.pinLabel" = "Enter PIN"; +"linkToAccount.explanationPinMessage" = "To generate the PIN code, go to the account managment settings on device that contain account you want to use. In devices settings Select \"Link another device to this account\". You will get the necessary PIN to complete this form. The PIN is only valid for 10 minutes."; + //Alerts "alerts.accountCannotBeFoundTitle" = "Can't find account"; -"alerts.acountCannotBeFoundMessage" = "Account couldn't be found on the Ring network. Make sure it was exported on Ring from an existing device, and that provided credentials are correct."; +"alerts.accountCannotBeFoundMessage" = "Account couldn't be found on the Ring network. Make sure it was exported on Ring from an existing device, and that provided credentials are correct."; "alerts.accountAddedTitle" = "Account Added"; "alerts.accountNoNetworkTitle" = "Can't connect to the network"; "alerts.accountNoNetworkMessage" = "Could not add account because Ring couldn't connect to the distributed network. Check your device connectivity."; @@ -69,6 +78,7 @@ "alerts.profileTakePhoto" = "Take photo"; "alerts.profileUploadPhoto" = "Upload photo"; "alerts.profileCancelPhoto" = "Cancel"; +"alerts.accountLinkedTitle" = "Linking account"; //Account Page "accountPage.devicesListHeader" = "Devices"; diff --git a/Ring/Ring/Services/AccountsService.swift b/Ring/Ring/Services/AccountsService.swift index 4c7cc9a9ce07745fd7ec19e30f808464219a5688..528bdb79ee167d4d951aa9ef07634eecf6658005 100644 --- a/Ring/Ring/Services/AccountsService.swift +++ b/Ring/Ring/Services/AccountsService.swift @@ -220,6 +220,42 @@ class AccountsService: AccountAdapterDelegate { } } + func linkToRingAccount(withPin pin: String, password: String) { + do { + var ringDetails = try self.getRingInitialAccountDetails() + ringDetails.updateValue(password, forKey: ConfigKey.archivePassword.rawValue) + ringDetails.updateValue(pin, forKey: ConfigKey.archivePIN.rawValue) + let accountId = self.accountAdapter.addAccount(ringDetails) + guard accountId != nil else { + throw AddAccountError.unknownError + } + + var account = self.getAccount(fromAccountId: accountId!) + + if account == nil { + let details = self.getAccountDetails(fromAccountId: accountId!) + let volatileDetails = self.getVolatileAccountDetails(fromAccountId: accountId!) + let credentials = try self.getAccountCredentials(fromAccountId: accountId!) + let devices = getKnownRingDevices(fromAccountId: accountId!) + + account = try AccountModel(withAccountId: accountId!, + details: details, + volatileDetails: volatileDetails, + credentials: credentials, + devices: devices) + + let accountModelHelper = AccountModelHelper(withAccount: account!) + var accountAddedEvent = ServiceEvent(withEventType: .accountAdded) + accountAddedEvent.addEventInput(.id, value: account?.id) + accountAddedEvent.addEventInput(.state, value: accountModelHelper.getRegistrationState()) + self.responseStream.onNext(accountAddedEvent) + } + self.currentAccount = account + } catch { + self.responseStream.onError(error) + } + } + /** Entry point to create a brand-new SIP account.