2019-11-20 02:06:41 +01:00
// MARK: - D e v i c e L i n k s V i e w C o n t r o l l e r
@objc ( LKDeviceLinksVC )
final class DeviceLinksVC : UIViewController , UITableViewDataSource , UITableViewDelegate , DeviceLinkingModalDelegate , DeviceNameModalDelegate {
private var deviceLinks : [ DeviceLink ] = [ ] { didSet { updateUI ( ) } }
// MARK: C o m p o n e n t s
private lazy var tableView : UITableView = {
let result = UITableView ( )
result . dataSource = self
result . delegate = self
result . register ( Cell . self , forCellReuseIdentifier : " Cell " )
result . separatorStyle = . none
result . backgroundColor = . clear
return result
} ( )
private lazy var callToActionView : UIStackView = {
let explanationLabel = UILabel ( )
explanationLabel . textColor = Theme . primaryColor
explanationLabel . font = UIFont . ows_dynamicTypeSubheadlineClamped
explanationLabel . numberOfLines = 0
explanationLabel . lineBreakMode = . byWordWrapping
explanationLabel . textAlignment = . center
explanationLabel . text = NSLocalizedString ( " You don't have any linked devices yet " , comment : " " )
let linkNewDeviceButtonFont = UIFont . ows_dynamicTypeBodyClamped . ows_mediumWeight ( )
let linkNewDeviceButtonHeight = linkNewDeviceButtonFont . pointSize * 48 / 17
let linkNewDeviceButton = OWSFlatButton . button ( title : NSLocalizedString ( " Link a Device " , comment : " " ) , font : linkNewDeviceButtonFont , titleColor : . lokiGreen ( ) , backgroundColor : . clear , target : self , selector : #selector ( linkNewDevice ) )
linkNewDeviceButton . setBackgroundColors ( upColor : . clear , downColor : . clear )
linkNewDeviceButton . autoSetDimension ( . height , toSize : linkNewDeviceButtonHeight )
linkNewDeviceButton . button . contentHorizontalAlignment = . left
let result = UIStackView ( arrangedSubviews : [ explanationLabel , linkNewDeviceButton ] )
result . axis = . vertical
result . spacing = 4
result . alignment = . center
return result
} ( )
// MARK: L i f e c y c l e
override func viewDidLoad ( ) {
title = NSLocalizedString ( " Linked Devices " , comment : " " )
let masterDeviceHexEncodedPublicKey = UserDefaults . standard . string ( forKey : " masterDeviceHexEncodedPublicKey " )
let isMasterDevice = ( masterDeviceHexEncodedPublicKey = = nil )
if isMasterDevice {
navigationItem . rightBarButtonItem = UIBarButtonItem ( barButtonSystemItem : . add , target : self , action : #selector ( linkNewDevice ) )
}
view . backgroundColor = Theme . backgroundColor
view . addSubview ( tableView )
tableView . pin ( to : view )
view . addSubview ( callToActionView )
callToActionView . center ( in : view )
updateDeviceLinks ( )
}
// MARK: D a t a
func tableView ( _ tableView : UITableView , numberOfRowsInSection section : Int ) -> Int {
return deviceLinks . count
}
func tableView ( _ tableView : UITableView , cellForRowAt indexPath : IndexPath ) -> UITableViewCell {
let cell = tableView . dequeueReusableCell ( withIdentifier : " Cell " ) as ! Cell
2019-11-20 04:18:38 +01:00
let selectedBackgroundView = UIView ( )
selectedBackgroundView . backgroundColor = Theme . cellSelectedColor
cell . selectedBackgroundView = selectedBackgroundView
let device = deviceLinks [ indexPath . row ] . other
cell . device = device
2019-11-20 02:06:41 +01:00
return cell
}
// MARK: U p d a t i n g
private func updateDeviceLinks ( ) {
let storage = OWSPrimaryStorage . shared ( )
let userHexEncodedPublicKey = OWSIdentityManager . shared ( ) . identityKeyPair ( ) ! . hexEncodedPublicKey
var deviceLinks : [ DeviceLink ] = [ ]
storage . dbReadConnection . read { transaction in
deviceLinks = storage . getDeviceLinks ( for : userHexEncodedPublicKey , in : transaction ) . sorted { lhs , rhs in
return lhs . other . hexEncodedPublicKey > rhs . other . hexEncodedPublicKey
}
}
self . deviceLinks = deviceLinks
}
private func updateUI ( ) {
tableView . reloadData ( )
UIView . animate ( withDuration : 0.25 ) {
self . callToActionView . isHidden = ! self . deviceLinks . isEmpty
}
}
func handleDeviceLinkAuthorized ( _ deviceLink : DeviceLink ) {
2019-11-21 00:56:27 +01:00
// T h e m o d a l a l r e a d y d i s m i s s e s i t s e l f
2019-11-21 05:55:18 +01:00
updateDeviceLinks ( )
2019-11-20 02:06:41 +01:00
}
func handleDeviceLinkingModalDismissed ( ) {
// D o n o t h i n g
}
// MARK: I n t e r a c t i o n
@objc private func linkNewDevice ( ) {
if deviceLinks . isEmpty {
2019-11-21 00:56:27 +01:00
let deviceLinkingModal = DeviceLinkingModal ( mode : . master , delegate : self )
2019-11-20 02:06:41 +01:00
deviceLinkingModal . modalPresentationStyle = . overFullScreen
present ( deviceLinkingModal , animated : true , completion : nil )
} else {
let alert = UIAlertController ( title : NSLocalizedString ( " Multi Device Limit Reached " , comment : " " ) , message : NSLocalizedString ( " It's currently not allowed to link more than one device. " , comment : " " ) , preferredStyle : . alert )
alert . addAction ( UIAlertAction ( title : NSLocalizedString ( " OK " , comment : " " ) , style : . default , handler : nil ) )
present ( alert , animated : true , completion : nil )
}
}
func tableView ( _ tableView : UITableView , didSelectRowAt indexPath : IndexPath ) {
2019-11-20 04:18:38 +01:00
defer { tableView . deselectRow ( at : indexPath , animated : true ) }
2019-11-20 05:42:41 +01:00
let deviceLink = deviceLinks [ indexPath . row ]
2019-11-20 02:06:41 +01:00
let sheet = UIAlertController ( title : nil , message : nil , preferredStyle : . actionSheet )
sheet . addAction ( UIAlertAction ( title : NSLocalizedString ( " Change Name " , comment : " " ) , style : . default ) { [ weak self ] _ in
guard let self = self else { return }
let deviceNameModal = DeviceNameModal ( )
2019-11-20 05:42:41 +01:00
deviceNameModal . device = deviceLink . other
2019-11-20 02:06:41 +01:00
deviceNameModal . delegate = self
self . present ( deviceNameModal , animated : true , completion : nil )
} )
2019-11-20 05:42:41 +01:00
sheet . addAction ( UIAlertAction ( title : NSLocalizedString ( " Unlink " , comment : " " ) , style : . destructive ) { [ weak self ] _ in
self ? . removeDeviceLink ( deviceLink )
2019-11-20 02:06:41 +01:00
} )
2019-11-20 04:18:38 +01:00
sheet . addAction ( UIAlertAction ( title : NSLocalizedString ( " Cancel " , comment : " " ) , style : . cancel ) { _ in } )
2019-11-20 02:06:41 +01:00
present ( sheet , animated : true , completion : nil )
}
@objc func handleDeviceNameChanged ( to name : String , for device : DeviceLink . Device ) {
2019-11-20 04:18:38 +01:00
dismiss ( animated : true , completion : nil )
2019-11-20 02:06:41 +01:00
updateUI ( )
}
2019-11-20 05:42:41 +01:00
private func removeDeviceLink ( _ deviceLink : DeviceLink ) {
LokiStorageAPI . removeDeviceLink ( deviceLink ) . done { [ weak self ] in
2019-11-21 07:36:48 +01:00
let linkedDeviceHexEncodedPublicKey = deviceLink . other . hexEncodedPublicKey
guard let thread = TSContactThread . fetch ( uniqueId : TSContactThread . threadId ( fromContactId : linkedDeviceHexEncodedPublicKey ) ) else { return }
2019-11-20 05:42:41 +01:00
let unlinkDeviceMessage = UnlinkDeviceMessage ( thread : thread ) !
2019-11-26 00:49:06 +01:00
SSKEnvironment . shared . messageSender . send ( unlinkDeviceMessage , success : {
let storage = OWSPrimaryStorage . shared ( )
storage . dbReadWriteConnection . readWrite { transaction in
storage . deleteAllSessions ( forContact : linkedDeviceHexEncodedPublicKey , protocolContext : transaction )
}
} ) { _ in
print ( " [Loki] Failed to send unlink device message. " )
let storage = OWSPrimaryStorage . shared ( )
storage . dbReadWriteConnection . readWrite { transaction in
storage . deleteAllSessions ( forContact : linkedDeviceHexEncodedPublicKey , protocolContext : transaction )
}
2019-11-21 07:36:48 +01:00
}
2019-11-20 05:42:41 +01:00
self ? . updateDeviceLinks ( )
} . catch { [ weak self ] _ in
let alert = UIAlertController ( title : NSLocalizedString ( " Couldn't Unlink Device " , comment : " " ) , message : NSLocalizedString ( " Please check your internet connection and try again " , comment : " " ) , preferredStyle : . alert )
alert . addAction ( UIAlertAction ( title : NSLocalizedString ( " OK " , comment : " " ) , accessibilityIdentifier : nil , style : . default , handler : nil ) )
self ? . present ( alert , animated : true , completion : nil )
}
}
2019-11-20 02:06:41 +01:00
}
// MARK: - C e l l
private extension DeviceLinksVC {
final class Cell : UITableViewCell {
2019-11-20 04:18:38 +01:00
var device : DeviceLink . Device ! { didSet { update ( ) } }
2019-11-20 02:06:41 +01:00
// MARK: C o m p o n e n t s
private lazy var titleLabel : UILabel = {
let result = UILabel ( )
result . textColor = Theme . primaryColor
2019-11-20 04:18:38 +01:00
let font = UIFont . ows_dynamicTypeSubheadlineClamped
result . font = UIFont ( descriptor : font . fontDescriptor . withSymbolicTraits ( . traitBold ) ! , size : font . pointSize )
2019-11-20 02:06:41 +01:00
result . lineBreakMode = . byTruncatingTail
return result
} ( )
private lazy var subtitleLabel : UILabel = {
let result = UILabel ( )
result . textColor = Theme . primaryColor
result . font = UIFont . ows_dynamicTypeCaption1Clamped
result . lineBreakMode = . byTruncatingTail
return result
} ( )
// MARK: I n i t i a l i z a t i o n
override init ( style : UITableViewCell . CellStyle , reuseIdentifier : String ? ) {
super . init ( style : style , reuseIdentifier : reuseIdentifier )
setUpViewHierarchy ( )
}
required init ? ( coder : NSCoder ) {
super . init ( coder : coder )
setUpViewHierarchy ( )
}
private func setUpViewHierarchy ( ) {
backgroundColor = . clear
let stackView = UIStackView ( arrangedSubviews : [ titleLabel , subtitleLabel ] )
stackView . axis = . vertical
stackView . distribution = . equalCentering
stackView . spacing = 4
stackView . set ( . height , to : 36 )
contentView . addSubview ( stackView )
stackView . pin ( . leading , to : . leading , of : contentView , withInset : 16 )
stackView . pin ( . top , to : . top , of : contentView , withInset : 8 )
contentView . pin ( . trailing , to : . trailing , of : stackView , withInset : 16 )
contentView . pin ( . bottom , to : . bottom , of : stackView , withInset : 8 )
stackView . set ( . width , to : UIScreen . main . bounds . width - 2 * 16 )
}
// MARK: U p d a t i n g
private func update ( ) {
2019-11-20 04:18:38 +01:00
titleLabel . text = device . displayName
2019-11-22 04:22:43 +01:00
subtitleLabel . text = Mnemonic . hash ( hexEncodedString : device . hexEncodedPublicKey . removing05PrefixIfNeeded ( ) )
2019-11-20 02:06:41 +01:00
}
}
}