Merge branch 'charlesmchen/nationalPrefixes'
This commit is contained in:
commit
dc211afd53
@ -376,11 +376,17 @@ NSString *const kSelectRecipientViewControllerCellIdentifier = @"kSelectRecipien
|
||||
}
|
||||
NSString *possiblePhoneNumber =
|
||||
[self.callingCode stringByAppendingString:self.phoneNumberTextField.text.digitsOnly];
|
||||
PhoneNumber *parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:possiblePhoneNumber];
|
||||
NSArray<PhoneNumber *> *parsePhoneNumbers =
|
||||
[PhoneNumber tryParsePhoneNumbersFromsUserSpecifiedText:possiblePhoneNumber
|
||||
clientPhoneNumber:[TSAccountManager localNumber]];
|
||||
if (parsePhoneNumbers.count < 1) {
|
||||
return NO;
|
||||
}
|
||||
PhoneNumber *parsedPhoneNumber = parsePhoneNumbers[0];
|
||||
// It'd be nice to use [PhoneNumber isValid] but it always returns false for some countries
|
||||
// (like afghanistan) and there doesn't seem to be a good way to determine beforehand
|
||||
// which countries it can validate for without forking libPhoneNumber.
|
||||
return parsedPhoneNumber && parsedPhoneNumber.toE164.length > 1;
|
||||
return parsedPhoneNumber.toE164.length > 1;
|
||||
}
|
||||
|
||||
- (void)updatePhoneNumberButtonEnabling
|
||||
|
||||
@ -26,7 +26,7 @@ import Foundation
|
||||
public func initiateCall(handle: String) -> Bool {
|
||||
Logger.info("\(TAG) in \(#function) with handle: \(handle)")
|
||||
|
||||
guard let recipientId = PhoneNumber(fromUserSpecifiedText: handle)?.toE164() else {
|
||||
guard let recipientId = PhoneNumber(fromE164: handle)?.toE164() else {
|
||||
Logger.warn("\(TAG) unable to parse signalId from phone number: \(handle)")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <libPhoneNumber-iOS/NBPhoneNumberUtil.h>
|
||||
|
||||
#define COUNTRY_CODE_PREFIX @"+"
|
||||
|
||||
@ -13,15 +12,8 @@
|
||||
* Everything that expects a valid phone number should take a PhoneNumber, not a string, to avoid stringly typing.
|
||||
*
|
||||
*/
|
||||
@interface PhoneNumber : NSObject {
|
||||
@private
|
||||
NBPhoneNumber *phoneNumber;
|
||||
@private
|
||||
NSString *e164;
|
||||
}
|
||||
@interface PhoneNumber : NSObject
|
||||
|
||||
+ (PhoneNumber *)phoneNumberFromText:(NSString *)text andRegion:(NSString *)regionCode;
|
||||
+ (PhoneNumber *)phoneNumberFromUserSpecifiedText:(NSString *)text;
|
||||
+ (PhoneNumber *)phoneNumberFromE164:(NSString *)text;
|
||||
|
||||
+ (PhoneNumber *)tryParsePhoneNumberFromText:(NSString *)text fromRegion:(NSString *)regionCode;
|
||||
|
||||
@ -2,16 +2,40 @@
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NBAsYouTypeFormatter.h"
|
||||
#import "NBPhoneNumber.h"
|
||||
#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>
|
||||
|
||||
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]) {
|
||||
OWSAssert(phoneNumber);
|
||||
OWSAssert(e164.length > 0);
|
||||
|
||||
_phoneNumber = phoneNumber;
|
||||
_e164 = e164;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (PhoneNumber *)phoneNumberFromText:(NSString *)text andRegion:(NSString *)regionCode {
|
||||
OWSAssert(text != nil);
|
||||
OWSAssert(regionCode != nil);
|
||||
@ -32,10 +56,7 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
|
||||
return nil;
|
||||
}
|
||||
|
||||
PhoneNumber *phoneNumber = [PhoneNumber new];
|
||||
phoneNumber->phoneNumber = number;
|
||||
phoneNumber->e164 = e164;
|
||||
return phoneNumber;
|
||||
return [[PhoneNumber alloc] initWithPhoneNumber:number e164:e164];
|
||||
}
|
||||
|
||||
+ (PhoneNumber *)phoneNumberFromUserSpecifiedText:(NSString *)text {
|
||||
@ -118,8 +139,92 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
|
||||
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
|
||||
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);
|
||||
|
||||
@ -210,30 +315,32 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
|
||||
}
|
||||
|
||||
- (NSURL *)toSystemDialerURL {
|
||||
NSString *link = [NSString stringWithFormat:@"telprompt://%@", e164];
|
||||
NSString *link = [NSString stringWithFormat:@"telprompt://%@", self.e164];
|
||||
return [NSURL URLWithString:link];
|
||||
}
|
||||
|
||||
- (NSString *)toE164 {
|
||||
return e164;
|
||||
return self.e164;
|
||||
}
|
||||
|
||||
- (NSNumber *)getCountryCode {
|
||||
return phoneNumber.countryCode;
|
||||
return self.phoneNumber.countryCode;
|
||||
}
|
||||
|
||||
- (BOOL)isValid {
|
||||
return [[PhoneNumberUtil sharedUtil].nbPhoneNumberUtil isValidNumber:phoneNumber];
|
||||
return [[PhoneNumberUtil sharedUtil].nbPhoneNumberUtil isValidNumber:self.phoneNumber];
|
||||
}
|
||||
|
||||
- (NSString *)localizedDescriptionForUser {
|
||||
NBPhoneNumberUtil *phoneUtil = [PhoneNumberUtil sharedUtil].nbPhoneNumberUtil;
|
||||
|
||||
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)
|
||||
return e164;
|
||||
if (formatError != nil) {
|
||||
return self.e164;
|
||||
}
|
||||
return pretty;
|
||||
}
|
||||
|
||||
@ -242,18 +349,18 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return e164;
|
||||
return self.e164;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)encoder {
|
||||
[encoder encodeObject:phoneNumber forKey:RPDefaultsKeyPhoneNumberString];
|
||||
[encoder encodeObject:e164 forKey:RPDefaultsKeyPhoneNumberCanonical];
|
||||
[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];
|
||||
_phoneNumber = [decoder decodeObjectForKey:RPDefaultsKeyPhoneNumberString];
|
||||
_e164 = [decoder decodeObjectForKey:RPDefaultsKeyPhoneNumberCanonical];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@ -9,6 +9,8 @@
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation PhoneNumberTest
|
||||
|
||||
-(void)testE164 {
|
||||
@ -59,4 +61,73 @@
|
||||
XCTAssertEqualObjects(@"+13235551234", [actual toE164]);
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)unpackTryParsePhoneNumbersFromsUserSpecifiedText:(NSString *)text
|
||||
clientPhoneNumber:(NSString *)clientPhoneNumber
|
||||
{
|
||||
NSArray<PhoneNumber *> *phoneNumbers =
|
||||
[PhoneNumber tryParsePhoneNumbersFromsUserSpecifiedText:text clientPhoneNumber:clientPhoneNumber];
|
||||
NSMutableArray<NSString *> *result = [NSMutableArray new];
|
||||
for (PhoneNumber *phoneNumber in phoneNumbers) {
|
||||
[result addObject:phoneNumber.toE164];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)testTryParsePhoneNumbersFromsUserSpecifiedText_SimpleUSA
|
||||
{
|
||||
NSArray<NSString *> *parsed =
|
||||
[self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"323 555 1234" clientPhoneNumber:@"+13213214321"];
|
||||
XCTAssertTrue(parsed.count >= 1);
|
||||
XCTAssertTrue([parsed containsObject:@"+13235551234"]);
|
||||
XCTAssertEqualObjects(parsed.firstObject, @"+13235551234");
|
||||
|
||||
parsed = [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"323-555-1234" clientPhoneNumber:@"+13213214321"];
|
||||
XCTAssertTrue(parsed.count >= 1);
|
||||
XCTAssertTrue([parsed containsObject:@"+13235551234"]);
|
||||
XCTAssertEqualObjects(parsed.firstObject, @"+13235551234");
|
||||
|
||||
parsed = [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"323.555.1234" clientPhoneNumber:@"+13213214321"];
|
||||
XCTAssertTrue(parsed.count >= 1);
|
||||
XCTAssertTrue([parsed containsObject:@"+13235551234"]);
|
||||
XCTAssertEqualObjects(parsed.firstObject, @"+13235551234");
|
||||
|
||||
parsed =
|
||||
[self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"1-323-555-1234" clientPhoneNumber:@"+13213214321"];
|
||||
XCTAssertTrue(parsed.count >= 1);
|
||||
XCTAssertTrue([parsed containsObject:@"+13235551234"]);
|
||||
XCTAssertEqualObjects(parsed.firstObject, @"+13235551234");
|
||||
|
||||
parsed = [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"+13235551234" clientPhoneNumber:@"+13213214321"];
|
||||
XCTAssertTrue(parsed.count >= 1);
|
||||
XCTAssertTrue([parsed containsObject:@"+13235551234"]);
|
||||
XCTAssertEqualObjects(parsed.firstObject, @"+13235551234");
|
||||
}
|
||||
|
||||
- (void)testTryParsePhoneNumbersFromsUserSpecifiedText_Mexico1
|
||||
{
|
||||
NSArray<NSString *> *parsed =
|
||||
[self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"528341639157" clientPhoneNumber:@"+528341639144"];
|
||||
XCTAssertTrue(parsed.count >= 1);
|
||||
XCTAssertTrue([parsed containsObject:@"+528341639157"]);
|
||||
|
||||
parsed = [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"8341639157" clientPhoneNumber:@"+528341639144"];
|
||||
XCTAssertTrue(parsed.count >= 1);
|
||||
XCTAssertTrue([parsed containsObject:@"+528341639157"]);
|
||||
|
||||
parsed = [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"341639157" clientPhoneNumber:@"+528341639144"];
|
||||
XCTAssertTrue(parsed.count >= 1);
|
||||
// The parsing logic should try adding Mexico's national prefix for cell numbers "1"
|
||||
// after the country code.
|
||||
XCTAssertTrue([parsed containsObject:@"+52341639157"]);
|
||||
XCTAssertTrue([parsed containsObject:@"+521341639157"]);
|
||||
|
||||
parsed = [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"528341639157" clientPhoneNumber:@"+13213214321"];
|
||||
XCTAssertTrue(parsed.count >= 1);
|
||||
XCTAssertTrue([parsed containsObject:@"+528341639157"]);
|
||||
|
||||
parsed = [self unpackTryParsePhoneNumbersFromsUserSpecifiedText:@"13235551234" clientPhoneNumber:@"+528341639144"];
|
||||
XCTAssertTrue(parsed.count >= 1);
|
||||
XCTAssertTrue([parsed containsObject:@"+13235551234"]);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Loading…
Reference in New Issue
Block a user