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.
		
		
		
		
		
			
		
			
				
	
	
		
			211 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			211 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Swift
		
	
 | 
						|
@objc(LKConversationTitleView)
 | 
						|
final class ConversationTitleView : UIView {
 | 
						|
    private let thread: TSThread
 | 
						|
    private var currentStatus: Status? { didSet { updateSubtitleForCurrentStatus() } }
 | 
						|
    private var handledMessageTimestamps: Set<NSNumber> = []
 | 
						|
    
 | 
						|
    // MARK: Types
 | 
						|
    private enum Status : Int {
 | 
						|
        case calculatingPoW = 1
 | 
						|
        case routing = 2
 | 
						|
        case messageSending = 3
 | 
						|
        case messageSent = 4
 | 
						|
        case messageFailed = 5
 | 
						|
    }
 | 
						|
    
 | 
						|
    // MARK: Components
 | 
						|
    private lazy var profilePictureView: ProfilePictureView = {
 | 
						|
        let result = ProfilePictureView()
 | 
						|
        let size: CGFloat = 40
 | 
						|
        result.set(.width, to: size)
 | 
						|
        result.set(.height, to: size)
 | 
						|
        result.size = size
 | 
						|
        return result
 | 
						|
    }()
 | 
						|
 | 
						|
    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: 13)
 | 
						|
        result.lineBreakMode = .byTruncatingTail
 | 
						|
        return result
 | 
						|
    }()
 | 
						|
    
 | 
						|
    // MARK: Lifecycle
 | 
						|
    @objc init(thread: TSThread) {
 | 
						|
        self.thread = thread
 | 
						|
        super.init(frame: CGRect.zero)
 | 
						|
        setUpViewHierarchy()
 | 
						|
        updateTitle()
 | 
						|
        updateProfilePicture()
 | 
						|
        updateSubtitleForCurrentStatus()
 | 
						|
        let notificationCenter = NotificationCenter.default
 | 
						|
        notificationCenter.addObserver(self, selector: #selector(handleProfileChangedNotification(_:)), name: NSNotification.Name(rawValue: kNSNotificationName_OtherUsersProfileDidChange), object: nil)
 | 
						|
        notificationCenter.addObserver(self, selector: #selector(handleCalculatingMessagePoWNotification(_:)), name: .calculatingMessagePoW, object: nil)
 | 
						|
        notificationCenter.addObserver(self, selector: #selector(handleEncryptingMessageNotification(_:)), name: .encryptingMessage, object: nil)
 | 
						|
        notificationCenter.addObserver(self, selector: #selector(handleMessageSendingNotification(_:)), name: .messageSending, object: nil)
 | 
						|
        notificationCenter.addObserver(self, selector: #selector(handleMessageSentNotification(_:)), name: .messageSent, object: nil)
 | 
						|
        notificationCenter.addObserver(self, selector: #selector(handleMessageSendingFailedNotification(_:)), name: .messageSendingFailed, object: nil)
 | 
						|
    }
 | 
						|
    
 | 
						|
    override init(frame: CGRect) {
 | 
						|
        preconditionFailure("Use init(thread:) instead.")
 | 
						|
    }
 | 
						|
    
 | 
						|
    required init?(coder: NSCoder) {
 | 
						|
        preconditionFailure("Use init(thread:) instead.")
 | 
						|
    }
 | 
						|
    
 | 
						|
    private func setUpViewHierarchy() {
 | 
						|
        let labelStackView = UIStackView(arrangedSubviews: [ titleLabel, subtitleLabel ])
 | 
						|
        labelStackView.axis = .vertical
 | 
						|
        labelStackView.alignment = .leading
 | 
						|
        let stackView = UIStackView(arrangedSubviews: [ profilePictureView, labelStackView ])
 | 
						|
        stackView.axis = .horizontal
 | 
						|
        stackView.alignment = .center
 | 
						|
        stackView.spacing = 12
 | 
						|
        addSubview(stackView)
 | 
						|
        stackView.pin(to: self)
 | 
						|
    }
 | 
						|
    
 | 
						|
    deinit {
 | 
						|
        NotificationCenter.default.removeObserver(self)
 | 
						|
    }
 | 
						|
    
 | 
						|
    // MARK: Updating
 | 
						|
    private func updateTitle() {
 | 
						|
        let title: String
 | 
						|
        if thread.isGroupThread() {
 | 
						|
            if thread.name().isEmpty {
 | 
						|
                title = GroupDisplayNameUtilities.getDefaultDisplayName(for: thread as! TSGroupThread)
 | 
						|
            } else {
 | 
						|
                title = thread.name()
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            if thread.isNoteToSelf() {
 | 
						|
                title = NSLocalizedString("Note to Self", comment: "")
 | 
						|
            } else {
 | 
						|
                let hexEncodedPublicKey = thread.contactIdentifier()!
 | 
						|
                title = UserDisplayNameUtilities.getPrivateChatDisplayName(for: hexEncodedPublicKey) ?? hexEncodedPublicKey
 | 
						|
            }
 | 
						|
        }
 | 
						|
        titleLabel.text = title
 | 
						|
    }
 | 
						|
 | 
						|
    private func updateProfilePicture() {
 | 
						|
        profilePictureView.update(for: thread)
 | 
						|
    }
 | 
						|
    
 | 
						|
    @objc private func handleProfileChangedNotification(_ notification: Notification) {
 | 
						|
        guard let hexEncodedPublicKey = notification.userInfo?[kNSNotificationKey_ProfileRecipientId] as? String, let thread = self.thread as? TSContactThread,
 | 
						|
            hexEncodedPublicKey == thread.contactIdentifier() else { return }
 | 
						|
        updateTitle()
 | 
						|
        updateProfilePicture()
 | 
						|
    }
 | 
						|
    
 | 
						|
    @objc private func handleCalculatingMessagePoWNotification(_ notification: Notification) {
 | 
						|
        guard let timestamp = notification.object as? NSNumber else { return }
 | 
						|
        setStatusIfNeeded(to: .calculatingPoW, forMessageWithTimestamp: timestamp)
 | 
						|
    }
 | 
						|
    
 | 
						|
    @objc private func handleEncryptingMessageNotification(_ notification: Notification) {
 | 
						|
        guard let timestamp = notification.object as? NSNumber else { return }
 | 
						|
        setStatusIfNeeded(to: .routing, forMessageWithTimestamp: timestamp)
 | 
						|
    }
 | 
						|
    
 | 
						|
    @objc private func handleMessageSendingNotification(_ notification: Notification) {
 | 
						|
        guard let timestamp = notification.object as? NSNumber else { return }
 | 
						|
        setStatusIfNeeded(to: .messageSending, forMessageWithTimestamp: timestamp)
 | 
						|
    }
 | 
						|
    
 | 
						|
    @objc private func handleMessageSentNotification(_ notification: Notification) {
 | 
						|
        guard let timestamp = notification.object as? NSNumber else { return }
 | 
						|
        setStatusIfNeeded(to: .messageSent, forMessageWithTimestamp: timestamp)
 | 
						|
        handledMessageTimestamps.insert(timestamp)
 | 
						|
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
 | 
						|
            self?.clearStatusIfNeededForMessageWithTimestamp(timestamp)
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    @objc private func handleMessageSendingFailedNotification(_ notification: Notification) {
 | 
						|
        guard let timestamp = notification.object as? NSNumber else { return }
 | 
						|
        clearStatusIfNeededForMessageWithTimestamp(timestamp)
 | 
						|
    }
 | 
						|
    
 | 
						|
    private func setStatusIfNeeded(to status: Status, forMessageWithTimestamp timestamp: NSNumber) {
 | 
						|
        guard !handledMessageTimestamps.contains(timestamp) else { return }
 | 
						|
        var uncheckedTargetInteraction: TSInteraction? = nil
 | 
						|
        thread.enumerateInteractions { interaction in
 | 
						|
            guard interaction.timestamp == timestamp.uint64Value else { return }
 | 
						|
            uncheckedTargetInteraction = interaction
 | 
						|
        }
 | 
						|
        guard let targetInteraction = uncheckedTargetInteraction, targetInteraction.interactionType() == .outgoingMessage,
 | 
						|
            status.rawValue > (currentStatus?.rawValue ?? 0) else { return }
 | 
						|
        currentStatus = status
 | 
						|
    }
 | 
						|
    
 | 
						|
    private func clearStatusIfNeededForMessageWithTimestamp(_ timestamp: NSNumber) {
 | 
						|
        var uncheckedTargetInteraction: TSInteraction? = nil
 | 
						|
        OWSPrimaryStorage.shared().dbReadConnection.read { transaction in
 | 
						|
            guard let interactionsByThread = transaction.ext(TSMessageDatabaseViewExtensionName) as? YapDatabaseViewTransaction else { return }
 | 
						|
            interactionsByThread.enumerateKeysAndObjects(inGroup: self.thread.uniqueId!) { _, _, object, _, _ in
 | 
						|
                guard let interaction = object as? TSInteraction, interaction.timestamp == timestamp.uint64Value else { return }
 | 
						|
                uncheckedTargetInteraction = interaction
 | 
						|
            }
 | 
						|
        }
 | 
						|
        guard let targetInteraction = uncheckedTargetInteraction, targetInteraction.interactionType() == .outgoingMessage else { return }
 | 
						|
        self.currentStatus = nil
 | 
						|
    }
 | 
						|
    
 | 
						|
    @objc func updateSubtitleForCurrentStatus() {
 | 
						|
        DispatchQueue.main.async { [weak self] in
 | 
						|
            guard let self = self else { return }
 | 
						|
            self.subtitleLabel.isHidden = false
 | 
						|
            let subtitle = NSMutableAttributedString()
 | 
						|
            if let muteEndDate = self.thread.mutedUntilDate, self.thread.isMuted {
 | 
						|
                subtitle.append(NSAttributedString(string: "\u{e067}  ", attributes: [ .font : UIFont.ows_elegantIconsFont(10), .foregroundColor : Colors.unimportant ]))
 | 
						|
                let dateFormatter = DateFormatter()
 | 
						|
                dateFormatter.locale = Locale.current
 | 
						|
                dateFormatter.timeStyle = .medium
 | 
						|
                dateFormatter.dateStyle = .medium
 | 
						|
                subtitle.append(NSAttributedString(string: "Muted until " + dateFormatter.string(from: muteEndDate)))
 | 
						|
            } else if let thread = self.thread as? TSGroupThread {
 | 
						|
                var userCount: Int?
 | 
						|
                if thread.groupModel.groupType == .closedGroup {
 | 
						|
                    userCount = GroupUtilities.getClosedGroupMemberCount(thread)
 | 
						|
                } else if thread.groupModel.groupType == .openGroup {
 | 
						|
                    if let openGroup = Storage.shared.getOpenGroup(for: self.thread.uniqueId!) {
 | 
						|
                        userCount = Storage.shared.getUserCount(forOpenGroupWithID: openGroup.id)
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                if let userCount = userCount {
 | 
						|
                    subtitle.append(NSAttributedString(string: "\(userCount) members"))
 | 
						|
                } else if let hexEncodedPublicKey = (self.thread as? TSContactThread)?.contactIdentifier(), ECKeyPair.isValidHexEncodedPublicKey(candidate: hexEncodedPublicKey) {
 | 
						|
                    subtitle.append(NSAttributedString(string: hexEncodedPublicKey))
 | 
						|
                } else {
 | 
						|
                    self.subtitleLabel.isHidden = true
 | 
						|
                }
 | 
						|
            }
 | 
						|
            else {
 | 
						|
                self.subtitleLabel.isHidden = true
 | 
						|
            }
 | 
						|
            self.subtitleLabel.attributedText = subtitle
 | 
						|
            self.titleLabel.font = .boldSystemFont(ofSize: self.subtitleLabel.isHidden ? Values.veryLargeFontSize : Values.mediumFontSize)
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    // MARK: Layout
 | 
						|
    public override var intrinsicContentSize: CGSize {
 | 
						|
        return UIView.layoutFittingExpandedSize
 | 
						|
    }
 | 
						|
}
 |