Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  Contact.swift   Sprache: JAVA

 

import CocoaLumberjackSwift
import CoreData
import Foundation
import ThreemaEssentials
import ThreemaMacros
import ThreemaProtocols
import UIKit

/// Business representation of a Threema Contact
public final class Contact: NSObject {
    
    // These strings are used as static properties for performance reasons
    private static let inactiveString = #localize("inactive")
    private static let invalidString = #localize("invalid")
    private static let unknownString = #localize("(unknown)")
    private static let workAdjustedVerificationLevelString0 = #localize("level0_title")
    private static let workAdjustedVerificationLevelString1 = #localize("level1_title")
    private static let workAdjustedVerificationLevelString2 = #localize("level2_title")
    private static let workAdjustedVerificationLevelString3 = #localize("level3_title")
    private static let workAdjustedVerificationLevelString4 = #localize("level4_title")

    // MARK: - Observation
    
    // Tokens for entity subscription, will be removed when is deallocated
    private var subscriptionToken: EntityObserver.SubscriptionToken?

    /// This will be set to `true` when a contact is in the process to be deleted.
    ///
    /// This can be used to detect deletion in KVO-observers
    @objc public private(set) dynamic var willBeDeleted = false
    
    // Needed for KVO on `displayName`: https://stackoverflow.com/a/51108007
    @objc public class var keyPathsForValuesAffectingDisplayName: Set<String> {
        [
            #keyPath(firstName),
            #keyPath(lastName),
            #keyPath(publicNickname),
            #keyPath(state),
            #keyPath(objcIdentity),
        ]
    }
    
    @objc public private(set) dynamic var state: ContactEntity.ContactState = .active
    @objc public private(set) dynamic var firstName: String?
    @objc public private(set) dynamic var lastName: String?
    @objc public private(set) dynamic var csi: String?
    @objc public private(set) dynamic var jobTitle: String?
    @objc public private(set) dynamic var department: String?
    @objc public private(set) dynamic var publicNickname: String?
    @objc public private(set) dynamic var verificationLevel: ContactEntity.VerificationLevel = .unverified
    @objc public private(set) dynamic var featureMask: Int
    @objc public private(set) dynamic lazy var profilePicture: UIImage = resolveProfilePicture()
    @objc public private(set) dynamic var workAvailabilityStatus: WorkAvailabilityStatus?

    @objc public private(set) dynamic var displayName: String {
        get {
            resolveDisplayName()
        }
        set {
            // No-op
        }
    }
    
    @objc public private(set) dynamic var readReceipt: ContactEntity.ReadReceipt
    @objc public private(set) dynamic var typingIndicator: ContactEntity.TypingIndicator

    // MARK: - Public properties

    @available(*, deprecated, message: "Do not use anymore. Associate a contact with a Threema Identity instead.")
    public let objectID: NSManagedObjectID

    public let identity: ThreemaIdentity

    // This is not directly referenced, but used in `ContactStore.m:959`ff to filter contacts
    @available(*, deprecated, renamed: "identity", message: "Only use from Objective-C")
    @objc(identity) public let objcIdentity: String
    
    let publicKey: Data
    
    public var isActive: Bool {
        state == .active
    }
    
    // If true when acquaintance level is group or deleted
    @objc public private(set) dynamic var isHidden: Bool

    // This only means it's a verified contact from the admin (in the same work package)
    // To check if this contact is a work ID, use the work identities list in user settings
    // bad naming because of the history...
    public private(set) var isWorkContact: Bool

    public let showOtherTypeIcon: Bool
    
