2019-09-20 07:53:24 +02:00
import NVActivityIndicatorView
@objc ( LKDeviceLinkingModal )
2019-09-24 03:18:14 +02:00
final class DeviceLinkingModal : Modal , DeviceLinkingSessionDelegate {
2019-09-24 03:59:17 +02:00
private let mode : Mode
private let delegate : DeviceLinkingModalDelegate ?
2019-09-24 03:18:14 +02:00
private var deviceLink : DeviceLink ?
2019-09-20 07:53:24 +02:00
2019-09-24 03:59:17 +02:00
// MARK: T y p e s
enum Mode : String { case master , slave }
2019-09-20 07:53:24 +02:00
// MARK: C o m p o n e n t s
2019-09-23 06:20:03 +02:00
private lazy var topSpacer = UIView . spacer ( withHeight : 8 )
2019-09-20 07:53:24 +02:00
private lazy var spinner = NVActivityIndicatorView ( frame : CGRect . zero , type : . circleStrokeSpin , color : . white , padding : nil )
private lazy var titleLabel : UILabel = {
let result = UILabel ( )
result . textColor = Theme . primaryColor
result . font = UIFont . ows_dynamicTypeHeadlineClamped
result . numberOfLines = 0
result . lineBreakMode = . byWordWrapping
result . textAlignment = . center
return result
} ( )
private lazy var subtitleLabel : UILabel = {
let result = UILabel ( )
result . textColor = Theme . primaryColor
result . font = UIFont . ows_dynamicTypeCaption1Clamped
result . numberOfLines = 0
result . lineBreakMode = . byWordWrapping
result . textAlignment = . center
return result
} ( )
2019-09-23 06:20:03 +02:00
private lazy var mnemonicLabel : UILabel = {
let result = UILabel ( )
result . textColor = Theme . primaryColor
result . font = UIFont . ows_dynamicTypeCaption1Clamped
result . numberOfLines = 0
result . lineBreakMode = . byWordWrapping
result . textAlignment = . center
return result
} ( )
2019-09-26 02:05:00 +02:00
private lazy var buttonStackView : UIStackView = {
let result = UIStackView ( arrangedSubviews : [ authorizeButton , cancelButton ] )
result . axis = . horizontal
result . distribution = . fillEqually
return result
} ( )
2019-09-23 06:20:03 +02:00
private lazy var authorizeButton : OWSFlatButton = {
let result = OWSFlatButton . button ( title : NSLocalizedString ( " Authorize " , comment : " " ) , font : . ows_dynamicTypeBodyClamped , titleColor : . white , backgroundColor : . clear , target : self , selector : #selector ( authorizeDeviceLink ) )
result . setBackgroundColors ( upColor : . clear , downColor : . clear )
return result
} ( )
2019-09-26 02:05:00 +02:00
private lazy var bottomSpacer = UIView . spacer ( withHeight : 8 )
2019-09-23 06:20:03 +02:00
2019-09-20 07:53:24 +02:00
// MARK: L i f e c y c l e
2019-09-24 03:59:17 +02:00
init ( mode : Mode , delegate : DeviceLinkingModalDelegate ? ) {
self . mode = mode
if mode = = . slave {
guard delegate != nil else { preconditionFailure ( " Missing delegate for device linking modal in slave mode. " ) }
}
self . delegate = delegate
super . init ( nibName : nil , bundle : nil )
}
2019-09-24 07:55:03 +02:00
@objc ( initWithMode : delegate : )
convenience init ( modeAsString : String , delegate : DeviceLinkingModalDelegate ? ) {
2019-09-24 03:59:17 +02:00
guard let mode = Mode ( rawValue : modeAsString ) else { preconditionFailure ( " Invalid mode: \( modeAsString ) . " ) }
self . init ( mode : mode , delegate : delegate )
}
required init ? ( coder : NSCoder ) { preconditionFailure ( ) }
override init ( nibName : String ? , bundle : Bundle ? ) { preconditionFailure ( ) }
2019-09-20 07:53:24 +02:00
override func viewDidLoad ( ) {
super . viewDidLoad ( )
2019-09-24 07:05:59 +02:00
switch mode {
case . master : let _ = DeviceLinkingSession . startListeningForLinkingRequests ( with : self )
case . slave : let _ = DeviceLinkingSession . startListeningForLinkingAuthorization ( with : self )
}
2019-09-20 07:53:24 +02:00
}
2019-09-24 02:57:32 +02:00
override func populateContentView ( ) {
2019-09-26 02:05:00 +02:00
let stackView = UIStackView ( arrangedSubviews : [ topSpacer , spinner , UIView . spacer ( withHeight : 8 ) , titleLabel , subtitleLabel , mnemonicLabel , buttonStackView , bottomSpacer ] )
2019-09-23 06:20:03 +02:00
contentView . addSubview ( stackView )
2019-09-24 02:57:32 +02:00
stackView . spacing = 16
2019-09-23 06:20:03 +02:00
stackView . axis = . vertical
2019-09-20 08:47:27 +02:00
spinner . set ( . height , to : 64 )
2019-09-20 07:53:24 +02:00
spinner . startAnimating ( )
2019-09-24 03:59:17 +02:00
titleLabel . text = {
switch mode {
case . master : return NSLocalizedString ( " Waiting for Device " , comment : " " )
case . slave : return NSLocalizedString ( " Waiting for Authorization " , comment : " " )
}
} ( )
subtitleLabel . text = {
switch mode {
2019-09-25 04:22:34 +02:00
case . master : return NSLocalizedString ( " Create a new account on your other device and click \" Link Device \" when you're at the \" Create Your Loki Messenger Account \" step to start the linking process " , comment : " " )
case . slave : return NSLocalizedString ( " Please check that the words below match the ones shown on your other device " , comment : " " )
2019-09-24 03:59:17 +02:00
}
} ( )
mnemonicLabel . isHidden = ( mode = = . master )
2019-09-25 01:15:23 +02:00
if mode = = . slave {
2019-09-25 04:22:34 +02:00
let hexEncodedPublicKey = OWSIdentityManager . shared ( ) . identityKeyPair ( ) ! . hexEncodedPublicKey . removing05PrefixIfNeeded ( )
2019-09-25 01:15:23 +02:00
mnemonicLabel . text = Mnemonic . encode ( hexEncodedString : hexEncodedPublicKey ) . split ( separator : " " ) [ 0. . < 3 ] . joined ( separator : " " )
}
2019-09-23 06:20:03 +02:00
let buttonHeight = cancelButton . button . titleLabel ! . font . pointSize * 48 / 17
authorizeButton . set ( . height , to : buttonHeight )
cancelButton . set ( . height , to : buttonHeight )
authorizeButton . isHidden = true
2019-09-26 02:05:00 +02:00
bottomSpacer . isHidden = true
2019-09-24 02:57:32 +02:00
stackView . pin ( . leading , to : . leading , of : contentView , withInset : 16 )
stackView . pin ( . top , to : . top , of : contentView , withInset : 16 )
contentView . pin ( . trailing , to : . trailing , of : stackView , withInset : 16 )
contentView . pin ( . bottom , to : . bottom , of : stackView , withInset : 16 )
2019-09-20 07:53:24 +02:00
}
// MARK: D e v i c e L i n k i n g
2019-09-24 03:18:14 +02:00
func requestUserAuthorization ( for deviceLink : DeviceLink ) {
2019-09-23 06:20:03 +02:00
self . deviceLink = deviceLink
2019-09-25 01:15:23 +02:00
topSpacer . isHidden = true
spinner . stopAnimating ( )
spinner . isHidden = true
titleLabel . text = NSLocalizedString ( " Linking Request Received " , comment : " " )
2019-09-25 04:22:34 +02:00
subtitleLabel . text = NSLocalizedString ( " Please check that the words below match the ones shown on your other device " , comment : " " )
let hexEncodedPublicKey = deviceLink . slave . hexEncodedPublicKey . removing05PrefixIfNeeded ( )
mnemonicLabel . text = Mnemonic . encode ( hexEncodedString : hexEncodedPublicKey ) . split ( separator : " " ) [ 0. . < 3 ] . joined ( separator : " " )
2019-09-25 01:15:23 +02:00
mnemonicLabel . isHidden = false
authorizeButton . isHidden = false
2019-09-20 07:53:24 +02:00
}
2019-09-23 06:20:03 +02:00
@objc private func authorizeDeviceLink ( ) {
let deviceLink = self . deviceLink !
2019-09-24 07:55:03 +02:00
let linkingAuthorizationMessage = DeviceLinkingUtilities . getLinkingAuthorizationMessage ( for : deviceLink )
2019-09-25 08:30:45 +02:00
( 0. . < 4 ) . forEach { _ in ThreadUtil . enqueue ( linkingAuthorizationMessage ) }
2019-09-24 03:18:14 +02:00
let session = DeviceLinkingSession . current !
2019-09-23 06:20:03 +02:00
session . stopListeningForLinkingRequests ( )
2019-09-25 04:22:34 +02:00
session . markLinkingRequestAsProcessed ( )
2019-09-23 06:20:03 +02:00
dismiss ( animated : true , completion : nil )
2019-09-26 08:23:59 +02:00
let master = DeviceLink . Device ( hexEncodedPublicKey : deviceLink . master . hexEncodedPublicKey , signature : linkingAuthorizationMessage . masterSignature )
let signedDeviceLink = DeviceLink ( between : master , and : deviceLink . slave )
LokiStorageAPI . addDeviceLink ( signedDeviceLink ) . catch { error in
2019-09-26 06:43:37 +02:00
print ( " [Loki] Failed to add device link due to error: \( error ) . " )
}
2019-09-23 06:20:03 +02:00
}
2019-09-24 07:05:59 +02:00
func handleDeviceLinkAuthorized ( _ deviceLink : DeviceLink ) {
2019-09-25 04:22:34 +02:00
let session = DeviceLinkingSession . current !
session . stopListeningForLinkingAuthorization ( )
2019-09-26 02:05:00 +02:00
topSpacer . isHidden = true
spinner . stopAnimating ( )
spinner . isHidden = true
titleLabel . text = NSLocalizedString ( " Device Link Authorized " , comment : " " )
subtitleLabel . text = NSLocalizedString ( " Your device has been linked successfully " , comment : " " )
mnemonicLabel . isHidden = true
buttonStackView . isHidden = true
bottomSpacer . isHidden = false
2019-09-26 06:43:37 +02:00
LokiStorageAPI . addDeviceLink ( deviceLink ) . catch { error in
print ( " [Loki] Failed to add device link due to error: \( error ) . " )
}
2019-09-26 02:05:00 +02:00
Timer . scheduledTimer ( withTimeInterval : 2 , repeats : false ) { _ in
self . delegate ? . handleDeviceLinkAuthorized ( deviceLink )
self . dismiss ( animated : true , completion : nil )
}
2019-09-25 04:22:34 +02:00
}
@objc override func cancel ( ) {
let session = DeviceLinkingSession . current !
session . stopListeningForLinkingRequests ( )
session . markLinkingRequestAsProcessed ( ) // O n l y r e l e v a n t i n m a s t e r m o d e
delegate ? . handleDeviceLinkingModalDismissed ( ) // O n l y r e l e v a n t i n s l a v e m o d e
dismiss ( animated : true , completion : nil )
2019-09-24 07:05:59 +02:00
}
2019-09-20 07:53:24 +02:00
}