// _____ _
// |_ _| |_ _ _ ___ ___ _ __ __ _
// | | | ' \| '_/ -_) -_) ' \/ _` |_
// |_| |_||_|_| \___\___|_|_|_\__,_(_)
//
// Threema iOS Client
// Copyright (c) 2019-2020 Threema GmbH
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License, version 3,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
import UIKit
import ThreemaFramework
import QuartzCore
import Contacts
import ContactsUI
@objc protocol ContactDetailsViewControllerDelegate: class {
@objc func present(contactDetailsViewController: ContactDetailsViewController, onCompletion: @escaping ((_ contactsDetailsViewController: ContactDetailsViewController) -> Void))
}
class ContactDetailsViewController: ThemedTableViewController {
@IBOutlet weak var headerView: UIView!
@IBOutlet weak var disclosureButton: UIButton!
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var companyNameLabel: UILabel!
@IBOutlet weak var threemaTypeIcon: UIButton!
@IBOutlet weak var scanQrCodeBarButtonItem: UIBarButtonItem!
@objc var contact: Contact?
@objc var hideActionButtons: Bool = false
@objc weak var delegate : ContactDetailsViewControllerDelegate?
private var didHideTabBar: Bool = false
private var callNumbers: [String]?
private var cnAddressBook: CNContactStore = CNContactStore()
private var cnContact: CNContact?
private var cnContactViewShowing: Bool = false
private var canExportConversation: Bool = false
private var conversation: Conversation?
private var showcase: MaterialShowcase?
private var kvoContact: NSKeyValueObservation?
private let THREEMA_ID_SHARE_LINK = "https://threema.id/"
override internal var shouldAutorotate : Bool {
return true
}
override internal var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .pad {
return UIInterfaceOrientationMask.all
}
return UIInterfaceOrientationMask.allButUpsideDown
}
override internal var previewActionItems: [UIPreviewActionItem] {
let sendMessageAction = UIPreviewAction.init(title: BundleUtil.localizedString(forKey: "send_message"), style: .default) { (action, previewController) in
self.sendMessageAction()
}
let scanQrCodeAction = UIPreviewAction.init(title: BundleUtil.localizedString(forKey: "scan_qr"), style: .default) { (action, previewController) in
// we need to present contact details first and present qr scanner on top of that
self.delegate?.present(contactDetailsViewController: self, onCompletion: { (contactsDetailsViewController) in
contactsDetailsViewController.scanIdentityAction()
})
}
return [sendMessageAction, scanQrCodeAction]
}
override func viewDidLoad() {
super.viewDidLoad()
if ScanIdentityController.canScan() == false {
navigationItem.rightBarButtonItem = nil
}
navigationController?.interactivePopGestureRecognizer?.isEnabled = true
navigationController?.interactivePopGestureRecognizer?.delegate = nil
NotificationCenter.default.addObserver(forName: Notification.Name(kNotificationColorThemeChanged), object: nil, queue: nil) { (notification) in
self.setupColors()
}
NotificationCenter.default.addObserver(forName: Notification.Name(kNotificationShowProfilePictureChanged), object: nil, queue: nil) { (notification) in
self.updateView()
}
let disclosureTapRecognizer = UITapGestureRecognizer.init(target: self, action: #selector(tappedHeaderView))
disclosureButton.addGestureRecognizer(disclosureTapRecognizer)
disclosureButton.accessibilityLabel = BundleUtil.localizedString(forKey: "edit_contact")
let tapRecognizer = UITapGestureRecognizer.init(target: self, action: #selector(tappedImage))
imageView.addGestureRecognizer(tapRecognizer)
threemaTypeIcon.setTitle("", for: .normal)
threemaTypeIcon.setBackgroundImage(Utils.threemaTypeIcon(), for: .normal)
setupColors()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if cnContactViewShowing {
cnContactViewShowing = false
ContactStore.shared()?.update(contact)
let statusNavigationBar = navigationController?.navigationBar as! StatusNavigationBar
statusNavigationBar.showOrHideStatusView()
Colors.update(navigationController?.navigationBar)
}
view.alpha = 1.0
updateView()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if navigationController != nil {
if navigationController!.isNavigationBarHidden {
navigationController?.isNavigationBarHidden = false
}
}
if UserSettings.shared().workInfoShown == false && !Utils.hideThreemaTypeIcon(for: contact) {
showWorkInfo()
}
if #available(iOS 13.0, *) {
kvoContact = contact!.observe(\.verificationLevel, options: .new) { (changedContact, change) in
DispatchQueue.main.async {
self.updateView()
}
}
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if #available(iOS 13.0, *) {
kvoContact?.invalidate()
}
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
let fontDescriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .title3)
nameLabel.font = UIFont.boldSystemFont(ofSize: fontDescriptor.pointSize)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EditName" {
let editVC = segue.destination as? EditContactViewController
editVC?.contact = contact
}
else if segue.identifier == "ShowPushSetting" {
let notificationSettingViewController = segue.destination as? NotificationSettingViewController
notificationSettingViewController?.identity = contact?.identity
notificationSettingViewController?.isGroup = false
notificationSettingViewController?.conversation = conversation
}
}
}
extension ContactDetailsViewController {
// MARK: public functions
@objc public func sendMessageAction() {
if let selectedRow = self.tableView!.indexPathForSelectedRow {
self.tableView.deselectRow(at: selectedRow, animated: true)
}
let info: [AnyHashable: Any] = [kKeyContact : contact!, kKeyForceCompose: NSNumber.init(value: true)]
NotificationCenter.default.post(name: NSNotification.Name(rawValue: kNotificationShowConversation), object: nil, userInfo: info)
}
@objc public func scanIdentityAction() {
let scanController = ScanIdentityController.init()
scanController.containingViewController = self
scanController.expectedIdentity = contact?.identity
scanController.popupScanResults = false
scanController.startScan()
}
@objc public func startProfilePictureAction() {
let sender = ContactPhotoSender.init()
sender.startWithImage(toMember: contact, onCompletion: {
UIAlertTemplate .showAlert(owner: self, title: BundleUtil.localizedString(forKey: "my_profilepicture"), message: BundleUtil.localizedString(forKey: "contact_send_profilepicture_success"))
}) { (error) in
UIAlertTemplate .showAlert(owner: self, title: BundleUtil.localizedString(forKey: "my_profilepicture"), message: BundleUtil.localizedString(forKey: "contact_send_profilepicture_error"))
}
if let selectedRow = self.tableView!.indexPathForSelectedRow {
self.tableView.deselectRow(at: selectedRow, animated: true)
}
}
@objc public func startThreemaCallAction(_ startWithVideo: Bool = false) {
if VoIPCallStateManager.shared.currentCallState() == .idle {
var contactSet = Set()
contactSet.insert(contact!)
FeatureMask.check(Int(FEATURE_MASK_VOIP), forContacts: contactSet) { (unsupportedContacts) in
if let selectedRow = self.tableView!.indexPathForSelectedRow {
self.tableView.deselectRow(at: selectedRow, animated: true)
}
if unsupportedContacts == nil {
UIAlertTemplate.showAlert(owner: self, title: BundleUtil.localizedString(forKey: "call_voip_not_supported_title"), message: BundleUtil.localizedString(forKey: "call_voip_not_supported_text"))
return
}
if unsupportedContacts!.count == 0 {
self.startVoipCall(startWithVideo)
} else {
UIAlertTemplate.showAlert(owner: self, title: BundleUtil.localizedString(forKey: "call_voip_not_supported_title"), message: BundleUtil.localizedString(forKey: "call_voip_not_supported_text"))
}
}
} else {
if let selectedRow = self.tableView!.indexPathForSelectedRow {
self.tableView.deselectRow(at: selectedRow, animated: true)
}
}
}
}
extension ContactDetailsViewController {
// MARK: private functions
private func setupColors() {
nameLabel.textColor = Colors.fontNormal()
nameLabel.shadowColor = nil
companyNameLabel.textColor = Colors.fontNormal()
companyNameLabel.shadowColor = nil
let disclosureImage: UIImage
if #available(iOS 13.0, *) {
disclosureImage = disclosureButton.imageView!.image!.withTintColor(Colors.main())
} else {
disclosureImage = disclosureButton.imageView!.image!.withTint(Colors.main())
}
disclosureButton.setImage(disclosureImage, for: .normal)
if #available(iOS 11.0, *) {
imageView.accessibilityIgnoresInvertColors = true
threemaTypeIcon.accessibilityIgnoresInvertColors = true
}
}
private func updateView() {
if scanQrCodeBarButtonItem != nil {
scanQrCodeBarButtonItem.accessibilityLabel = BundleUtil.localizedString(forKey: "scan_identity")
}
navigationItem.title = contact?.displayName
nameLabel.text = contact?.displayName
headerView.accessibilityLabel = contact?.displayName
imageView.image = AvatarMaker.shared()?.avatar(for: contact, size: imageView.frame.size.width, masked: false)
imageView.contentMode = .scaleAspectFill
imageView.layer.masksToBounds = true
imageView.layer.cornerRadius = imageView.bounds.size.width / 2
threemaTypeIcon.isHidden = Utils.hideThreemaTypeIcon(for: contact)
companyNameLabel.text = ""
cnContact = nil
if contact?.cnContactId != nil {
updateViewWithCNContact()
}
companyNameLabel.isHidden = companyNameLabel.text?.count == 0
let headerHeight: CGFloat = companyNameLabel.text?.count == 0 ? 275.0 : 300.0
headerView.frame = CGRect.init(x: headerView.frame.origin.x, y: headerView.frame.origin.y, width: headerView.frame.size.width, height: headerHeight)
isExportConversationEnabled()
if didHideTabBar {
tabBarController?.tabBar.isHidden = false
didHideTabBar = false
}
tableView.reloadData()
}
private func updateViewWithCNContact() {
cnAddressBook.requestAccess(for: .contacts) { (granted, error) in
if granted {
let predicate: NSPredicate = CNContact.predicateForContacts(withIdentifiers: [(self.contact!.cnContactId)])
let cnContactKeys = [CNContactFamilyNameKey, CNContactGivenNameKey, CNContactMiddleNameKey, CNContactOrganizationNameKey, CNContactPhoneNumbersKey, CNContactEmailAddressesKey, CNContactImageDataKey, CNContactImageDataAvailableKey, CNContactThumbnailImageDataKey, CNContactFormatter.descriptorForRequiredKeys(for: .fullName), CNContactViewController.descriptorForRequiredKeys()] as [Any]
do {
let contacts = try self.cnAddressBook.unifiedContacts(matching: predicate, keysToFetch: cnContactKeys as! [CNKeyDescriptor])
if contacts.count > 0 {
self.cnContact = contacts.first
self.phoneCallNumbers()
DispatchQueue.main.async {
self.companyNameLabel.text = self.cnContact?.organizationName
self.tableView.reloadData()
}
}
}
catch let err{
DDLogNotice("Can't get CNContact form addressbook \(err.localizedDescription)")
}
}
}
}
private func phoneCallNumbers() {
if cnContact != nil {
callNumbers = nil
callNumbers = [String]()
for phone: CNLabeledValue in cnContact!.phoneNumbers {
let number = phone.value.stringValue
if callNumbers!.contains(number) {
continue
}
callNumbers?.append(number)
}
}
}
private func isExportConversationEnabled() {
let mdmSetup = MDMSetup.init(setup: false)
let entityManager = EntityManager.init()
conversation = entityManager.entityFetcher.conversation(for: contact)
canExportConversation = conversation != nil && !mdmSetup!.disableExport()
}
@objc private func tappedImage() {
if contact != nil {
var image: UIImage?
if (contact!.contactImage != nil && UserSettings.shared().showProfilePictures) {
image = UIImage.init(data: contact!.contactImage.data)
}
else if contact!.imageData != nil {
image = UIImage.init(data: contact!.imageData)
}
if image != nil {
guard let imageController = FullscreenImageViewController.init(for: image) else { return }
if UIDevice.current.userInterfaceIdiom == .pad {
let nav = ModalNavigationController.init(rootViewController: imageController)
nav.showDoneButton = true
nav.showFullScreenOnIPad = true
present(nav, animated: true, completion: nil)
} else {
navigationController?.pushViewController(imageController, animated: true)
}
} else {
tappedHeaderView()
}
}
}
@objc private func tappedHeaderView() {
if contact != nil {
if cnContact != nil {
let personVC = CNContactViewController.init(for: cnContact!)
personVC.allowsActions = true
personVC.allowsEditing = true
cnContactViewShowing = true
if tabBarController?.tabBar.isHidden == false {
didHideTabBar = true
tabBarController?.tabBar.isHidden = true
}
let statusNavigationBar = navigationController?.navigationBar as! StatusNavigationBar
statusNavigationBar.hideStatusView()
navigationController?.navigationBar.barStyle = .default
navigationController?.pushViewController(personVC, animated: true)
} else {
showEditContactVC()
}
}
}
private func showEditContactVC() {
let editVC = storyboard!.instantiateViewController(withIdentifier: "EditContactViewController") as! EditContactViewController
editVC.contact = contact
navigationController?.pushViewController(editVC, animated: true)
}
private func conversationAction(sender: Any?) {
let title = String(format: BundleUtil.localizedString(forKey: "include_media_title"), kExportConversationMediaSizeLimit)
let actionSheet = UIAlertController(title: title, message: nil, preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: BundleUtil.localizedString(forKey: "include_media"), style: .default, handler: { (action) in
let em = EntityManager()
let exporter = ConversationExporter(viewController: self, contact: self.contact!, entityManager: em, withMedia: true)
exporter.exportConversation()
}))
actionSheet.addAction(UIAlertAction(title: BundleUtil.localizedString(forKey: "without_media"), style: .default, handler: { (action) in
let em = EntityManager()
let exporter = ConversationExporter(viewController: self, contact: self.contact!, entityManager: em, withMedia: false)
exporter.exportConversation()
}))
actionSheet.addAction(UIAlertAction(title: BundleUtil.localizedString(forKey: "cancel"), style: .cancel, handler: { (action) in
self.tableView.deselectRow(at: IndexPath.init(row: 1, section: 1), animated: true)
}))
if sender is UIView {
let senderView = sender as! UIView
actionSheet.popoverPresentationController?.sourceRect = senderView.frame
actionSheet.popoverPresentationController?.sourceView = view
}
AppDelegate.shared()?.currentTopViewController()?.present(actionSheet, animated: true, completion: nil)
if let selectedRow = self.tableView!.indexPathForSelectedRow {
self.tableView.deselectRow(at: selectedRow, animated: true)
}
}
private func startVoipCall(_ startWithVideo: Bool = false) {
if let selectedRow = self.tableView!.indexPathForSelectedRow {
self.tableView.deselectRow(at: selectedRow, animated: true)
}
if ServerConnector.shared().connectionState == ConnectionStateLoggedIn {
let action = VoIPCallUserAction.init(action: startWithVideo ? .callWithVideo : .call, contact:contact!, callId: nil, completion: nil)
VoIPCallStateManager.shared.processUserAction(action)
} else {
let title = BundleUtil.localizedString(forKey: "cannot_connect_title")
let message = BundleUtil.localizedString(forKey: "cannot_connect_message")
UIAlertTemplate.showAlert(owner: self, title: title, message: message) { (action) in
self.extensionContext?.completeRequest(returningItems: [Any](), completionHandler: nil)
}
}
}
private func makeTelUrlForPhone(_ phoneNumber: String) -> URL {
let urlString = "tel:\(phoneNumber.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlHostAllowed) ?? "")"
return URL.init(string: urlString)!
}
private func linkNewContact(view: UIView) {
if cnContact != nil {
let actionSheet = UIAlertController.init(title: nil, message: nil, preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction.init(title: BundleUtil.localizedString(forKey: "unlink_contact"), style: .destructive, handler: { (action) in
ContactStore.shared()?.unlinkContact(self.contact)
self.updateView()
}))
actionSheet.addAction(UIAlertAction.init(title: BundleUtil.localizedString(forKey: "choose_new_contact"), style: .default, handler: { (action) in
self.linkNewContactCheckAuthorization()
}))
actionSheet.addAction(UIAlertAction.init(title: BundleUtil.localizedString(forKey: "cancel"), style: .cancel, handler: { (action) in
if let selectedRow = self.tableView!.indexPathForSelectedRow {
self.tableView.deselectRow(at: selectedRow, animated: true)
}
}))
actionSheet.popoverPresentationController?.sourceRect = view.frame
actionSheet.popoverPresentationController?.sourceView = view
present(actionSheet, animated: true, completion: nil)
} else {
linkNewContactCheckAuthorization()
}
}
private func linkNewContactCheckAuthorization() {
if CNContactStore.authorizationStatus(for: .contacts) != .authorized {
cnAddressBook.requestAccess(for: .contacts) { (granted, error) in
if granted {
DispatchQueue.main.async {
self.linkNewContactPick()
}
} else {
DispatchQueue.main.async {
let accessAlert = UIAlertController.init(title: BundleUtil.localizedString(forKey: "no_contacts_permission_title"), message: BundleUtil.localizedString(forKey: "no_contacts_permission_message"), preferredStyle: .alert)
if self.contact!.cnContactId != nil {
accessAlert.addAction(UIAlertAction.init(title: BundleUtil.localizedString(forKey: "unlink_contact"), style: .default, handler: { (action) in
ContactStore.shared()?.unlinkContact(self.contact!)
self.updateView()
}))
}
accessAlert.addAction(UIAlertAction.init(title: BundleUtil.localizedString(forKey: "ok"), style: .default, handler: nil))
self.present(accessAlert, animated: true, completion: nil)
if let selectedRow = self.tableView!.indexPathForSelectedRow {
self.tableView.deselectRow(at: selectedRow, animated: true)
}
}
}
}
} else {
linkNewContactPick()
}
}
private func linkNewContactPick() {
let picker = CNContactPickerViewController.init()
picker.delegate = self
picker.modalPresentationStyle = .formSheet
present(picker, animated: true, completion: nil)
}
private func shouldHideCell(_ indexPath: IndexPath) -> Bool{
switch indexPath.section {
case 0:
switch indexPath.row {
case 2:
if contact?.publicNickname == nil {
return true
} else {
if contact?.publicNickname.count == 0 || contact?.publicNickname == contact?.identity {
return true
}
}
break
case 3:
if contact!.isGatewayId() {
return true
}
break
default:
break
}
break
case 1:
switch indexPath.row {
case 1:
if !UserSettings.shared().enableThreemaCall || is64Bit != 1 {
return true
}
break
case 2:
if canExportConversation == false {
return true
}
break
case 3:
if ScanIdentityController.canScan() == false {
return true
}
break
case 4:
if contact!.isGatewayId() || contact!.isEchoEcho() || UserSettings.shared().sendProfilePicture == SendProfilePictureNone || (UserSettings.shared().sendProfilePicture == SendProfilePictureContacts && !UserSettings.shared().profilePictureContactList.contains(where: {($0 as! String) == contact!.identity})) {
return true
}
break
default:
break
}
break
default:
break
}
return false
}
private func showWorkInfo(_ autoDismiss: Bool = true) {
threemaTypeIcon.isHighlighted = false
threemaTypeIcon.isSelected = false
if showcase == nil {
showcase = MaterialShowcase()
showcase!.setTargetView(button: threemaTypeIcon)
if LicenseStore.requiresLicenseKey() == false {
showcase!.primaryText = BundleUtil.localizedString(forKey: "contact_threema_work_title")
showcase!.secondaryText = BundleUtil.localizedString(forKey: "contact_threema_work_info")
showcase!.backgroundPromptColor = Colors.workBlue()
} else {
showcase!.primaryText = BundleUtil.localizedString(forKey: "contact_threema_title")
showcase!.secondaryText = BundleUtil.localizedString(forKey: "contact_threema_info")
showcase!.backgroundPromptColor = Colors.green()
}
showcase!.backgroundPromptColorAlpha = 0.93
showcase!.primaryTextSize = 24.0
showcase!.secondaryTextSize = 20.0
showcase!.primaryTextColor = Colors.white()
showcase!.secondaryTextColor = Colors.white()
showcase!.delegate = self
}
showcase!.show(completion: nil)
if autoDismiss == true {
DispatchQueue.main.asyncAfter(deadline: .now() + 6, execute: {
if self.showcase != nil {
self.showcase!.completeShowcase()
}
})
}
}
// MARK: IBAction
@IBAction func shareButtonTapped(sender: UIButton) {
let contactShareLink = String.init(format: "%@%@", THREEMA_ID_SHARE_LINK, contact!.identity)
let contactShareText = "\(contact!.displayName!): \(contactShareLink)"
let activityViewController = UIActivityViewController.init(activityItems: [contactShareText], applicationActivities: nil)
if UIDevice.current.userInterfaceIdiom == .pad {
activityViewController.popoverPresentationController?.sourceRect = sender.frame
activityViewController.popoverPresentationController?.sourceView = view
}
present(activityViewController, animated: true, completion: nil)
}
@IBAction func scanQrCodeAction(sender: UIButton) {
scanIdentityAction()
}
@IBAction func workInfoButtonTapped(sender: UIButton) {
showWorkInfo(false)
}
}
extension ContactDetailsViewController {
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
if !(contact!.isGatewayId()) && !contact!.isEchoEcho() && UserSettings.shared()?.sendProfilePicture == SendProfilePictureContacts {
return 4
}
return 3
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return 6
case 1:
return 5
case 2:
if !(contact!.isGatewayId()) && !contact!.isEchoEcho() && UserSettings.shared()?.sendProfilePicture == SendProfilePictureContacts {
return 1
} else {
return 2
}
case 3:
return 2
default:
return 0
}
}
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
if section == 2 && !contact!.isGatewayId() && !contact!.isEchoEcho() && UserSettings.shared().sendProfilePicture == SendProfilePictureContacts {
if UserSettings.shared().profilePictureContactList.contains(where: {($0 as! String) == contact!.identity}) {
return BundleUtil.localizedString(forKey: "contact_added_to_profilepicture_list")
} else {
return BundleUtil.localizedString(forKey: "contact_removed_from_profilepicture_list")
}
}
return nil
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return shouldHideCell(indexPath) == true ? 0 : UITableView.automaticDimension
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
super.tableView(tableView, willDisplay: cell, forRowAt: indexPath)
cell.isHidden = shouldHideCell(indexPath)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.section {
case 0:
switch indexPath.row {
case 0:
let identityCell = tableView.dequeueReusableCell(withIdentifier: "IdentityCell")
identityCell!.detailTextLabel?.text = contact?.identity
identityCell!.detailTextLabel!.isAccessibilityElement = false
let shareButton = identityCell?.accessoryView as! UIButton
let shareImage: UIImage
if #available(iOS 13.0, *) {
shareImage = shareButton.imageView!.image!.withTintColor(Colors.main())
} else {
shareImage = shareButton.imageView!.image!.withTint(Colors.main())
}
shareButton.setImage(shareImage, for: .normal)
return identityCell!
case 1:
let vlc = tableView.dequeueReusableCell(withIdentifier: "VerificationLevelCell") as! VerificationLevelCell
vlc.contact = contact
vlc.accessibilityTraits = .button
return vlc
case 2:
let publicNicknameCell = tableView.dequeueReusableCell(withIdentifier: "PublicNicknameCell")
publicNicknameCell!.detailTextLabel?.text = contact?.publicNickname
return publicNicknameCell!
case 3:
let lcc = tableView.dequeueReusableCell(withIdentifier: "LinkedContactCell") as! LinkedContactCell
lcc.accessibilityTraits = .button
if cnContact != nil {
lcc.displayNameLabel.text = CNContactFormatter.string(from: cnContact!, style: .fullName)
if lcc.displayNameLabel.text == nil || lcc.displayNameLabel.text?.count == 0 {
if cnContact!.emailAddresses.count > 0 {
let first:CNLabeledValue = cnContact!.emailAddresses.first!
lcc.displayNameLabel.text = first.value as String
}
}
} else {
lcc.displayNameLabel.text = BundleUtil.localizedString(forKey: "(none)")
}
return lcc
case 4:
let groupMembershipCell = tableView.dequeueReusableCell(withIdentifier: "GroupMembershipCell")
groupMembershipCell?.textLabel?.text = BundleUtil.localizedString(forKey: "member_in_groups")
groupMembershipCell?.detailTextLabel?.text = String.init(format: "%lu", contact?.groupConversations.count ?? 0)
groupMembershipCell?.accessibilityTraits = .button
return groupMembershipCell!
case 5:
let kfc = tableView.dequeueReusableCell(withIdentifier: "KeyFingerprintCell") as! KeyFingerprintCell
kfc.fingerprintValueLabel.text = CryptoUtils.fingerprint(forPublicKey: contact?.publicKey)
return kfc
default:
break
}
case 1:
var cellIdentifier = ""
switch indexPath.row {
case 0:
cellIdentifier = "SendMessageCell"
break
case 1:
cellIdentifier = "ThreemaCallCell"
break
case 2:
cellIdentifier = "ExportConversationCell"
break
case 3:
cellIdentifier = "ScanIDCell"
break
case 4:
cellIdentifier = "SendProfilePictureCell"
break
default:
cellIdentifier = "SendMessageCell"
}
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)
cell?.accessibilityTraits = .button
return cell!
case 2:
if !(contact!.isGatewayId()) && !contact!.isEchoEcho() && UserSettings.shared()?.sendProfilePicture == SendProfilePictureContacts {
let profilePictureRecipientCell = tableView.dequeueReusableCell(withIdentifier: "ProfilePictureRecipientCell") as! ProfilePictureRecipientCell
profilePictureRecipientCell.identity = contact?.identity
profilePictureRecipientCell.delegate = self
return profilePictureRecipientCell
} else {
switch indexPath.row {
case 0:
let pushSettingCell = tableView.dequeueReusableCell(withIdentifier: "PushSettingCell")
pushSettingCell?.textLabel?.text = BundleUtil.localizedString(forKey: "pushSetting_title")
return pushSettingCell!
case 1:
let bcc = tableView.dequeueReusableCell(withIdentifier: "BlockCell") as! BlockContactCell
bcc.identity = contact?.identity
return bcc
default:
break
}
}
break
case 3:
switch indexPath.row {
case 0:
let pushSettingCell = tableView.dequeueReusableCell(withIdentifier: "PushSettingCell")
pushSettingCell?.textLabel?.text = BundleUtil.localizedString(forKey: "pushSetting_title")
return pushSettingCell!
case 1:
let bcc = tableView.dequeueReusableCell(withIdentifier: "BlockCell") as! BlockContactCell
bcc.identity = contact?.identity
return bcc
default:
break
}
break
default:
break
}
return UITableViewCell()
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
switch indexPath.section {
case 0:
if indexPath.row == 3 {
linkNewContact(view: cell!)
}
else if indexPath.row == 4 {
let vc = storyboard!.instantiateViewController(withIdentifier: "contactGroupMembershipViewController") as! ContactGroupMembershipViewController
vc.groupContact = contact
navigationController?.pushViewController(vc, animated: true)
}
break
case 1:
if cell?.reuseIdentifier == "SendMessageCell" {
sendMessageAction()
}
else if cell?.reuseIdentifier == "ThreemaCallCell" {
startThreemaCallAction()
}
else if cell?.reuseIdentifier == "ExportConversationCell" {
tableView.deselectRow(at: indexPath, animated: true)
conversationAction(sender: cell)
}
else if cell?.reuseIdentifier == "ScanIDCell" {
scanIdentityAction()
}
else if cell?.reuseIdentifier == "SendProfilePictureCell" {
startProfilePictureAction()
}
break
default:
break
}
}
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
if indexPath.section == 0 && indexPath.row == 1 {
performSegue(withIdentifier: "VerificationSegue", sender: nil)
}
}
}
extension ContactDetailsViewController: ProfilePictureRecipientCellDelegate {
func valueChanged(_ cell: ProfilePictureRecipientCell) {
let indexSet: IndexSet = [1]
tableView.beginUpdates()
tableView.reloadSections(indexSet, with: .automatic)
tableView.endUpdates()
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
extension ContactDetailsViewController: CNContactPickerDelegate {
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
let statusNavigationBar = navigationController?.navigationBar as! StatusNavigationBar
statusNavigationBar.showOrHideStatusView()
ContactStore.shared()?.linkContact(self.contact, toCnContactId: contact.identifier)
dismiss(animated: true, completion: nil)
updateView()
}
func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) {
dismiss(animated: true, completion: nil)
}
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
if let selectedRow = self.tableView!.indexPathForSelectedRow {
self.tableView.deselectRow(at: selectedRow, animated: true)
}
let statusNavigationBar = navigationController?.navigationBar as! StatusNavigationBar
statusNavigationBar.showOrHideStatusView()
dismiss(animated: true, completion: nil)
}
}
extension ContactDetailsViewController: MaterialShowcaseDelegate {
func showCaseWillDismiss(showcase: MaterialShowcase, didTapTarget: Bool) {
UserSettings.shared()?.workInfoShown = true
}
func showCaseDidDismiss(showcase: MaterialShowcase, didTapTarget: Bool) {
}
}