    /// This will return a attributed string from the displayName with invalid, inactive and blocked format
    public var attributedDisplayName: NSAttributedString {
        var attributedNameString = NSMutableAttributedString(string: displayName)
        
        // Check style for the title
        if state == .invalid {
            // Contact is invalid
            attributedNameString.addAttribute(
                .strikethroughStyle,
                value: 2,
                range: NSMakeRange(0, attributedNameString.length)
            )
            attributedNameString.addAttribute(
                .foregroundColor,
                value: UIColor.secondaryLabel,
                range: NSMakeRange(0, attributedNameString.length)
            )
        }
        else if state == .inactive {
            // Contact is inactive
            attributedNameString.addAttribute(
                .foregroundColor,
                value: UIColor.secondaryLabel,
                range: NSMakeRange(0, attributedNameString.length)
            )
        }
        else {
            attributedNameString.addAttribute(
                .foregroundColor,
                value: UIColor.label,
                range: NSMakeRange(0, attributedNameString.length)
            )
        }
        
        if UserSettings.shared().blacklist.contains(identity) {
            // Contact is blacklisted
            attributedNameString = NSMutableAttributedString(string: "�� " + attributedNameString.string)
        }
        
        return attributedNameString
    }
    
    /// Shorter version of `displayName` if available
    var shortDisplayName: String {
        // This is an "opt-in" feature
        guard !TargetManager.isBusinessApp else {
            return displayName
        }

        if let firstName, !firstName.isEmpty {
            return firstName
        }

        return displayName
    }
    
    public let createdAt: Date? // TODO: (IOS-6024) Currently optional. This should be changed for data backup
    
    public var hasGatewayID: Bool {
        identity.isGatewayID
    }
    
    public var isEchoEcho: Bool {
        identity.rawValue == "ECHOECHO"
    }
    
    public var forwardSecurityMode: ForwardSecurityMode {
        
        guard isForwardSecurityAvailable else {
            return .none
        }
        
        do {
            let businessInjector = BusinessInjector()
            guard let dhSession = try businessInjector.dhSessionStore.bestDHSession(
                myIdentity: MyIdentityStore.shared().identity,
                peerIdentity: identity.rawValue
            ) else {
                return .none
            }
            
            let state = try dhSession.state
            
            switch state {
            case .L20:
                return .twoDH
            case .RL44:
                return .fourDH
            case .R20:
                return .twoDH
            case .R24:
                return .twoDH
            }
        }
        catch {
            DDLogError("Could not get ForwardSecurityMode for contact with identity: \(identity).")
            return .none
        }
    }
    
    public private(set) var usesNonGeneratedProfilePicture = false
    
    public var supportsCalls: Bool {
        FeatureMask.check(contact: self, for: .o2OAudioCallSupport) &&
            !hasGatewayID &&
            !isEchoEcho
    }

    // MARK: - Private properties
    
    public var isForwardSecurityAvailable: Bool

    private lazy var idColor: UIColor = IDColor.forData(Data(identity.rawValue.utf8))

    /// Either first character of first and last name in order depending of the user setting, or first two characters of
    /// public
    /// nickname or of ID if no name is specified.
    private var initials: String {
        
        // If we have both a non empty first and last name
        if let firstName = firstName?.replacingOccurrences(of: " ", with: ""),
           let lastName = lastName?.replacingOccurrences(of: " ", with: ""),
           !firstName.isEmpty,
           !lastName.isEmpty {
            if UserSettings.shared().displayOrderFirstName {
                return String(firstName.prefix(1)) + String(lastName.prefix(1)).uppercased()
            }
            else {
                return String(lastName.prefix(1)) + String(firstName.prefix(1)).uppercased()
            }
        }
        
        // If we have a non empty first name we use the first two letters
        if let firstName = firstName?.replacingOccurrences(of: " ", with: ""),
           !firstName.isEmpty {
            return String(firstName.prefix(2)).uppercased()
        }
        
        // If we have a non empty first last we use the first two letters
        if let lastName = lastName?.replacingOccurrences(of: " ", with: ""),
           !lastName.isEmpty {
            return String(lastName.prefix(2)).uppercased()
        }
        
        // Public nickname
        if let publicNickname = publicNickname?.replacingOccurrences(of: " ", with: ""),
           !publicNickname.isEmpty {
            return String(publicNickname.prefix(2)).uppercased()
        }
        
        // ID
        return String(identity.rawValue.prefix(2)).uppercased()
    }
    
