Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Threema/ThreemaFramework/Utilities/   (Social Medium ©)  Datei vom 26.9.2026 mit Größe 15 kB image not shown  

Quelle  ThreemaUtility.swift   Sprache: JAVA

 

import CocoaLumberjackSwift
import CoreLocation
import Foundation
import PromiseKit
import ThreemaEssentials
import ThreemaMacros

public final class ThreemaUtility: NSObject {
    
    static let postalAddressFormatter: CNPostalAddressFormatter = {
        let formatter = CNPostalAddressFormatter()
        formatter.style = .mailingAddress
        return formatter
    }()
        
    static let modelName: String = {
        var systemInfo = utsname()
        uname(&systemInfo)
        let machineMirror = Mirror(reflecting: systemInfo.machine)
        let identifier = machineMirror.children.reduce("") { identifier, element in
            guard let value = element.value as? Int8, value != 0 else {
                return identifier
            }
            return identifier + String(UnicodeScalar(UInt8(value)))
        }
        return identifier
    }()
    
    /// Format: 4.7b2687
    /// Format Work: 4.7kb2687
    /// Format TestFlight: 4.7b2687-T
    public static let appAndBuildVersion: String = {
        guard let mainBundle = BundleUtil.mainBundle(),
              let version = mainBundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String,
              let suffix = mainBundle.object(forInfoDictionaryKey: "ThreemaVersionSuffix") as? String,
              let build = mainBundle.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String else {
            return "Unknown"
        }
        
        return "\(version)\(suffix)b\(build)\(EnvironmentType.current.shortDescription)"
    }()
    
    /// Format: 4.7 (2687)
    /// Format Work: 4.7k (2687)
    /// Format TestFlight: 4.7 (2687-T)
    public static let appAndBuildVersionPretty: String = {
        guard let mainBundle = BundleUtil.mainBundle() else {
            return "Unknown"
        }
        
        let version = mainBundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String
        let suffix = mainBundle.object(forInfoDictionaryKey: "ThreemaVersionSuffix") as! String
        let build = mainBundle.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as! String
        
        return "\(version)\(suffix) (\(build)\(EnvironmentType.current.shortDescription))"
    }()
    
    private static let locale: String = {
        var language = Locale.current.language.languageCode?.identifier ?? "?"
        // call clientVersionMDMString to save it
        let mdmDescription = additionalMDMString
        let countryCode = (Locale.current as NSLocale).object(forKey: .countryCode)

        if AppGroup.getCurrentType() == AppGroupTypeApp {
            let lang = MyIdentityStore.shared().lastWorkInfoLanguage
            if language != lang {
                MyIdentityStore.shared().lastWorkInfoLanguage = language
            }
        }
        else {
            if let lang = MyIdentityStore.shared().lastWorkInfoLanguage {
                language = lang
            }
        }
        
        return "\(language)/\(countryCode ?? "?")"
    }()
    
    private static let additionalMDMString: String = {
        // The setup state is passed explicitly to keep `MDMSetup()` from reading `AppSetup.state`: this runs from
        // `preLaunchSetup()`, before `AppSetup.registerIfADatabaseFileExists()`, and reading the state that early
        // persists a wrong one if it was never stored before. The value itself does not matter here, because
        // `supportDescriptionString()` reads the two MDM dictionaries from the user defaults directly.
        var mdmDescription = MDMSetup(
            appSetupStateRawValue: AppSetupState.notSetup.rawValue
        ).supportDescriptionString() ?? ""
        if AppGroup.getCurrentType() == AppGroupTypeApp {
            let mdmDes = MyIdentityStore.shared().lastWorkInfoMdmDescription
            if mdmDescription != mdmDes {
                MyIdentityStore.shared().lastWorkInfoMdmDescription = mdmDescription
            }
        }
        else {
            if let mdmDesc = MyIdentityStore.shared().lastWorkInfoMdmDescription {
                mdmDescription = mdmDesc
            }
        }
        
        return mdmDescription
    }()
    
    public static let appInfo = AppInfo(
        version: appAndBuildVersion,
        locale: locale,
        deviceModel: ThreemaUtility.modelName,
        osVersion: UIDevice.current.systemVersion
    )
    
    /// Format: 4.7b2687;de/CH;iPhone7,2;15.1
    @objc public static let clientVersion =
        "\(appInfo.version);I;\(appInfo.locale);\(appInfo.deviceModel);\(appInfo.osVersion)"
    
    @objc public static let clientVersionWithMDM: String = {
        if !additionalMDMString.isEmpty {
            return clientVersion + ";" + additionalMDMString
        }
        return clientVersion
    }()
    
    // MARK: - Other threema type
        
