mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
235 lines
11 KiB
Swift
235 lines
11 KiB
Swift
|
|
// MARK: - Device Links View Controller
|
|
|
|
@objc(LKDeviceLinksVC)
|
|
final class DeviceLinksVC : BaseVC, UITableViewDataSource, UITableViewDelegate, DeviceLinkingModalDelegate, DeviceNameModalDelegate {
|
|
private var deviceLinks: [DeviceLink] = [] { didSet { updateUI() } }
|
|
|
|
// MARK: Components
|
|
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 = Colors.text
|
|
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
|
|
explanationLabel.numberOfLines = 0
|
|
explanationLabel.lineBreakMode = .byWordWrapping
|
|
explanationLabel.textAlignment = .center
|
|
explanationLabel.text = NSLocalizedString("vc_linked_devices_empty_state_message", comment: "")
|
|
let linkNewDeviceButton = Button(style: .prominentOutline, size: .large)
|
|
linkNewDeviceButton.setTitle(NSLocalizedString("vc_linked_devices_empty_state_button_title", comment: ""), for: UIControl.State.normal)
|
|
linkNewDeviceButton.addTarget(self, action: #selector(linkNewDevice), for: UIControl.Event.touchUpInside)
|
|
linkNewDeviceButton.set(.width, to: 196)
|
|
let result = UIStackView(arrangedSubviews: [ explanationLabel, linkNewDeviceButton ])
|
|
result.axis = .vertical
|
|
result.spacing = Values.mediumSpacing
|
|
result.alignment = .center
|
|
return result
|
|
}()
|
|
|
|
// MARK: Lifecycle
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
setUpGradientBackground()
|
|
setUpNavBarStyle()
|
|
setNavBarTitle(NSLocalizedString("vc_linked_devices_title", comment: ""))
|
|
// Set up link new device button
|
|
let linkNewDeviceButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(linkNewDevice))
|
|
linkNewDeviceButton.tintColor = Colors.text
|
|
navigationItem.rightBarButtonItem = linkNewDeviceButton
|
|
// Set up constraints
|
|
view.addSubview(tableView)
|
|
tableView.pin(to: view)
|
|
view.addSubview(callToActionView)
|
|
callToActionView.center(.horizontal, in: view)
|
|
let verticalCenteringConstraint = callToActionView.center(.vertical, in: view)
|
|
verticalCenteringConstraint.constant = -16 // Makes things appear centered visually
|
|
// Perform initial update
|
|
updateDeviceLinks()
|
|
}
|
|
|
|
// MARK: Data
|
|
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
|
|
let selectedBackgroundView = UIView()
|
|
selectedBackgroundView.backgroundColor = Colors.cellSelected
|
|
cell.selectedBackgroundView = selectedBackgroundView
|
|
let device = deviceLinks[indexPath.row].other
|
|
cell.device = device
|
|
return cell
|
|
}
|
|
|
|
// MARK: Updating
|
|
private func updateDeviceLinks() {
|
|
let storage = OWSPrimaryStorage.shared()
|
|
let userHexEncodedPublicKey = getUserHexEncodedPublicKey()
|
|
var deviceLinks: [DeviceLink] = []
|
|
storage.dbReadConnection.read { transaction in
|
|
deviceLinks = storage.getDeviceLinks(for: userHexEncodedPublicKey, in: transaction).sorted { lhs, rhs in
|
|
return lhs.other.publicKey > rhs.other.publicKey
|
|
}
|
|
}
|
|
self.deviceLinks = deviceLinks
|
|
}
|
|
|
|
private func updateUI() {
|
|
tableView.reloadData()
|
|
UIView.animate(withDuration: 0.25) {
|
|
self.callToActionView.isHidden = !self.deviceLinks.isEmpty
|
|
}
|
|
}
|
|
|
|
func handleDeviceLinkAuthorized(_ deviceLink: DeviceLink) {
|
|
// The modal already dismisses itself
|
|
updateDeviceLinks()
|
|
}
|
|
|
|
func handleDeviceLinkingModalDismissed() {
|
|
// Do nothing
|
|
}
|
|
|
|
// MARK: Interaction
|
|
@objc private func linkNewDevice() {
|
|
if deviceLinks.isEmpty {
|
|
let deviceLinkingModal = DeviceLinkingModal(mode: .master, delegate: self)
|
|
deviceLinkingModal.modalPresentationStyle = .overFullScreen
|
|
deviceLinkingModal.modalTransitionStyle = .crossDissolve
|
|
present(deviceLinkingModal, animated: true, completion: nil)
|
|
} else {
|
|
let alert = UIAlertController(title: NSLocalizedString("vc_linked_devices_multi_device_limit_reached_modal_title", 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) {
|
|
defer { tableView.deselectRow(at: indexPath, animated: true) }
|
|
let deviceLink = deviceLinks[indexPath.row]
|
|
let sheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
|
sheet.addAction(UIAlertAction(title: NSLocalizedString("vc_device_list_bottom_sheet_change_name_button_title", comment: ""), style: .default) { [weak self] _ in
|
|
guard let self = self else { return }
|
|
let deviceNameModal = DeviceNameModal()
|
|
deviceNameModal.device = deviceLink.other
|
|
deviceNameModal.delegate = self
|
|
deviceNameModal.modalPresentationStyle = .overFullScreen
|
|
deviceNameModal.modalTransitionStyle = .crossDissolve
|
|
self.present(deviceNameModal, animated: true, completion: nil)
|
|
})
|
|
sheet.addAction(UIAlertAction(title: NSLocalizedString("vc_device_list_bottom_sheet_unlink_device_button_title", comment: ""), style: .destructive) { [weak self] _ in
|
|
self?.removeDeviceLink(deviceLink)
|
|
})
|
|
sheet.addAction(UIAlertAction(title: NSLocalizedString("cancel", comment: ""), style: .cancel) { _ in })
|
|
present(sheet, animated: true, completion: nil)
|
|
}
|
|
|
|
@objc func handleDeviceNameChanged(to name: String, for device: DeviceLink.Device) {
|
|
dismiss(animated: true, completion: nil)
|
|
updateUI()
|
|
}
|
|
|
|
private func removeDeviceLink(_ deviceLink: DeviceLink) {
|
|
FileServerAPI.removeDeviceLink(deviceLink).done { [weak self] in
|
|
let linkedDevicePublicKey = deviceLink.other.publicKey
|
|
guard let thread = TSContactThread.fetch(uniqueId: TSContactThread.threadId(fromContactId: linkedDevicePublicKey)) else { return }
|
|
let unlinkDeviceMessage = UnlinkDeviceMessage(thread: thread)
|
|
SSKEnvironment.shared.messageSender.send(unlinkDeviceMessage, success: {
|
|
let storage = OWSPrimaryStorage.shared()
|
|
Storage.writeSync { transaction in
|
|
storage.removePreKeyBundle(forContact: linkedDevicePublicKey, transaction: transaction)
|
|
storage.deleteAllSessions(forContact: linkedDevicePublicKey, protocolContext: transaction)
|
|
for groupPublicKey in Storage.getUserClosedGroupPublicKeys() {
|
|
// TODO: Possibly re-implement in the future
|
|
// ClosedGroupsProtocol.removeMembers([ linkedDevicePublicKey ], from: groupPublicKey, using: transaction)
|
|
}
|
|
}
|
|
}, failure: { _ in
|
|
print("[Loki] Failed to send unlink device message.")
|
|
let storage = OWSPrimaryStorage.shared()
|
|
Storage.writeSync { transaction in
|
|
storage.removePreKeyBundle(forContact: linkedDevicePublicKey, transaction: transaction)
|
|
storage.deleteAllSessions(forContact: linkedDevicePublicKey, protocolContext: transaction)
|
|
for groupPublicKey in Storage.getUserClosedGroupPublicKeys() {
|
|
// TODO: Possibly re-implement in the future
|
|
// ClosedGroupsProtocol.removeMembers([ linkedDevicePublicKey ], from: groupPublicKey, using: transaction)
|
|
}
|
|
}
|
|
})
|
|
self?.updateDeviceLinks()
|
|
}.catch { [weak self] _ in
|
|
let alert = UIAlertController(title: "Couldn't Unlink Device", message: "Please check your internet connection and try again", preferredStyle: .alert)
|
|
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), accessibilityIdentifier: nil, style: .default, handler: nil))
|
|
self?.present(alert, animated: true, completion: nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Cell
|
|
|
|
private extension DeviceLinksVC {
|
|
|
|
final class Cell : UITableViewCell {
|
|
var device: DeviceLink.Device! { didSet { update() } }
|
|
|
|
// MARK: Components
|
|
private lazy var titleLabel: UILabel = {
|
|
let result = UILabel()
|
|
result.textColor = Colors.text
|
|
result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
|
|
result.lineBreakMode = .byTruncatingTail
|
|
return result
|
|
}()
|
|
|
|
private lazy var subtitleLabel: UILabel = {
|
|
let result = UILabel()
|
|
result.textColor = Colors.text
|
|
result.font = .systemFont(ofSize: Values.smallFontSize)
|
|
result.lineBreakMode = .byTruncatingTail
|
|
return result
|
|
}()
|
|
|
|
// MARK: Initialization
|
|
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 = Colors.cellBackground
|
|
let stackView = UIStackView(arrangedSubviews: [ titleLabel, subtitleLabel ])
|
|
stackView.axis = .vertical
|
|
stackView.distribution = .equalCentering
|
|
stackView.spacing = Values.verySmallSpacing
|
|
stackView.set(.height, to: 44)
|
|
contentView.addSubview(stackView)
|
|
stackView.pin(.leading, to: .leading, of: contentView, withInset: Values.largeSpacing)
|
|
stackView.pin(.top, to: .top, of: contentView, withInset: 12)
|
|
contentView.pin(.trailing, to: .trailing, of: stackView, withInset: Values.largeSpacing)
|
|
contentView.pin(.bottom, to: .bottom, of: stackView, withInset: 12)
|
|
stackView.set(.width, to: UIScreen.main.bounds.width - 2 * Values.largeSpacing)
|
|
}
|
|
|
|
// MARK: Updating
|
|
private func update() {
|
|
titleLabel.text = device.displayName
|
|
subtitleLabel.text = Mnemonic.hash(hexEncodedString: device.publicKey.removing05PrefixIfNeeded())
|
|
}
|
|
}
|
|
}
|