    /// Image data sent from threema. For KVO, observe `profilePicture` directly.
    private var threemaImageData: Data? {
        didSet {
            updateProfilePicture()
        }
    }
    
    /// Image data set from Contacts. For KVO, observe `profilePicture` directly.
    private var contactImageData: Data? {
        didSet {
            updateProfilePicture()
        }
    }
    
    // MARK: - Lifecycle
    
    /// Initialize Contact properties and subscribe ContactEntity on EntityObserver for updates.
    /// Note: Contact properties will be only refreshed if it's ContactEntity object already saved in Core Data.
    ///
    /// - Parameter contactEntity: Core Data object
    @objc public init(contactEntity: ContactEntity) {
        self.objectID = contactEntity.objectID
        self.identity = ThreemaIdentity(contactEntity.identity)
        self.objcIdentity = contactEntity.identity
        self.publicKey = contactEntity.publicKey
        self.publicNickname = contactEntity.publicNickname
        self.firstName = contactEntity.firstName
        self.lastName = contactEntity.lastName
        self.csi = contactEntity.csi
        self.jobTitle = contactEntity.jobTitle
        self.department = contactEntity.department
        self.verificationLevel = contactEntity.contactVerificationLevel
        self.createdAt = contactEntity.createdAt
        self.state = contactEntity.contactState
        self.isHidden = contactEntity.isHidden
        self.isWorkContact = contactEntity.isWorkContact
        self.showOtherTypeIcon = contactEntity.showOtherThreemaTypeIcon
        self.isForwardSecurityAvailable = contactEntity.isForwardSecurityAvailable
        self.featureMask = Int(truncating: contactEntity.featureMask)
        self.threemaImageData = contactEntity.contactImage?.data
        self.contactImageData = contactEntity.imageData
        self.readReceipt = contactEntity.readReceipt
        self.typingIndicator = contactEntity.typingIndicator
        self.workAvailabilityStatusEntity = contactEntity.workAvailabilityStatus
        
        super.init()

        // Update tracking
        subscribeForContactEntityChanges(contactEntity: contactEntity)
        addOtherObservers()
        
        updateWorkAvailability()
    }

    public func profilePictureForGroupCalls() -> UIImage {
        updateProfilePicture()
        
        if usesNonGeneratedProfilePicture {
            return profilePicture
        }
        return ProfilePictureGenerator.generateGroupCallImage(initials: initials, color: idColor)
    }
    
    public func generatedProfilePicture() -> UIImage {
        ProfilePictureGenerator.generateImage(
            for: hasGatewayID ? .gateway : .contact(letters: initials),
            color: idColor
        )
    }
    
    // MARK: - Private functions
    
