|
|
@ -2,16 +2,40 @@
|
|
|
|
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
|
|
|
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
|
|
|
//
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
#import "NBAsYouTypeFormatter.h"
|
|
|
|
|
|
|
|
#import "NBPhoneNumber.h"
|
|
|
|
|
|
|
|
#import "PhoneNumber.h"
|
|
|
|
#import "PhoneNumber.h"
|
|
|
|
#import "PhoneNumberUtil.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>
|
|
|
|
|
|
|
|
|
|
|
|
static NSString *const RPDefaultsKeyPhoneNumberString = @"RPDefaultsKeyPhoneNumberString";
|
|
|
|
static NSString *const RPDefaultsKeyPhoneNumberString = @"RPDefaultsKeyPhoneNumberString";
|
|
|
|
static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneNumberCanonical";
|
|
|
|
static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneNumberCanonical";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@interface PhoneNumber ()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property (nonatomic, readonly) NBPhoneNumber *phoneNumber;
|
|
|
|
|
|
|
|
@property (nonatomic, readonly) NSString *e164;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
|
|
@implementation PhoneNumber
|
|
|
|
@implementation PhoneNumber
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (instancetype)initWithPhoneNumber:(NBPhoneNumber *)phoneNumber e164:(NSString *)e164
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (self = [self init]) {
|
|
|
|
|
|
|
|
OWSAssert(phoneNumber);
|
|
|
|
|
|
|
|
OWSAssert(e164.length > 0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_phoneNumber = phoneNumber;
|
|
|
|
|
|
|
|
_e164 = e164;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return self;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
+ (PhoneNumber *)phoneNumberFromText:(NSString *)text andRegion:(NSString *)regionCode {
|
|
|
|
+ (PhoneNumber *)phoneNumberFromText:(NSString *)text andRegion:(NSString *)regionCode {
|
|
|
|
OWSAssert(text != nil);
|
|
|
|
OWSAssert(text != nil);
|
|
|
|
OWSAssert(regionCode != nil);
|
|
|
|
OWSAssert(regionCode != nil);
|
|
|
@ -32,10 +56,7 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
|
|
|
|
return nil;
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
PhoneNumber *phoneNumber = [PhoneNumber new];
|
|
|
|
return [[PhoneNumber alloc] initWithPhoneNumber:number e164:e164];
|
|
|
|
phoneNumber->phoneNumber = number;
|
|
|
|
|
|
|
|
phoneNumber->e164 = e164;
|
|
|
|
|
|
|
|
return phoneNumber;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
+ (PhoneNumber *)phoneNumberFromUserSpecifiedText:(NSString *)text {
|
|
|
|
+ (PhoneNumber *)phoneNumberFromUserSpecifiedText:(NSString *)text {
|
|
|
@ -118,8 +139,92 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
|
|
|
|
return [self phoneNumberFromUserSpecifiedText:sanitizedString];
|
|
|
|
return [self phoneNumberFromUserSpecifiedText:sanitizedString];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+ (NSString *)nationalPrefixTransformRuleForDefaultRegion
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
static NSString *result = nil;
|
|
|
|
|
|
|
|
static dispatch_once_t onceToken;
|
|
|
|
|
|
|
|
dispatch_once(&onceToken, ^{
|
|
|
|
|
|
|
|
NSString *defaultRegionCode = [self defaultRegionCode];
|
|
|
|
|
|
|
|
NBMetadataHelper *helper = [[NBMetadataHelper alloc] init];
|
|
|
|
|
|
|
|
NBPhoneMetaData *defaultRegionMetadata = [helper getMetadataForRegion:defaultRegionCode];
|
|
|
|
|
|
|
|
result = defaultRegionMetadata.nationalPrefixTransformRule;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// clientPhoneNumber is the local user's phone number and should never change.
|
|
|
|
|
|
|
|
+ (NSString *)nationalPrefixTransformRuleForClientPhoneNumber:(NSString *)clientPhoneNumber
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (clientPhoneNumber.length < 1) {
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static NSString *result = nil;
|
|
|
|
|
|
|
|
static NSString *cachedClientPhoneNumber = nil;
|
|
|
|
|
|
|
|
static dispatch_once_t onceToken;
|
|
|
|
|
|
|
|
dispatch_once(&onceToken, ^{
|
|
|
|
|
|
|
|
// clientPhoneNumber is the local user's phone number and should never change.
|
|
|
|
|
|
|
|
NSNumber *localCallingCode = [[PhoneNumber phoneNumberFromE164:clientPhoneNumber] getCountryCode];
|
|
|
|
|
|
|
|
if (localCallingCode != nil) {
|
|
|
|
|
|
|
|
NSString *localCallingCodePrefix = [NSString stringWithFormat:@"+%@", localCallingCode];
|
|
|
|
|
|
|
|
NSString *localCountryCode =
|
|
|
|
|
|
|
|
[PhoneNumberUtil.sharedUtil probableCountryCodeForCallingCode:localCallingCodePrefix];
|
|
|
|
|
|
|
|
if (localCountryCode && ![localCountryCode isEqualToString:[self defaultRegionCode]]) {
|
|
|
|
|
|
|
|
NBMetadataHelper *helper = [[NBMetadataHelper alloc] init];
|
|
|
|
|
|
|
|
NBPhoneMetaData *localNumberRegionMetadata = [helper getMetadataForRegion:localCountryCode];
|
|
|
|
|
|
|
|
result = localNumberRegionMetadata.nationalPrefixTransformRule;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
cachedClientPhoneNumber = [clientPhoneNumber copy];
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
OWSAssert([cachedClientPhoneNumber isEqualToString:clientPhoneNumber]);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
+ (NSArray<PhoneNumber *> *)tryParsePhoneNumbersFromsUserSpecifiedText:(NSString *)text
|
|
|
|
+ (NSArray<PhoneNumber *> *)tryParsePhoneNumbersFromsUserSpecifiedText:(NSString *)text
|
|
|
|
clientPhoneNumber:(NSString *)clientPhoneNumber
|
|
|
|
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
|
|
|
|
{
|
|
|
|
{
|
|
|
|
OWSAssert(text != nil);
|
|
|
|
OWSAssert(text != nil);
|
|
|
|
|
|
|
|
|
|
|
@ -210,30 +315,32 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (NSURL *)toSystemDialerURL {
|
|
|
|
- (NSURL *)toSystemDialerURL {
|
|
|
|
NSString *link = [NSString stringWithFormat:@"telprompt://%@", e164];
|
|
|
|
NSString *link = [NSString stringWithFormat:@"telprompt://%@", self.e164];
|
|
|
|
return [NSURL URLWithString:link];
|
|
|
|
return [NSURL URLWithString:link];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (NSString *)toE164 {
|
|
|
|
- (NSString *)toE164 {
|
|
|
|
return e164;
|
|
|
|
return self.e164;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (NSNumber *)getCountryCode {
|
|
|
|
- (NSNumber *)getCountryCode {
|
|
|
|
return phoneNumber.countryCode;
|
|
|
|
return self.phoneNumber.countryCode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (BOOL)isValid {
|
|
|
|
- (BOOL)isValid {
|
|
|
|
return [[PhoneNumberUtil sharedUtil].nbPhoneNumberUtil isValidNumber:phoneNumber];
|
|
|
|
return [[PhoneNumberUtil sharedUtil].nbPhoneNumberUtil isValidNumber:self.phoneNumber];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (NSString *)localizedDescriptionForUser {
|
|
|
|
- (NSString *)localizedDescriptionForUser {
|
|
|
|
NBPhoneNumberUtil *phoneUtil = [PhoneNumberUtil sharedUtil].nbPhoneNumberUtil;
|
|
|
|
NBPhoneNumberUtil *phoneUtil = [PhoneNumberUtil sharedUtil].nbPhoneNumberUtil;
|
|
|
|
|
|
|
|
|
|
|
|
NSError *formatError = nil;
|
|
|
|
NSError *formatError = nil;
|
|
|
|
NSString *pretty = [phoneUtil format:phoneNumber numberFormat:NBEPhoneNumberFormatINTERNATIONAL error:&formatError];
|
|
|
|
NSString *pretty =
|
|
|
|
|
|
|
|
[phoneUtil format:self.phoneNumber numberFormat:NBEPhoneNumberFormatINTERNATIONAL error:&formatError];
|
|
|
|
|
|
|
|
|
|
|
|
if (formatError != nil)
|
|
|
|
if (formatError != nil) {
|
|
|
|
return e164;
|
|
|
|
return self.e164;
|
|
|
|
|
|
|
|
}
|
|
|
|
return pretty;
|
|
|
|
return pretty;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -242,18 +349,18 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (NSString *)description {
|
|
|
|
- (NSString *)description {
|
|
|
|
return e164;
|
|
|
|
return self.e164;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)encodeWithCoder:(NSCoder *)encoder {
|
|
|
|
- (void)encodeWithCoder:(NSCoder *)encoder {
|
|
|
|
[encoder encodeObject:phoneNumber forKey:RPDefaultsKeyPhoneNumberString];
|
|
|
|
[encoder encodeObject:self.phoneNumber forKey:RPDefaultsKeyPhoneNumberString];
|
|
|
|
[encoder encodeObject:e164 forKey:RPDefaultsKeyPhoneNumberCanonical];
|
|
|
|
[encoder encodeObject:self.e164 forKey:RPDefaultsKeyPhoneNumberCanonical];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (id)initWithCoder:(NSCoder *)decoder {
|
|
|
|
- (id)initWithCoder:(NSCoder *)decoder {
|
|
|
|
if ((self = [super init])) {
|
|
|
|
if ((self = [super init])) {
|
|
|
|
phoneNumber = [decoder decodeObjectForKey:RPDefaultsKeyPhoneNumberString];
|
|
|
|
_phoneNumber = [decoder decodeObjectForKey:RPDefaultsKeyPhoneNumberString];
|
|
|
|
e164 = [decoder decodeObjectForKey:RPDefaultsKeyPhoneNumberCanonical];
|
|
|
|
_e164 = [decoder decodeObjectForKey:RPDefaultsKeyPhoneNumberCanonical];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return self;
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
}
|
|
|
|