    /// Icon to show if `Contact.showOtherThreemaIcon` is `true`
    ///
    /// If you need a view for it use `OtherThreemaTypeImageView`
    public static var otherThreemaTypeIcon: UIImage {
        if TargetManager.isBusinessApp {
            StyleKit.houseIcon
        }
        else {
            StyleKit.workIcon
        }
    }
    
    /// Accessibility label to use if `Contact.showOtherThreemaIcon` is `true`
    public static var otherThreemaTypeAccessibilityLabel: String {
        if TargetManager.isBusinessApp {
            #localize("threema_type_icon_private_accessibility_label")
        }
        else {
            #localize("threema_type_icon_work_accessibility_label")
        }
    }
    
    /// Checks if the otherTypeIcon should be hidden for a given contact
    /// - Parameter contact: Contact to check
    /// - Returns: Bool that states if icon should be hidden
    @available(*, deprecated, renamed: "ContactEntity.showOtherThreemaTypeIcon")
    public static func shouldHideOtherTypeIcon(for contact: ContactEntity?) -> Bool {
        
        guard let contact else {
            return true
        }
        
        if contact.isEchoEcho || contact.isGatewayID || TargetManager.isOnPrem {
            return true
        }
        
        if TargetManager.isBusinessApp {
            return UserSettings.shared().workIdentities.contains(contact.identity)
        }
        else {
            return !UserSettings.shared().workIdentities.contains(contact.identity)
        }
    }
    
    // MARK: - POI
    
    /// Fetches the address given a location if the privacy setting is enabled, else returns coordinate string
    /// - Parameters:
    /// - location: Location to fetch address for
    /// - completionHandler: Returns address or coordinate string
    @available(swift, obsoleted: 1.0, renamed: "fetchAddress(for:)", message: "Only use from Objective-C")
    @objc static func fetchAddressObjc(for location: CLLocation, completionHandler: @escaping (String) -> Void) {
        fetchAddress(for: location)
            .done { address in
                completionHandler(address)
            }
    }
    
    /// Fetches the address given a location if the privacy setting is enabled, else returns coordinate string
    /// - Parameter location: Location to fetch address for
    /// - Returns: Guarantee with address or coordinate string
    static func fetchAddress(for location: CLLocation) -> Guarantee<String> {
        Guarantee { seal in
            let coordinates = String(
                format: "%.5f°, %.5f°",
                location.coordinate.latitude,
                location.coordinate.longitude
            )
            
            // Don't fetch address if POI are disabled in privacy settings
            guard UserSettings.shared().enablePoi else {
                seal(coordinates)
                return
            }
            
            CLGeocoder().reverseGeocodeLocation(location, preferredLocale: Locale.current) { placemarks, error in
                
                if let error {
                    DDLogError("Reverse geocoding failed: \(error)")
                    seal(coordinates)
                    return
                }
                
                guard let placemark = placemarks?.first, let postalAddress = placemark.postalAddress else {
                    seal(coordinates)
                    return
                }
                // Format address and return it
                seal(postalAddressFormatter.string(from: postalAddress))
            }
        }
    }
    
