TM-SGNL-iOS/SignalUI/RecipientPickers/PhoneNumberCountry.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

129 lines
5.3 KiB
Swift

//
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import SignalServiceKit
public struct PhoneNumberCountry: Equatable {
// e.g. France
public let countryName: String
// e.g. +33
public let plusPrefixedCallingCode: String
// e.g. FR
public let countryCode: String
public init(countryName: String, plusPrefixedCallingCode: String, countryCode: String) {
self.countryName = countryName
self.plusPrefixedCallingCode = plusPrefixedCallingCode
self.countryCode = countryCode
}
public static var defaultValue: PhoneNumberCountry {
AssertIsOnMainThread()
let countryCode: String = PhoneNumberUtil.defaultCountryCode()
let callingCodeNumber = SSKEnvironment.shared.phoneNumberUtilRef.getCallingCode(forRegion: countryCode)
let plusPrefixedCallingCode = "\(PhoneNumber.countryCodePrefix)\(callingCodeNumber)"
let countryName = PhoneNumberUtil.countryName(fromCountryCode: countryCode)
return PhoneNumberCountry(countryName: countryName, plusPrefixedCallingCode: plusPrefixedCallingCode, countryCode: countryCode)
}
// MARK: -
private static func bestEffortCallingCode(fromCountryCode countryCode: String) -> Int? {
if let result = PhoneNumberUtil.callingCodeForUnsupportedCountryCode(countryCode) {
return result
}
let result = SSKEnvironment.shared.phoneNumberUtilRef.getCallingCode(forRegion: countryCode)
return result == 0 ? nil : result
}
private static func does(_ string: String, matchQuery query: String) -> Bool {
let searchOptions: String.CompareOptions = [.caseInsensitive, .anchored]
let stringTokens = string.components(separatedBy: .whitespaces)
let queryTokens = query.components(separatedBy: .whitespaces)
return queryTokens.allSatisfy { queryToken in
if queryToken.isEmpty {
return true
}
return stringTokens.contains { stringToken in
stringToken.range(of: queryToken, options: searchOptions) != nil
}
}
}
/// Get country codes from a search term.
private static func countryCodes(forSearchTerm searchTerm: String?) -> [String] {
let cleanedSearch = (searchTerm ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
var countryCodesAndNames = NSLocale.isoCountryCodes.compactMap { countryCode -> (countryCode: String, countryName: String)? in
guard let callingCode = bestEffortCallingCode(fromCountryCode: countryCode) else {
// Clipperton Island is filtered intentionally.
owsAssertDebug(countryCode == "CP")
return nil
}
let countryName = PhoneNumberUtil.countryName(fromCountryCode: countryCode)
let isMatch = (
cleanedSearch.isEmpty ||
Self.does(countryName, matchQuery: cleanedSearch) ||
Self.does(countryCode, matchQuery: cleanedSearch) ||
"+\(callingCode)".contains(cleanedSearch)
)
return isMatch ? (countryCode, countryName) : nil
}
countryCodesAndNames.sort(by: { lhs, rhs in
return lhs.countryName.localizedCaseInsensitiveCompare(rhs.countryName) == .orderedAscending
})
return countryCodesAndNames.map(\.countryCode)
}
public static func buildCountries(searchText: String?) -> [PhoneNumberCountry] {
let searchText = searchText?.strippedOrNil
let countryCodes: [String] = countryCodes(forSearchTerm: searchText)
return PhoneNumberCountry.buildCountries(forCountryCodes: countryCodes)
}
private static func buildCountries(forCountryCodes countryCodes: [String]) -> [PhoneNumberCountry] {
return countryCodes.compactMap { (countryCode: String) -> PhoneNumberCountry? in
guard let countryCode = countryCode.strippedOrNil else {
owsFailDebug("Invalid countryCode.")
return nil
}
return buildCountry(forCountryCode: countryCode)
}
}
public static func buildCountry(forCountryCode countryCode: String) -> PhoneNumberCountry? {
guard let callingCode = bestEffortCallingCode(fromCountryCode: countryCode) else {
owsFailDebug("Invalid countryCode.")
return nil
}
return buildCountry(countryCode: countryCode, plusPrefixedCallingCode: "+\(callingCode)")
}
public static func buildCountry(forCallingCode callingCode: Int) -> PhoneNumberCountry? {
let phoneNumberUtil = SSKEnvironment.shared.phoneNumberUtilRef
guard let countryCode = phoneNumberUtil.getFilteredRegionCodeForCallingCode(callingCode) else {
owsFailDebug("Invalid callingCode.")
return nil
}
return buildCountry(countryCode: countryCode, plusPrefixedCallingCode: "+\(callingCode)")
}
private static func buildCountry(countryCode: String, plusPrefixedCallingCode: String) -> PhoneNumberCountry {
return PhoneNumberCountry(
countryName: PhoneNumberUtil.countryName(fromCountryCode: countryCode),
plusPrefixedCallingCode: plusPrefixedCallingCode,
countryCode: countryCode
)
}
}