mirror of https://github.com/oxen-io/session-ios
Create contact view.
parent
9c661b220a
commit
2738bcbc58
@ -0,0 +1,303 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SignalServiceKit
|
||||||
|
import SignalMessaging
|
||||||
|
import Reachability
|
||||||
|
|
||||||
|
class ContactViewController: OWSViewController {
|
||||||
|
|
||||||
|
let TAG = "[ContactView]"
|
||||||
|
|
||||||
|
enum ContactViewMode {
|
||||||
|
case systemContactWithSignal,
|
||||||
|
systemContactWithoutSignal,
|
||||||
|
nonSystemContactWithSignal,
|
||||||
|
nonSystemContactWithoutSignal,
|
||||||
|
noPhoneNumber,
|
||||||
|
unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ContactLookupMode {
|
||||||
|
case notLookingUp,
|
||||||
|
lookingUp,
|
||||||
|
lookedUpNoAccount,
|
||||||
|
lookedUpHasAccount
|
||||||
|
}
|
||||||
|
|
||||||
|
private var hasLoadedView = false
|
||||||
|
|
||||||
|
private var viewMode = ContactViewMode.unknown {
|
||||||
|
didSet {
|
||||||
|
SwiftAssertIsOnMainThread(#function)
|
||||||
|
|
||||||
|
if oldValue != viewMode && hasLoadedView {
|
||||||
|
updateContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var lookupMode = ContactLookupMode.notLookingUp {
|
||||||
|
didSet {
|
||||||
|
SwiftAssertIsOnMainThread(#function)
|
||||||
|
|
||||||
|
if oldValue != lookupMode && hasLoadedView {
|
||||||
|
updateContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let contactsManager: OWSContactsManager
|
||||||
|
|
||||||
|
var reachability: Reachability?
|
||||||
|
|
||||||
|
override public var canBecomeFirstResponder: Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private let contact: OWSContact
|
||||||
|
|
||||||
|
// MARK: - Initializers
|
||||||
|
|
||||||
|
@available(*, unavailable, message: "use init(call:) constructor instead.")
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("Unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
required init(contact: OWSContact) {
|
||||||
|
contactsManager = Environment.current().contactsManager
|
||||||
|
self.contact = contact
|
||||||
|
|
||||||
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
|
||||||
|
tryToDetermineMode()
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(forName: .OWSContactsManagerSignalAccountsDidChange, object: nil, queue: nil) { [weak self] _ in
|
||||||
|
guard let strongSelf = self else { return }
|
||||||
|
strongSelf.tryToDetermineMode()
|
||||||
|
}
|
||||||
|
|
||||||
|
reachability = Reachability.forInternetConnection()
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(forName: .reachabilityChanged, object: nil, queue: nil) { [weak self] _ in
|
||||||
|
guard let strongSelf = self else { return }
|
||||||
|
strongSelf.tryToDetermineMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - View Lifecycle
|
||||||
|
|
||||||
|
override func viewDidDisappear(_ animated: Bool) {
|
||||||
|
super.viewDidDisappear(animated)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
self.becomeFirstResponder()
|
||||||
|
|
||||||
|
contactsManager.requestSystemContactsOnce(completion: { [weak self] _ in
|
||||||
|
guard let strongSelf = self else { return }
|
||||||
|
strongSelf.tryToDetermineMode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
|
self.becomeFirstResponder()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func loadView() {
|
||||||
|
super.loadView()
|
||||||
|
self.view.backgroundColor = UIColor.white
|
||||||
|
|
||||||
|
updateContent()
|
||||||
|
|
||||||
|
hasLoadedView = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private func tryToDetermineMode() {
|
||||||
|
SwiftAssertIsOnMainThread(#function)
|
||||||
|
|
||||||
|
guard let firstPhoneNumber = contact.phoneNumbers?.first else {
|
||||||
|
viewMode = .noPhoneNumber
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if contactsManager.hasSignalAccount(forRecipientId: firstPhoneNumber.phoneNumber) {
|
||||||
|
viewMode = .systemContactWithSignal
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if contactsManager.allContactsMap[firstPhoneNumber.phoneNumber] != nil {
|
||||||
|
// We can infer that this is _not_ a signal user because
|
||||||
|
// all contacts in contactsManager.allContactsMap have
|
||||||
|
// already been looked up.
|
||||||
|
viewMode = .systemContactWithoutSignal
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch lookupMode {
|
||||||
|
case .notLookingUp:
|
||||||
|
lookupMode = .lookingUp
|
||||||
|
viewMode = .unknown
|
||||||
|
ContactsUpdater.shared().lookupIdentifiers([firstPhoneNumber.phoneNumber], success: { [weak self] (signalRecipients) in
|
||||||
|
guard let strongSelf = self else { return }
|
||||||
|
|
||||||
|
let hasSignalAccount = signalRecipients.filter({ (signalRecipient) -> Bool in
|
||||||
|
return signalRecipient.recipientId() == firstPhoneNumber.phoneNumber
|
||||||
|
}).count > 0
|
||||||
|
|
||||||
|
if hasSignalAccount {
|
||||||
|
strongSelf.lookupMode = .lookedUpHasAccount
|
||||||
|
strongSelf.tryToDetermineMode()
|
||||||
|
} else {
|
||||||
|
strongSelf.lookupMode = .lookedUpNoAccount
|
||||||
|
strongSelf.tryToDetermineMode()
|
||||||
|
}
|
||||||
|
}) { [weak self] (error) in
|
||||||
|
guard let strongSelf = self else { return }
|
||||||
|
Logger.error("\(strongSelf.logTag) error looking up contact: \(error)")
|
||||||
|
strongSelf.lookupMode = .notLookingUp
|
||||||
|
strongSelf.tryToDetermineModeRetry()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case .lookingUp:
|
||||||
|
viewMode = .unknown
|
||||||
|
return
|
||||||
|
case .lookedUpNoAccount:
|
||||||
|
viewMode = .nonSystemContactWithoutSignal
|
||||||
|
return
|
||||||
|
case .lookedUpHasAccount:
|
||||||
|
viewMode = .nonSystemContactWithSignal
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func tryToDetermineModeRetry() {
|
||||||
|
// Try again after a minute.
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 60.0) { [weak self] in
|
||||||
|
guard let strongSelf = self else { return }
|
||||||
|
strongSelf.tryToDetermineMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateContent() {
|
||||||
|
SwiftAssertIsOnMainThread(#function)
|
||||||
|
|
||||||
|
for subview in self.view.subviews {
|
||||||
|
subview.removeFromSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: The design calls for no navigation bar, just a back button.
|
||||||
|
let topView = UIView.container()
|
||||||
|
topView.backgroundColor = UIColor(rgbHex: 0xefeff4)
|
||||||
|
topView.preservesSuperviewLayoutMargins = true
|
||||||
|
self.view.addSubview(topView)
|
||||||
|
topView.autoPinEdge(toSuperviewEdge: .top)
|
||||||
|
topView.autoPinWidthToSuperview()
|
||||||
|
|
||||||
|
// TODO: Use actual avatar.
|
||||||
|
let avatarSize = CGFloat(100)
|
||||||
|
let avatarView = UIView.container()
|
||||||
|
avatarView.backgroundColor = UIColor.ows_materialBlue
|
||||||
|
avatarView.layer.cornerRadius = avatarSize * 0.5
|
||||||
|
topView.addSubview(avatarView)
|
||||||
|
avatarView.autoPin(toTopLayoutGuideOf: self, withInset: 0)
|
||||||
|
avatarView.autoHCenterInSuperview()
|
||||||
|
avatarView.autoSetDimension(.width, toSize: avatarSize)
|
||||||
|
avatarView.autoSetDimension(.height, toSize: avatarSize)
|
||||||
|
|
||||||
|
let nameLabel = UILabel()
|
||||||
|
nameLabel.text = contact.displayName
|
||||||
|
nameLabel.font = UIFont.ows_dynamicTypeTitle3
|
||||||
|
nameLabel.textColor = UIColor.black
|
||||||
|
nameLabel.lineBreakMode = .byTruncatingTail
|
||||||
|
nameLabel.textAlignment = .center
|
||||||
|
topView.addSubview(nameLabel)
|
||||||
|
nameLabel.autoPinEdge(.top, to: .bottom, of: avatarView, withOffset: 10)
|
||||||
|
nameLabel.autoPinLeadingToSuperviewMargin()
|
||||||
|
nameLabel.autoPinTrailingToSuperviewMargin()
|
||||||
|
|
||||||
|
var lastView: UIView = nameLabel
|
||||||
|
|
||||||
|
if let firstPhoneNumber = contact.phoneNumbers?.first {
|
||||||
|
let phoneNumberLabel = UILabel()
|
||||||
|
phoneNumberLabel.text = firstPhoneNumber.phoneNumber
|
||||||
|
phoneNumberLabel.font = UIFont.ows_dynamicTypeCaption1
|
||||||
|
phoneNumberLabel.textColor = UIColor.black
|
||||||
|
phoneNumberLabel.lineBreakMode = .byTruncatingTail
|
||||||
|
phoneNumberLabel.textAlignment = .center
|
||||||
|
topView.addSubview(phoneNumberLabel)
|
||||||
|
phoneNumberLabel.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 10)
|
||||||
|
phoneNumberLabel.autoPinLeadingToSuperviewMargin()
|
||||||
|
phoneNumberLabel.autoPinTrailingToSuperviewMargin()
|
||||||
|
lastView = phoneNumberLabel
|
||||||
|
}
|
||||||
|
|
||||||
|
switch viewMode {
|
||||||
|
case .systemContactWithSignal:
|
||||||
|
break
|
||||||
|
case .systemContactWithoutSignal:
|
||||||
|
break
|
||||||
|
case .nonSystemContactWithSignal:
|
||||||
|
break
|
||||||
|
case .nonSystemContactWithoutSignal:
|
||||||
|
break
|
||||||
|
case .noPhoneNumber:
|
||||||
|
break
|
||||||
|
case .unknown:
|
||||||
|
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
|
||||||
|
topView.addSubview(activityIndicator)
|
||||||
|
activityIndicator.autoPinEdge(.top, to: .bottom, of: lastView, withOffset: 10)
|
||||||
|
activityIndicator.autoHCenterInSuperview()
|
||||||
|
lastView = activityIndicator
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
lastView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
// acceptIncomingButton = createButton(image: #imageLiteral(resourceName: "call-active-wide"),
|
||||||
|
// action: #selector(didPressAnswerCall))
|
||||||
|
// acceptIncomingButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_ACCEPT_INCOMING_CALL_LABEL",
|
||||||
|
// comment: "Accessibility label for accepting incoming calls")
|
||||||
|
|
||||||
|
// func createButton(image: UIImage, action: Selector) -> UIButton {
|
||||||
|
// let button = UIButton()
|
||||||
|
// button.setImage(image, for: .normal)
|
||||||
|
// button.imageEdgeInsets = UIEdgeInsets(top: buttonInset(),
|
||||||
|
// left: buttonInset(),
|
||||||
|
// bottom: buttonInset(),
|
||||||
|
// right: buttonInset())
|
||||||
|
// button.addTarget(self, action: action, for: .touchUpInside)
|
||||||
|
// button.autoSetDimension(.width, toSize: buttonSize())
|
||||||
|
// button.autoSetDimension(.height, toSize: buttonSize())
|
||||||
|
// return button
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // MARK: - Layout
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// func didPressFlipCamera(sender: UIButton) {
|
||||||
|
// // toggle value
|
||||||
|
// sender.isSelected = !sender.isSelected
|
||||||
|
//
|
||||||
|
// let useBackCamera = sender.isSelected
|
||||||
|
// Logger.info("\(TAG) in \(#function) with useBackCamera: \(useBackCamera)")
|
||||||
|
//
|
||||||
|
// callUIAdapter.setCameraSource(call: call, useBackCamera: useBackCamera)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// internal func dismissImmediately(completion: (() -> Void)?) {
|
||||||
|
// if ContactView.kShowCallViewOnSeparateWindow {
|
||||||
|
// OWSWindowManager.shared().endCall(self)
|
||||||
|
// completion?()
|
||||||
|
// } else {
|
||||||
|
// self.dismiss(animated: true, completion: completion)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
}
|
Loading…
Reference in New Issue