Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/SignalUI/RecipientPickers/ContactCell.swift
1 views
//
// Copyright 2016 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

public import Contacts
public import SignalServiceKit
import UIKit

public class ContactCell: UITableViewCell, ReusableTableViewCell {
    public static let reuseIdentifier = "ContactCell"

    private static let avatarDiameter: CGFloat = 36

    private let contactImageView = AvatarImageView()
    private lazy var textStackView: UIStackView = {
        let stackView = UIStackView(arrangedSubviews: [titleLabel])
        stackView.axis = .vertical
        return stackView
    }()

    private let titleLabel: UILabel = {
        let label = UILabel()
        label.font = .dynamicTypeBody
        label.adjustsFontForContentSizeCategory = true
        return label
    }()

    private let subtitleLabel: UILabel = {
        let label = UILabel()
        label.font = .dynamicTypeSubheadline
        label.adjustsFontForContentSizeCategory = true
        return label
    }()

    private var showsWhenSelected: Bool = false

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        selectionStyle = UITableViewCell.SelectionStyle.none

        contactImageView.autoSetDimensions(to: CGSize(square: CGFloat(ContactCell.avatarDiameter)))

        let contentColumns: UIStackView = UIStackView(arrangedSubviews: [contactImageView, textStackView])
        contentColumns.axis = .horizontal
        contentColumns.spacing = 12
        contentColumns.alignment = .center
        contentView.addSubview(contentColumns)
        contentColumns.autoPinWidthToSuperviewMargins()
        contentColumns.autoPinHeightToSuperview(withMargin: 7)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override public func prepareForReuse() {
        accessoryType = .none
        subtitleLabel.removeFromSuperview()
    }

    override public func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        if showsWhenSelected {
            accessoryType = selected ? .checkmark : .none
        }
    }

    public func configure(systemContact: SystemContact, sortOrder: CNContactSortOrder, subtitleType: SubtitleCellValue, showsWhenSelected: Bool) {
        self.showsWhenSelected = showsWhenSelected

        let cnContact = SSKEnvironment.shared.contactManagerRef.cnContact(withId: systemContact.cnContactId)

        if let cnContact {
            titleLabel.attributedText = cnContact.formattedFullName(sortOrder: sortOrder, font: titleLabel.font)
        } else {
            titleLabel.text = systemContact.fullName
        }

        updateSubtitle(subtitleType: subtitleType, systemContact: systemContact)

        var contactImage: UIImage?
        if let cnContact {
            if let avatarImage = SSKEnvironment.shared.contactManagerRef.avatarImage(for: cnContact.identifier) {
                contactImage = avatarImage
            } else if cnContact.imageDataAvailable, let contactImageData = cnContact.imageData {
                contactImage = UIImage(data: contactImageData)
            }
        }
        if contactImage == nil {
            var nameComponents = PersonNameComponents()
            nameComponents.givenName = systemContact.firstName
            nameComponents.familyName = systemContact.lastName

            let avatar = SSKEnvironment.shared.databaseStorageRef.read { transaction in
                SSKEnvironment.shared.avatarBuilderRef.defaultAvatarImage(
                    personNameComponents: nameComponents,
                    diameterPoints: UInt(ContactCell.avatarDiameter),
                    transaction: transaction,
                )
            }
            contactImage = avatar
        }
        contactImageView.image = contactImage
    }

    private func updateSubtitle(subtitleType: SubtitleCellValue, systemContact: SystemContact) {
        switch subtitleType {
        case .none:
            owsAssertBeta(subtitleLabel.superview == nil)
        case .phoneNumber:
            textStackView.addArrangedSubview(subtitleLabel)

            if let firstPhoneNumber = systemContact.phoneNumbers.first {
                subtitleLabel.text = firstPhoneNumber.value
            } else {
                subtitleLabel.text = OWSLocalizedString("CONTACT_PICKER_NO_PHONE_NUMBERS_AVAILABLE", comment: "table cell subtitle when contact card has no known phone number")
            }
        case .email:
            textStackView.addArrangedSubview(subtitleLabel)

            if let firstEmailAddress = systemContact.emailAddresses.first {
                subtitleLabel.text = firstEmailAddress
            } else {
                subtitleLabel.text = OWSLocalizedString("CONTACT_PICKER_NO_EMAILS_AVAILABLE", comment: "table cell subtitle when contact card has no email")
            }
        }
    }

    override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.traitCollectionDidChange(previousTraitCollection)

        titleLabel.textColor = Theme.primaryTextColor
        subtitleLabel.textColor = Theme.secondaryTextAndIconColor

        OWSTableItem.configureCell(self)
    }
}

private extension CNContact {
    /**
     * Bold the sorting portion of the name. e.g. if we sort by family name, bold the family name.
     */
    func formattedFullName(sortOrder: CNContactSortOrder, font: UIFont) -> NSAttributedString? {
        let keyToHighlight = sortOrder == .familyName ? CNContactFamilyNameKey : CNContactGivenNameKey

        let boldDescriptor = font.fontDescriptor.withSymbolicTraits(.traitBold)
        let boldAttributes = [
            NSAttributedString.Key.font: UIFont(descriptor: boldDescriptor!, size: 0),
        ]

        if let attributedName = CNContactFormatter.attributedString(from: self, style: .fullName, defaultAttributes: nil) {
            let highlightedName = attributedName.mutableCopy() as! NSMutableAttributedString
            highlightedName.enumerateAttributes(in: highlightedName.entireRange, options: [], using: { attrs, range, _ in
                if let property = attrs[NSAttributedString.Key(rawValue: CNContactPropertyAttribute)] as? String, property == keyToHighlight {
                    highlightedName.addAttributes(boldAttributes, range: range)
                }
            })
            return highlightedName
        }

        if let emailAddress = self.emailAddresses.first?.value {
            return NSAttributedString(string: emailAddress as String, attributes: boldAttributes)
        }

        if let phoneNumber = self.phoneNumbers.first?.value.stringValue {
            return NSAttributedString(string: phoneNumber, attributes: boldAttributes)
        }

        return nil
    }
}