mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
582 lines
22 KiB
Objective-C
582 lines
22 KiB
Objective-C
//
|
|
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
#import "PhoneNumber.h"
|
|
#import "PhoneNumberUtil.h"
|
|
#import <libPhoneNumber_iOS/NBAsYouTypeFormatter.h>
|
|
#import <libPhoneNumber_iOS/NBMetadataHelper.h>
|
|
#import <libPhoneNumber_iOS/NBPhoneMetaData.h>
|
|
#import <libPhoneNumber_iOS/NBPhoneNumber.h>
|
|
#import <libPhoneNumber_iOS/NBPhoneNumberUtil.h>
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
static NSString *const RPDefaultsKeyPhoneNumberString = @"RPDefaultsKeyPhoneNumberString";
|
|
static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneNumberCanonical";
|
|
|
|
@interface PhoneNumber ()
|
|
|
|
@property (nonatomic, readonly) NBPhoneNumber *phoneNumber;
|
|
@property (nonatomic, readonly) NSString *e164;
|
|
|
|
@end
|
|
|
|
#pragma mark -
|
|
|
|
@implementation PhoneNumber
|
|
|
|
- (instancetype)initWithPhoneNumber:(NBPhoneNumber *)phoneNumber e164:(NSString *)e164
|
|
{
|
|
if (self = [self init]) {
|
|
OWSAssertDebug(phoneNumber);
|
|
OWSAssertDebug(e164.length > 0);
|
|
|
|
_phoneNumber = phoneNumber;
|
|
_e164 = e164;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
+ (nullable PhoneNumber *)phoneNumberFromText:(NSString *)text andRegion:(NSString *)regionCode {
|
|
OWSAssertDebug(text != nil);
|
|
OWSAssertDebug(regionCode != nil);
|
|
|
|
PhoneNumberUtil *phoneUtil = [PhoneNumberUtil sharedThreadLocal];
|
|
|
|
NSError *parseError = nil;
|
|
NBPhoneNumber *number = [phoneUtil parse:text defaultRegion:regionCode error:&parseError];
|
|
|
|
if (parseError) {
|
|
return nil;
|
|
}
|
|
|
|
NSError *toE164Error;
|
|
NSString *e164 = [phoneUtil format:number numberFormat:NBEPhoneNumberFormatE164 error:&toE164Error];
|
|
if (toE164Error) {
|
|
OWSLogDebug(@"Issue while formatting number: %@", [toE164Error description]);
|
|
return nil;
|
|
}
|
|
|
|
return [[PhoneNumber alloc] initWithPhoneNumber:number e164:e164];
|
|
}
|
|
|
|
+ (nullable PhoneNumber *)phoneNumberFromUserSpecifiedText:(NSString *)text {
|
|
OWSAssertDebug(text != nil);
|
|
|
|
return [PhoneNumber phoneNumberFromText:text andRegion:[self defaultCountryCode]];
|
|
}
|
|
|
|
+ (NSString *)defaultCountryCode
|
|
{
|
|
NSLocale *locale = [NSLocale currentLocale];
|
|
|
|
NSString *_Nullable countryCode = nil;
|
|
#if TARGET_OS_IPHONE
|
|
countryCode = [[PhoneNumberUtil sharedThreadLocal].nbPhoneNumberUtil countryCodeByCarrier];
|
|
|
|
if ([countryCode isEqualToString:@"ZZ"]) {
|
|
countryCode = [locale objectForKey:NSLocaleCountryCode];
|
|
}
|
|
#else
|
|
countryCode = [locale objectForKey:NSLocaleCountryCode];
|
|
#endif
|
|
if (!countryCode) {
|
|
OWSFailDebug(@"Could not identify country code for locale: %@", locale);
|
|
countryCode = @"US";
|
|
}
|
|
return countryCode;
|
|
}
|
|
|
|
+ (nullable PhoneNumber *)phoneNumberFromE164:(NSString *)text {
|
|
return [[PhoneNumber alloc] initWithPhoneNumber:[NBPhoneNumber new] e164:text];
|
|
// Loki: Original code:
|
|
// ========
|
|
// OWSAssertDebug(text != nil);
|
|
// OWSAssertDebug([text hasPrefix:COUNTRY_CODE_PREFIX]);
|
|
// PhoneNumber *number = [PhoneNumber phoneNumberFromText:text andRegion:@"ZZ"];
|
|
// OWSAssertDebug(number != nil);
|
|
// return number;
|
|
// ========
|
|
}
|
|
|
|
+ (NSString *)bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:(NSString *)input {
|
|
return [PhoneNumber bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:input
|
|
withSpecifiedRegionCode:[self defaultCountryCode]];
|
|
}
|
|
|
|
+ (NSString *)bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:(NSString *)input
|
|
withSpecifiedCountryCodeString:(NSString *)countryCodeString {
|
|
return [PhoneNumber
|
|
bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:input
|
|
withSpecifiedRegionCode:
|
|
[PhoneNumber regionCodeFromCountryCodeString:countryCodeString]];
|
|
}
|
|
|
|
+ (NSString *)bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:(NSString *)input
|
|
withSpecifiedRegionCode:(NSString *)regionCode {
|
|
NBAsYouTypeFormatter *formatter = [[NBAsYouTypeFormatter alloc] initWithRegionCode:regionCode];
|
|
|
|
NSString *result = input;
|
|
for (NSUInteger i = 0; i < input.length; i++) {
|
|
result = [formatter inputDigit:[input substringWithRange:NSMakeRange(i, 1)]];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
+ (NSString *)formatIntAsEN:(int)value
|
|
{
|
|
static NSNumberFormatter *formatter = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
formatter = [NSNumberFormatter new];
|
|
formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US"];
|
|
});
|
|
return [formatter stringFromNumber:@(value)];
|
|
}
|
|
|
|
+ (NSString *)bestEffortLocalizedPhoneNumberWithE164:(NSString *)phoneNumber
|
|
{
|
|
OWSAssertDebug(phoneNumber);
|
|
|
|
if (![phoneNumber hasPrefix:COUNTRY_CODE_PREFIX]) {
|
|
return phoneNumber;
|
|
}
|
|
|
|
PhoneNumber *_Nullable parsedPhoneNumber = [self tryParsePhoneNumberFromE164:phoneNumber];
|
|
if (!parsedPhoneNumber) {
|
|
OWSLogWarn(@"could not parse phone number.");
|
|
return phoneNumber;
|
|
}
|
|
NSNumber *_Nullable countryCode = [parsedPhoneNumber getCountryCode];
|
|
if (!countryCode) {
|
|
OWSLogWarn(@"parsed phone number has no country code.");
|
|
return phoneNumber;
|
|
}
|
|
NSString *countryCodeString = [self formatIntAsEN:countryCode.intValue];
|
|
if (countryCodeString.length < 1) {
|
|
OWSLogWarn(@"invalid country code.");
|
|
return phoneNumber;
|
|
}
|
|
NSString *_Nullable formattedPhoneNumber =
|
|
[self bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:phoneNumber
|
|
withSpecifiedRegionCode:countryCodeString];
|
|
if (!countryCode) {
|
|
OWSLogWarn(@"could not format phone number.");
|
|
return phoneNumber;
|
|
}
|
|
return formattedPhoneNumber;
|
|
}
|
|
|
|
+ (NSString *)regionCodeFromCountryCodeString:(NSString *)countryCodeString {
|
|
NBPhoneNumberUtil *phoneUtil = [PhoneNumberUtil sharedThreadLocal].nbPhoneNumberUtil;
|
|
NSString *regionCode =
|
|
[phoneUtil getRegionCodeForCountryCode:@([[countryCodeString substringFromIndex:1] integerValue])];
|
|
return regionCode;
|
|
}
|
|
|
|
+ (nullable PhoneNumber *)tryParsePhoneNumberFromUserSpecifiedText:(NSString *)text {
|
|
OWSAssertDebug(text != nil);
|
|
|
|
if ([text isEqualToString:@""]) {
|
|
return nil;
|
|
}
|
|
NSString *sanitizedString = [self removeFormattingCharacters:text];
|
|
|
|
return [self phoneNumberFromUserSpecifiedText:sanitizedString];
|
|
}
|
|
|
|
+ (nullable NSString *)nationalPrefixTransformRuleForDefaultRegion
|
|
{
|
|
static NSString *result = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
NSString *defaultCountryCode = [self defaultCountryCode];
|
|
NBMetadataHelper *helper = [[NBMetadataHelper alloc] init];
|
|
NBPhoneMetaData *defaultRegionMetadata = [helper getMetadataForRegion:defaultCountryCode];
|
|
result = defaultRegionMetadata.nationalPrefixTransformRule;
|
|
});
|
|
return result;
|
|
}
|
|
|
|
// clientPhoneNumber is the local user's phone number and should never change.
|
|
+ (nullable NSString *)nationalPrefixTransformRuleForClientPhoneNumber:(NSString *)clientPhoneNumber
|
|
{
|
|
if (clientPhoneNumber.length < 1) {
|
|
return nil;
|
|
}
|
|
static NSString *result = nil;
|
|
static NSString *cachedClientPhoneNumber = nil;
|
|
static dispatch_once_t onceToken;
|
|
|
|
// clientPhoneNumber is the local user's phone number and should never change.
|
|
static void (^updateCachedClientPhoneNumber)(void);
|
|
updateCachedClientPhoneNumber = ^(void) {
|
|
NSNumber *localCallingCode = [[PhoneNumber phoneNumberFromE164:clientPhoneNumber] getCountryCode];
|
|
if (localCallingCode != nil) {
|
|
NSString *localCallingCodePrefix = [NSString stringWithFormat:@"+%@", localCallingCode];
|
|
NSString *localCountryCode =
|
|
[PhoneNumberUtil.sharedThreadLocal probableCountryCodeForCallingCode:localCallingCodePrefix];
|
|
if (localCountryCode && ![localCountryCode isEqualToString:[self defaultCountryCode]]) {
|
|
NBMetadataHelper *helper = [[NBMetadataHelper alloc] init];
|
|
NBPhoneMetaData *localNumberRegionMetadata = [helper getMetadataForRegion:localCountryCode];
|
|
result = localNumberRegionMetadata.nationalPrefixTransformRule;
|
|
} else {
|
|
result = nil;
|
|
}
|
|
}
|
|
cachedClientPhoneNumber = [clientPhoneNumber copy];
|
|
};
|
|
|
|
#ifdef DEBUG
|
|
// For performance, we want to cahce this result, but it breaks tests since local number
|
|
// can change.
|
|
if (CurrentAppContext().isRunningTests) {
|
|
updateCachedClientPhoneNumber();
|
|
} else {
|
|
dispatch_once(&onceToken, ^{
|
|
updateCachedClientPhoneNumber();
|
|
});
|
|
}
|
|
#else
|
|
dispatch_once(&onceToken, ^{
|
|
updateCachedClientPhoneNumber();
|
|
});
|
|
OWSAssertDebug([cachedClientPhoneNumber isEqualToString:clientPhoneNumber]);
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
+ (NSArray<PhoneNumber *> *)tryParsePhoneNumbersFromsUserSpecifiedText:(NSString *)text
|
|
clientPhoneNumber:(NSString *)clientPhoneNumber
|
|
{
|
|
NSMutableArray<PhoneNumber *> *result =
|
|
[[self tryParsePhoneNumbersFromNormalizedText:text clientPhoneNumber:clientPhoneNumber] mutableCopy];
|
|
|
|
// A handful of countries (Mexico, Argentina, etc.) require a "national" prefix after
|
|
// their country calling code.
|
|
//
|
|
// It's a bit hacky, but we reconstruct these national prefixes from libPhoneNumber's
|
|
// parsing logic. It's okay if we botch this a little. The risk is that we end up with
|
|
// some misformatted numbers with extra non-numeric regex syntax. These erroneously
|
|
// parsed numbers will never be presented to the user, since they'll never survive the
|
|
// contacts intersection.
|
|
//
|
|
// 1. Try to apply a "national prefix" using the phone's region.
|
|
NSString *nationalPrefixTransformRuleForDefaultRegion = [self nationalPrefixTransformRuleForDefaultRegion];
|
|
if ([nationalPrefixTransformRuleForDefaultRegion containsString:@"$1"]) {
|
|
NSString *normalizedText =
|
|
[nationalPrefixTransformRuleForDefaultRegion stringByReplacingOccurrencesOfString:@"$1" withString:text];
|
|
if (![normalizedText containsString:@"$"]) {
|
|
[result addObjectsFromArray:[self tryParsePhoneNumbersFromNormalizedText:normalizedText
|
|
clientPhoneNumber:clientPhoneNumber]];
|
|
}
|
|
}
|
|
|
|
// 2. Try to apply a "national prefix" using the region that corresponds to the
|
|
// calling code for the local phone number.
|
|
NSString *nationalPrefixTransformRuleForClientPhoneNumber =
|
|
[self nationalPrefixTransformRuleForClientPhoneNumber:clientPhoneNumber];
|
|
if ([nationalPrefixTransformRuleForClientPhoneNumber containsString:@"$1"]) {
|
|
NSString *normalizedText =
|
|
[nationalPrefixTransformRuleForClientPhoneNumber stringByReplacingOccurrencesOfString:@"$1"
|
|
withString:text];
|
|
if (![normalizedText containsString:@"$"]) {
|
|
[result addObjectsFromArray:[self tryParsePhoneNumbersFromNormalizedText:normalizedText
|
|
clientPhoneNumber:clientPhoneNumber]];
|
|
}
|
|
}
|
|
|
|
return [result copy];
|
|
}
|
|
|
|
+ (NSArray<PhoneNumber *> *)tryParsePhoneNumbersFromNormalizedText:(NSString *)text
|
|
clientPhoneNumber:(NSString *)clientPhoneNumber
|
|
{
|
|
OWSAssertDebug(text != nil);
|
|
|
|
text = [text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
|
if ([text isEqualToString:@""]) {
|
|
return nil;
|
|
}
|
|
|
|
NSString *sanitizedString = [self removeFormattingCharacters:text];
|
|
OWSAssertDebug(sanitizedString != nil);
|
|
|
|
NSMutableArray *result = [NSMutableArray new];
|
|
NSMutableSet *phoneNumberSet = [NSMutableSet new];
|
|
void (^tryParsingWithCountryCode)(NSString *, NSString *) = ^(NSString *text,
|
|
NSString *countryCode) {
|
|
PhoneNumber *phoneNumber = [PhoneNumber phoneNumberFromText:text
|
|
andRegion:countryCode];
|
|
if (phoneNumber && [phoneNumber toE164] && ![phoneNumberSet containsObject:[phoneNumber toE164]]) {
|
|
[result addObject:phoneNumber];
|
|
[phoneNumberSet addObject:[phoneNumber toE164]];
|
|
}
|
|
};
|
|
|
|
tryParsingWithCountryCode(sanitizedString, [self defaultCountryCode]);
|
|
|
|
if ([sanitizedString hasPrefix:@"+"]) {
|
|
// If the text starts with "+", don't try prepending
|
|
// anything else.
|
|
return result;
|
|
}
|
|
|
|
// Try just adding "+" and parsing it.
|
|
tryParsingWithCountryCode([NSString stringWithFormat:@"+%@", sanitizedString], [self defaultCountryCode]);
|
|
|
|
// Order matters; better results should appear first so prefer
|
|
// matches with the same country code as this client's phone number.
|
|
if (clientPhoneNumber.length == 0) {
|
|
OWSFailDebug(@"clientPhoneNumber had unexpected length");
|
|
return result;
|
|
}
|
|
|
|
// Note that NBPhoneNumber uses "country code" to refer to what we call a
|
|
// "calling code" (i.e. 44 in +44123123). Within SSK we use "country code"
|
|
// (and sometimes "region code") to refer to a country's ISO 2-letter code
|
|
// (ISO 3166-1 alpha-2).
|
|
NSNumber *callingCodeForLocalNumber = [[PhoneNumber phoneNumberFromE164:clientPhoneNumber] getCountryCode];
|
|
if (callingCodeForLocalNumber == nil) {
|
|
OWSFailDebug(@"callingCodeForLocalNumber was unexpectedly nil");
|
|
return result;
|
|
}
|
|
|
|
NSString *callingCodePrefix = [NSString stringWithFormat:@"+%@", callingCodeForLocalNumber];
|
|
|
|
tryParsingWithCountryCode([callingCodePrefix stringByAppendingString:sanitizedString], [self defaultCountryCode]);
|
|
|
|
// Try to determine what the country code is for the local phone number
|
|
// and also try parsing the phone number using that country code if it
|
|
// differs from the device's region code.
|
|
//
|
|
// For example, a French person living in Italy might have an
|
|
// Italian phone number but use French region/language for their
|
|
// phone. They're likely to have both Italian and French contacts.
|
|
NSString *localCountryCode =
|
|
[PhoneNumberUtil.sharedThreadLocal probableCountryCodeForCallingCode:callingCodePrefix];
|
|
if (localCountryCode && ![localCountryCode isEqualToString:[self defaultCountryCode]]) {
|
|
tryParsingWithCountryCode([callingCodePrefix stringByAppendingString:sanitizedString], localCountryCode);
|
|
}
|
|
|
|
NSString *_Nullable phoneNumberByApplyingMissingAreaCode =
|
|
[self applyMissingAreaCodeWithCallingCodeForReferenceNumber:callingCodeForLocalNumber
|
|
referenceNumber:clientPhoneNumber
|
|
sanitizedInputText:sanitizedString];
|
|
if (phoneNumberByApplyingMissingAreaCode) {
|
|
tryParsingWithCountryCode(phoneNumberByApplyingMissingAreaCode, localCountryCode);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#pragma mark - missing area code
|
|
|
|
+ (nullable NSString *)applyMissingAreaCodeWithCallingCodeForReferenceNumber:(NSNumber *)callingCodeForReferenceNumber
|
|
referenceNumber:(NSString *)referenceNumber
|
|
sanitizedInputText:(NSString *)sanitizedInputText
|
|
{
|
|
if ([callingCodeForReferenceNumber isEqual:@(55)]) {
|
|
return
|
|
[self applyMissingBrazilAreaCodeWithReferenceNumber:referenceNumber sanitizedInputText:sanitizedInputText];
|
|
} else if ([callingCodeForReferenceNumber isEqual:@(1)]) {
|
|
return [self applyMissingUnitedStatesAreaCodeWithReferenceNumber:referenceNumber
|
|
sanitizedInputText:sanitizedInputText];
|
|
} else {
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
#pragma mark - missing brazil area code
|
|
|
|
+ (nullable NSString *)applyMissingBrazilAreaCodeWithReferenceNumber:(NSString *)referenceNumber
|
|
sanitizedInputText:(NSString *)sanitizedInputText
|
|
{
|
|
NSError *error;
|
|
NSRegularExpression *missingAreaCodeRegex =
|
|
[[NSRegularExpression alloc] initWithPattern:@"^(9?\\d{8})$" options:0 error:&error];
|
|
if (error) {
|
|
OWSFailDebug(@"failure: %@", error);
|
|
return nil;
|
|
}
|
|
|
|
if ([missingAreaCodeRegex firstMatchInString:sanitizedInputText
|
|
options:0
|
|
range:NSMakeRange(0, sanitizedInputText.length)]
|
|
== nil) {
|
|
}
|
|
|
|
NSString *_Nullable referenceAreaCode = [self brazilAreaCodeFromReferenceNumberE164:referenceNumber];
|
|
if (!referenceAreaCode) {
|
|
return nil;
|
|
}
|
|
return [NSString stringWithFormat:@"+55%@%@", referenceAreaCode, sanitizedInputText];
|
|
}
|
|
|
|
+ (nullable NSString *)brazilAreaCodeFromReferenceNumberE164:(NSString *)referenceNumberE164
|
|
{
|
|
NSError *error;
|
|
NSRegularExpression *areaCodeRegex =
|
|
[[NSRegularExpression alloc] initWithPattern:@"^\\+55(\\d{2})9?\\d{8}" options:0 error:&error];
|
|
if (error) {
|
|
OWSFailDebug(@"failure: %@", error);
|
|
return nil;
|
|
}
|
|
|
|
NSArray<NSTextCheckingResult *> *matches =
|
|
[areaCodeRegex matchesInString:referenceNumberE164 options:0 range:NSMakeRange(0, referenceNumberE164.length)];
|
|
if (matches.count == 0) {
|
|
OWSFailDebug(@"failure: unexpectedly unable to extract area code from US number");
|
|
return nil;
|
|
}
|
|
NSTextCheckingResult *match = matches[0];
|
|
|
|
NSRange firstCaptureRange = [match rangeAtIndex:1];
|
|
return [referenceNumberE164 substringWithRange:firstCaptureRange];
|
|
}
|
|
|
|
#pragma mark - missing US area code
|
|
|
|
+ (nullable NSString *)applyMissingUnitedStatesAreaCodeWithReferenceNumber:(NSString *)referenceNumber
|
|
sanitizedInputText:(NSString *)sanitizedInputText
|
|
{
|
|
NSError *error;
|
|
NSRegularExpression *missingAreaCodeRegex =
|
|
[[NSRegularExpression alloc] initWithPattern:@"^(\\d{7})$" options:0 error:&error];
|
|
if (error) {
|
|
OWSFailDebug(@"failure: %@", error);
|
|
return nil;
|
|
}
|
|
|
|
if ([missingAreaCodeRegex firstMatchInString:sanitizedInputText
|
|
options:0
|
|
range:NSMakeRange(0, sanitizedInputText.length)]
|
|
== nil) {
|
|
// area code isn't missing
|
|
return nil;
|
|
}
|
|
|
|
NSString *_Nullable referenceAreaCode = [self unitedStateAreaCodeFromReferenceNumberE164:referenceNumber];
|
|
if (!referenceAreaCode) {
|
|
return nil;
|
|
}
|
|
return [NSString stringWithFormat:@"+1%@%@", referenceAreaCode, sanitizedInputText];
|
|
}
|
|
|
|
+ (nullable NSString *)unitedStateAreaCodeFromReferenceNumberE164:(NSString *)referenceNumberE164
|
|
{
|
|
NSError *error;
|
|
NSRegularExpression *areaCodeRegex =
|
|
[[NSRegularExpression alloc] initWithPattern:@"^\\+1(\\d{3})" options:0 error:&error];
|
|
if (error) {
|
|
OWSFailDebug(@"failure: %@", error);
|
|
return nil;
|
|
}
|
|
|
|
NSArray<NSTextCheckingResult *> *matches =
|
|
[areaCodeRegex matchesInString:referenceNumberE164 options:0 range:NSMakeRange(0, referenceNumberE164.length)];
|
|
if (matches.count == 0) {
|
|
OWSFailDebug(@"failure: unexpectedly unable to extract area code from US number");
|
|
return nil;
|
|
}
|
|
NSTextCheckingResult *match = matches[0];
|
|
|
|
NSRange firstCaptureRange = [match rangeAtIndex:1];
|
|
return [referenceNumberE164 substringWithRange:firstCaptureRange];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
+ (NSString *)removeFormattingCharacters:(NSString *)inputString {
|
|
char outputString[inputString.length + 1];
|
|
|
|
int outputLength = 0;
|
|
for (NSUInteger i = 0; i < inputString.length; i++) {
|
|
unichar c = [inputString characterAtIndex:i];
|
|
if (c == '+' || (c >= '0' && c <= '9')) {
|
|
outputString[outputLength++] = (char)c;
|
|
}
|
|
}
|
|
|
|
outputString[outputLength] = 0;
|
|
return [NSString stringWithUTF8String:(void *)outputString];
|
|
}
|
|
|
|
+ (nullable PhoneNumber *)tryParsePhoneNumberFromE164:(NSString *)text {
|
|
OWSAssertDebug(text != nil);
|
|
if (![text hasPrefix:COUNTRY_CODE_PREFIX]) {
|
|
return nil;
|
|
}
|
|
|
|
return [self phoneNumberFromE164:text];
|
|
}
|
|
|
|
- (NSURL *)toSystemDialerURL {
|
|
NSString *link = [NSString stringWithFormat:@"telprompt://%@", self.e164];
|
|
return [NSURL URLWithString:link];
|
|
}
|
|
|
|
- (NSString *)toE164 {
|
|
return self.e164;
|
|
}
|
|
|
|
- (nullable NSNumber *)getCountryCode {
|
|
return self.phoneNumber.countryCode;
|
|
}
|
|
|
|
- (nullable NSString *)nationalNumber
|
|
{
|
|
NSError *error;
|
|
NSString *nationalNumber = [[PhoneNumberUtil sharedThreadLocal] format:self.phoneNumber
|
|
numberFormat:NBEPhoneNumberFormatNATIONAL
|
|
error:&error];
|
|
if (error) {
|
|
OWSLogVerbose(@"error parsing number into national format: %@", error);
|
|
return nil;
|
|
}
|
|
|
|
return nationalNumber;
|
|
}
|
|
|
|
- (BOOL)isValid
|
|
{
|
|
return [[PhoneNumberUtil sharedThreadLocal].nbPhoneNumberUtil isValidNumber:self.phoneNumber];
|
|
}
|
|
|
|
- (NSString *)description {
|
|
return self.e164;
|
|
}
|
|
|
|
- (void)encodeWithCoder:(NSCoder *)encoder {
|
|
[encoder encodeObject:self.phoneNumber forKey:RPDefaultsKeyPhoneNumberString];
|
|
[encoder encodeObject:self.e164 forKey:RPDefaultsKeyPhoneNumberCanonical];
|
|
}
|
|
|
|
- (id)initWithCoder:(NSCoder *)decoder {
|
|
if ((self = [super init])) {
|
|
_phoneNumber = [decoder decodeObjectForKey:RPDefaultsKeyPhoneNumberString];
|
|
_e164 = [decoder decodeObjectForKey:RPDefaultsKeyPhoneNumberCanonical];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (NSComparisonResult)compare:(PhoneNumber *)other
|
|
{
|
|
return [self.toE164 compare:other.toE164];
|
|
}
|
|
|
|
- (BOOL)isEqual:(id)other
|
|
{
|
|
if (![other isMemberOfClass:[self class]]) {
|
|
return NO;
|
|
}
|
|
PhoneNumber *otherPhoneNumber = (PhoneNumber *)other;
|
|
|
|
return [self.phoneNumber isEqual:otherPhoneNumber.phoneNumber];
|
|
}
|
|
|
|
@end
|
|
|
|
NS_ASSUME_NONNULL_END
|