    /// Fire a local push notification
    /// - Parameters:
    ///   - identifier: Identifier of the notification
    ///   - title: Title of the notification
    ///   - body: Body of the notification
    ///   - badge: Badge count of the notification
    ///   - userInfo: UserInfo of the notification
    ///   - completionHandler: completionHandler
    public static func showLocalNotification(
        identifier: String,
        title: String,
        body: String,
        badge: Int,
        userInfo: [AnyHashable: Any]?,
        completionHandler: (() -> Void)? = nil
    ) {
        let notification = UNMutableNotificationContent()
        notification.title = title
        notification.body = body
        notification.badge = NSNumber(integerLiteral: badge)
        
        if let userInfo {
            notification.userInfo = userInfo
        }
        
        if let pushGroupSound = UserSettings.shared().pushGroupSound,
           pushGroupSound != "none" {
            notification.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "\(pushGroupSound).caf"))
        }
        
        let request = UNNotificationRequest(identifier: identifier, content: notification, trigger: nil)
        UNUserNotificationCenter.current().add(request) { _ in
            completionHandler?()
        }
    }
    
    /// Fire a local notification when disconnect from Threema Web
    /// - Parameter entityManager: EntityManager to load the unread messages count
    public static func sendThreemaWebConnectionLostLocalNotification(entityManager: EntityManager) {
        let unreadMessages = UnreadMessages(entityManager: entityManager, taskManager: TaskManager())
        showLocalNotification(
            identifier: "threemaWeb",
            title: #localize("notification_threemaweb_connectionlost_title"),
            body: #localize("notification_threemaweb_connectionlost_body"),
            badge: unreadMessages.totalCount(),
            userInfo: nil
        )
    }
    
    /// Fire a local notification when microphone permission is not set or denied
    /// - Parameter entityManager: EntityManager to load the unread messages count
    public static func sendMicrophonePermissionErrorLocalNotification(entityManager: EntityManager) {
        let unreadMessages = UnreadMessages(entityManager: entityManager, taskManager: TaskManager())
        ThreemaUtility.showLocalNotification(
            identifier: "microphonePermissionError",
            title: String.localizedStringWithFormat(
                #localize("notification_no_access_microphone_title"),
                TargetManager.localizedAppName
            ),
            body: String.localizedStringWithFormat(
                #localize("notification_no_access_microphone_body"),
                TargetManager.appName
            ),
            badge: unreadMessages.totalCount(),
            userInfo: ["threema": ["cmd": "microphonePermissionError"]]
        )
    }
    
    // MARK: - String Conversions
    
    /// Trims whiteSpaces and newlines as well as (U+FFFC) from string
    /// - Parameter string: String to trim
    /// - Returns: Trimmed string
    public static func trimCharacters(in string: String) -> String {
        // Remove text attachments from the string we want to send.
        // If we do not remove this we'll be able to send "empty" messages.
        // This character usually gets inserted when showing the microphone icon in the text field when using dictation
        // from iOS.
        // https://stackoverflow.com/questions/41564176/remove-u-0000fffc-unicode-scalar-from-string/45058555#45058555
        // https://www.fileformat.info/info/unicode/char/fffc/index.htm
        let sanitized = string.trimmingCharacters(in: ["\u{fffc}"])
        
        // Trim general whitespace
        return sanitized.trimmingCharacters(in: .whitespacesAndNewlines)
    }
    
    public static func accessibilityString(atTime timeInterval: TimeInterval, with prefixKey: String) -> String {
        let accessibilityTime = accessibilityTimeString(for: Int(timeInterval))
        return String.localizedStringWithFormat("%@ %@", prefixKey, accessibilityTime)
    }
    
    public static func accessibilityTimeString(for totalSeconds: Int) -> String {
        let formatter = DateComponentsFormatter()
        formatter.unitsStyle = .full
        formatter.collapsesLargestUnit = true
        formatter.allowedUnits = [.minute, .second]
        
        return formatter.string(from: .init(totalSeconds)) ?? ""
    }
    
    /// Trims a given message be of`kMaxMessageLen` length at most
    /// - Parameter text: Text to check
    /// - Returns: Array containing split messages, or the original text if it was short enough
    public static func trimMessageText(text: String, length: Int = Int(kMaxMessageLen)) -> [String] {
       
        guard !text.replacingOccurrences(of: " ", with: "").isEmpty else {
            return []
        }
        
        // If the text is shorter than the max length, we return directly
        if Data(text.utf8).count <= length {
            return [text]
        }
        
        var remainingMessage = text
        var trimmedMessages = [String]()
        var tempTrimmedMessage = text
        
        while !remainingMessage.isEmpty {
            var stringToCheck: String
            
            if Data(tempTrimmedMessage.utf8).count <= length {
                stringToCheck = tempTrimmedMessage
            }
            else {
                if let lastSpace = tempTrimmedMessage.lastIndex(of: " ") {
                    let postSpaceCount = tempTrimmedMessage.distance(from: lastSpace, to: tempTrimmedMessage.endIndex)
                    stringToCheck = String(tempTrimmedMessage.dropLast(postSpaceCount))
                    
                    if stringToCheck.isEmpty {
                        remainingMessage = String(remainingMessage.dropFirst())
                        stringToCheck = String(tempTrimmedMessage.dropFirst())
                    }
                }
                else {
                    stringToCheck = String(tempTrimmedMessage.dropLast())
                }
            }

            if Data(stringToCheck.utf8).count <= length {
                trimmedMessages.append(stringToCheck)
                remainingMessage = String(remainingMessage.dropFirst(stringToCheck.count))
                tempTrimmedMessage = remainingMessage
                if tempTrimmedMessage.isEmpty {
                    break
                }
            }
            else {
                tempTrimmedMessage = stringToCheck
            }
        }
        
        return trimmedMessages.map { $0.trimmingCharacters(in: .whitespaces) }
    }
    
    public static func formatDataLength(_ numBytes: Float) -> String {
        let formatter = ByteCountFormatter()
        formatter.countStyle = .file
        formatter.allowedUnits = [.useKB, .useMB]
        formatter.includesUnit = true
        formatter.includesCount = true
        formatter.isAdaptive = true

        return formatter.string(fromByteCount: Int64(numBytes))
    }
}

Messung
CHZG
85989793

¤ Dauer der Verarbeitung: 0.5 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.