    private func addOtherObservers() {
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(updateProfilePicture),
            name: Notification.Name(kNotificationShowProfilePictureChanged),
            object: nil
        )
    }

    private func subscribeForContactEntityChanges(contactEntity: ContactEntity) {
        
        subscriptionToken = EntityObserver.shared.subscribe(
            managedObject: contactEntity
        ) { [weak self] managedObject, reason in
            guard let self, let contactEntity = managedObject as? ContactEntity else {
                DDLogError("Wrong type, should be ContactEntity")
                return
            }

            switch reason {
            case .deleted:
                if !willBeDeleted {
                    willBeDeleted = true
                }
            case .updated:
                guard identity == ThreemaIdentity(contactEntity.identity),
                      publicKey == contactEntity.publicKey else {
                    DDLogError("Identity or public key mismatch")
                    return
                }

                if publicNickname != contactEntity.publicNickname {
                    publicNickname = contactEntity.publicNickname
                }
                if firstName != contactEntity.firstName {
                    firstName = contactEntity.firstName
                }
                if lastName != contactEntity.lastName {
                    lastName = contactEntity.lastName
                }
                if csi != contactEntity.csi {
                    csi = contactEntity.csi
                }
                if jobTitle != contactEntity.jobTitle {
                    jobTitle = contactEntity.jobTitle
                }
                if department != contactEntity.department {
                    department = contactEntity.department
                }
                if verificationLevel != contactEntity.contactVerificationLevel {
                    verificationLevel = contactEntity.contactVerificationLevel
                }
                let newState = contactEntity.contactState
                if state != newState {
                    state = newState
                }
                if isHidden != contactEntity.isHidden {
                    isHidden = contactEntity.isHidden
                }
                if isWorkContact != contactEntity.isWorkContact {
                    isWorkContact = contactEntity.isWorkContact
                }
                if featureMask != Int(truncating: contactEntity.featureMask) {
                    featureMask = Int(truncating: contactEntity.featureMask)
                    isForwardSecurityAvailable = contactEntity.isForwardSecurityAvailable
                }
                if isWorkContact != contactEntity.isWorkContact {
                    isWorkContact = contactEntity.isWorkContact
                }
                if threemaImageData != contactEntity.contactImage?.data {
                    threemaImageData = contactEntity.contactImage?.data
                }
                if contactImageData != contactEntity.imageData {
                    contactImageData = contactEntity.imageData
                }
                if readReceipt != contactEntity.readReceipt {
                    readReceipt = contactEntity.readReceipt
                }
                if typingIndicator != contactEntity.typingIndicator {
                    typingIndicator = contactEntity.typingIndicator
                }
                if workAvailabilityStatusEntity != contactEntity.workAvailabilityStatus {
                    workAvailabilityStatusEntity = contactEntity.workAvailabilityStatus
                }
            }
        }
    }
    
    private func resolveDisplayName() -> String {
        var value = String(ContactUtil.name(fromFirstname: firstName, lastname: lastName) ?? "")

        if value.isEmpty, let publicNickname, !publicNickname.isEmpty, publicNickname != identity.rawValue {
            value = "~\(publicNickname)"
        }

        if value.isEmpty {
            value = identity.rawValue
        }

        switch state {
        case .inactive:
            value = "\(value) (\(Contact.inactiveString))"
        case .invalid:
            value = "\(value) (\(Contact.invalidString))"
        default:
            break
        }

        if value.isEmpty {
            DDLogError(
                "Display name is marked as nonnull and we should have something to show. Falling back to (unknown)."
            )
            value = Contact.unknownString
        }

        return value
    }
   
    @objc private func updateProfilePicture() {
        profilePicture = resolveProfilePicture()
    }
    
    private func resolveProfilePicture() -> UIImage {
        
        // If `showProfilePictures` is enabled, we prioritize the image sent by the contact, else we use the image
        // set from contacts, if one was set.
        let imageData: Data? =
            if UserSettings.shared().showProfilePictures,
            let data = threemaImageData {
                data
            }
            else {
                contactImageData
            }
        
        // If no data was found, we generate a profile picture
        if let imageData, let image = UIImage(data: imageData) {
            usesNonGeneratedProfilePicture = true
            return image
        }
        else {
            usesNonGeneratedProfilePicture = false
            return ProfilePictureGenerator.generateImage(
                for: hasGatewayID ? .gateway : .contact(letters: initials),
                color: idColor
            )
        }
    }

    // MARK: - Verification level

    public var verificationLevelImageSmall: UIImage {
        switch verificationLevel {
        case .unverified:
            StyleKit.verificationSmall0
        case .serverVerified:
            if isWorkContact {
                StyleKit.verificationSmall3
            }
            else {
                StyleKit.verificationSmall1
            }
        case .fullyVerified:
            if isWorkContact {
                StyleKit.verificationSmall4
            }
            else {
                StyleKit.verificationSmall2
            }
        }
    }

    public var verificationLevelImage: UIImage {
        switch verificationLevel {
        case .unverified:
            StyleKit.verification0
        case .serverVerified:
            if isWorkContact {
                StyleKit.verification3
            }
            else {
                StyleKit.verification1
            }
        case .fullyVerified:
            if isWorkContact {
                StyleKit.verification4
            }
            else {
                StyleKit.verification2
            }
        }
    }

    public var verificationLevelImageBig: UIImage {
        switch verificationLevel {
        case .unverified:
            StyleKit.verificationBig0
        case .serverVerified:
            if isWorkContact {
                StyleKit.verificationBig3
            }
            else {
                StyleKit.verificationBig1
            }
        case .fullyVerified:
            if isWorkContact {
                StyleKit.verificationBig4
            }
            else {
                StyleKit.verificationBig2
            }
        }
    }

    /// Localized string of verification level usable for accessibility
    public var verificationLevelAccessibilityLabel: String {
        switch verificationLevel {
        case .unverified:
            Contact.workAdjustedVerificationLevelString0
        case .serverVerified:
            if isWorkContact {
                Contact.workAdjustedVerificationLevelString3
            }
            else {
                Contact.workAdjustedVerificationLevelString1
            }
        case .fullyVerified:
            if isWorkContact {
                Contact.workAdjustedVerificationLevelString4
            }
            else {
                Contact.workAdjustedVerificationLevelString2
            }
        }
    }
    
    // MARK: - Work availability status
    
    private var workAvailabilityStatusEntity: WorkAvailabilityStatusEntity? {
        didSet {
            updateWorkAvailability()
        }
    }
    
    private func updateWorkAvailability() {
        if let workAvailabilityStatusEntity {
            workAvailabilityStatus = WorkAvailabilityStatus(
                value: workAvailabilityStatusEntity.value.intValue,
                text: workAvailabilityStatusEntity.text
            )
        }
        else {
            workAvailabilityStatus = nil
        }
    }
    
    // MARK: Comparing function

    public func isEqual(to object: Any?) -> Bool {
        guard let object = object as? Contact else {
            return false
        }

        return willBeDeleted == object.willBeDeleted &&
            identity == object.identity &&
            publicKey == object.publicKey &&
            firstName == object.firstName &&
            lastName == object.lastName &&
            csi == object.csi &&
            jobTitle == object.jobTitle &&
            department == object.department &&
            publicNickname == object.publicNickname &&
            verificationLevel == object.verificationLevel &&
            state == object.state &&
            isWorkContact == object.isWorkContact &&
            profilePicture.pngData() == object.profilePicture.pngData() &&
            threemaImageData == object.threemaImageData &&
            contactImageData == object.contactImageData
    }
    
    public func isNameEqual(to object: Any?) -> Bool {
        guard let object = object as? Contact else {
            return false
        }

        return willBeDeleted == object.willBeDeleted &&
            firstName == object.firstName &&
            lastName == object.lastName &&
            publicNickname == object.publicNickname
    }
}

extension Set<Contact> {
    // Comparing contacts independent of the oder
    func contactsEqual(to other: Set<Contact>) -> Bool {
        count == other.count &&
            allSatisfy { le in
                other.contains(where: { le.isEqual(to: $0) })
            }
    }
    
    // Comparing contacts independent of the oder
    func contactIdentitiesEqual(to other: Set<Contact>) -> Bool {
        count == other.count &&
            allSatisfy { le in
                other.contains(where: { le.identity == $0.identity })
            }
    }
    
    // Comparing contacts independent of the oder
    func contactNameEqual(to other: Set<Contact>) -> Bool {
        count == other.count &&
            allSatisfy { le in
                other.contains(where: { le.isNameEqual(to: $0) })
            }
    }
}

Messung
CHZG
869410093

¤ Dauer der Verarbeitung: 0.16 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104845
#Domains=499168