Improve country code extraction

Use new formatting in phone number change section too
This commit is contained in:
Ilya Laktyushin 2020-08-25 19:06:24 +03:00
parent b901cdfb0d
commit 8d89ad6a38
6 changed files with 114 additions and 41 deletions

View File

@ -152,6 +152,18 @@ private final class AuthorizationSequenceCountrySelectionNavigationContentNode:
} }
} }
private func removePlus(_ text: String?) -> String {
var result = ""
if let text = text {
for c in text {
if c != "+" {
result += String(c)
}
}
}
return result
}
public final class AuthorizationSequenceCountrySelectionController: ViewController { public final class AuthorizationSequenceCountrySelectionController: ViewController {
static func countries() -> [Country] { static func countries() -> [Country] {
return countryCodes return countryCodes
@ -176,6 +188,7 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll
} }
public static func lookupCountryIdByNumber(_ number: String, preferredCountries: [String: String]) -> (Country, Country.CountryCode)? { public static func lookupCountryIdByNumber(_ number: String, preferredCountries: [String: String]) -> (Country, Country.CountryCode)? {
let number = removePlus(number)
var results: [(Country, Country.CountryCode)]? = nil var results: [(Country, Country.CountryCode)]? = nil
if number.count == 1, let preferredCountryId = preferredCountries[number], let country = lookupCountryById(preferredCountryId), let code = country.countryCodes.first { if number.count == 1, let preferredCountryId = preferredCountries[number], let country = lookupCountryById(preferredCountryId), let code = country.countryCodes.first {
return (country, code) return (country, code)

View File

@ -1,6 +1,29 @@
import Foundation import Foundation
import AppBundle import AppBundle
public func emojiFlagForISOCountryCode(_ countryCode: String) -> String {
if countryCode.count != 2 {
return ""
}
if countryCode == "XG" {
return "🛰️"
} else if countryCode == "XV" {
return "🌍"
}
if ["YL"].contains(countryCode) {
return ""
}
let base : UInt32 = 127397
var s = ""
for v in countryCode.unicodeScalars {
s.unicodeScalars.append(UnicodeScalar(base + v.value)!)
}
return String(s)
}
private func loadCountriesInfo() -> [(Int, String, String)] { private func loadCountriesInfo() -> [(Int, String, String)] {
guard let filePath = getAppBundle().path(forResource: "PhoneCountries", ofType: "txt") else { guard let filePath = getAppBundle().path(forResource: "PhoneCountries", ofType: "txt") else {
return [] return []

View File

@ -166,6 +166,7 @@ public final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate {
public var returnAction: (() -> Void)? public var returnAction: (() -> Void)?
private let phoneFormatter = InteractivePhoneFormatter() private let phoneFormatter = InteractivePhoneFormatter()
public var customFormatter: ((String) -> String?)?
public var mask: NSAttributedString? { public var mask: NSAttributedString? {
didSet { didSet {
@ -180,7 +181,7 @@ public final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate {
if let text = self.numberField.textField.text { if let text = self.numberField.textField.text {
mutableMask.replaceCharacters(in: NSRange(location: 0, length: min(text.count, mask.string.count)), with: text) mutableMask.replaceCharacters(in: NSRange(location: 0, length: min(text.count, mask.string.count)), with: text)
} }
mutableMask.addAttribute(.foregroundColor, value: UIColor.clear, range: NSRange(location: 0, length: min(self.numberField.textField.text?.count ?? 0, mask.string.count))) mutableMask.addAttribute(.foregroundColor, value: UIColor.clear, range: NSRange(location: 0, length: min(self.numberField.textField.text?.count ?? 0, mutableMask.string.count)))
mutableMask.addAttribute(.kern, value: 1.6, range: NSRange(location: 0, length: mask.string.count)) mutableMask.addAttribute(.kern, value: 1.6, range: NSRange(location: 0, length: mask.string.count))
self.placeholderNode.attributedText = mutableMask self.placeholderNode.attributedText = mutableMask
} else { } else {
@ -265,7 +266,12 @@ public final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate {
} }
private func updateNumber(_ inputText: String, tryRestoringInputPosition: Bool = true, forceNotifyCountryCodeUpdated: Bool = false) { private func updateNumber(_ inputText: String, tryRestoringInputPosition: Bool = true, forceNotifyCountryCodeUpdated: Bool = false) {
let (regionPrefix, text) = self.phoneFormatter.updateText(inputText) var (regionPrefix, text) = self.phoneFormatter.updateText(inputText)
if let customFormatter = self.customFormatter, let customRegionPrefix = customFormatter(inputText) {
regionPrefix = "+\(customRegionPrefix)"
text = inputText
}
var realRegionPrefix: String var realRegionPrefix: String
var numberText: String var numberText: String

View File

@ -87,6 +87,12 @@ final class ChangePhoneNumberController: ViewController {
strongSelf.push(controller) strongSelf.push(controller)
} }
} }
loadServerCountryCodes(accountManager: self.context.sharedContext.accountManager, network: self.context.account.network, completion: { [weak self] in
if let strongSelf = self {
strongSelf.controllerNode.updateCountryCode()
}
})
} }
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {

View File

@ -91,6 +91,8 @@ final class ChangePhoneNumberControllerNode: ASDisplayNode {
} }
} }
var preferredCountryIdForCode: [String: String] = [:]
var selectCountryCode: (() -> Void)? var selectCountryCode: (() -> Void)?
var inProgress: Bool = false { var inProgress: Bool = false {
@ -155,9 +157,43 @@ final class ChangePhoneNumberControllerNode: ASDisplayNode {
self.countryButton.addTarget(self, action: #selector(self.countryPressed), forControlEvents: .touchUpInside) self.countryButton.addTarget(self, action: #selector(self.countryPressed), forControlEvents: .touchUpInside)
let processNumberChange: (String) -> Bool = { [weak self] number in
guard let strongSelf = self else {
return false
}
if let (country, _) = AuthorizationSequenceCountrySelectionController.lookupCountryIdByNumber(number, preferredCountries: strongSelf.preferredCountryIdForCode) {
let flagString = emojiFlagForISOCountryCode(country.id)
let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(country.id, strings: strongSelf.presentationData.strings) ?? country.name
strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(17.0), with: strongSelf.presentationData.theme.list.itemPrimaryTextColor, for: [])
let maskFont = Font.with(size: 20.0, design: .regular, traits: [.monospacedNumbers])
if let mask = AuthorizationSequenceCountrySelectionController.lookupPatternByNumber(number, preferredCountries: strongSelf.preferredCountryIdForCode).flatMap({ NSAttributedString(string: $0, font: maskFont, textColor: strongSelf.presentationData.theme.list.itemPlaceholderTextColor) }) {
strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = nil
strongSelf.phoneInputNode.mask = mask
} else {
strongSelf.phoneInputNode.mask = nil
strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strongSelf.presentationData.strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: strongSelf.presentationData.theme.list.itemPlaceholderTextColor)
}
return true
} else {
return false
}
}
self.phoneInputNode.numberTextUpdated = { [weak self] number in
if let strongSelf = self {
let _ = processNumberChange(strongSelf.phoneInputNode.number)
}
}
self.phoneInputNode.countryCodeUpdated = { [weak self] code, name in self.phoneInputNode.countryCodeUpdated = { [weak self] code, name in
if let strongSelf = self { if let strongSelf = self {
if let code = Int(code), let name = name, let countryName = countryCodeAndIdToName[CountryCodeAndId(code: code, id: name)] { if let name = name {
strongSelf.preferredCountryIdForCode[code] = name
}
if processNumberChange(strongSelf.phoneInputNode.number) {
} else if let code = Int(code), let name = name, let countryName = countryCodeAndIdToName[CountryCodeAndId(code: code, id: name)] {
let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(name, strings: strongSelf.presentationData.strings) ?? countryName let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(name, strings: strongSelf.presentationData.strings) ?? countryName
strongSelf.countryButton.setTitle(localizedName, with: Font.regular(17.0), with: strongSelf.presentationData.theme.list.itemPrimaryTextColor, for: []) strongSelf.countryButton.setTitle(localizedName, with: Font.regular(17.0), with: strongSelf.presentationData.theme.list.itemPrimaryTextColor, for: [])
} else if let code = Int(code), let (_, countryName) = countryCodeToIdAndName[code] { } else if let code = Int(code), let (_, countryName) = countryCodeToIdAndName[code] {
@ -168,6 +204,14 @@ final class ChangePhoneNumberControllerNode: ASDisplayNode {
} }
} }
self.phoneInputNode.customFormatter = { number in
if let (_, code) = AuthorizationSequenceCountrySelectionController.lookupCountryIdByNumber(number, preferredCountries: [:]) {
return code.code
} else {
return nil
}
}
var countryId: String? = nil var countryId: String? = nil
let networkInfo = CTTelephonyNetworkInfo() let networkInfo = CTTelephonyNetworkInfo()
if let carrier = networkInfo.subscriberCellularProvider { if let carrier = networkInfo.subscriberCellularProvider {
@ -193,6 +237,10 @@ final class ChangePhoneNumberControllerNode: ASDisplayNode {
self.phoneInputNode.number = "+\(countryCodeAndId.0)" self.phoneInputNode.number = "+\(countryCodeAndId.0)"
} }
func updateCountryCode() {
self.phoneInputNode.codeAndNumber = self.codeAndNumber
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
var insets = layout.insets(options: [.statusBar, .input]) var insets = layout.insets(options: [.statusBar, .input])
insets.left = layout.safeInsets.left insets.left = layout.safeInsets.left

View File

@ -13,24 +13,6 @@ import SwiftSignalKit
import Postbox import Postbox
import AccountContext import AccountContext
private func emojiFlagForISOCountryCode(_ countryCode: NSString) -> String {
if countryCode.length != 2 {
return ""
}
let base: UInt32 = 127462 - 65
let first: UInt32 = base + UInt32(countryCode.character(at: 0))
let second: UInt32 = base + UInt32(countryCode.character(at: 1))
var data = Data()
data.count = 8
data.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt32>) -> Void in
bytes[0] = first
bytes[1] = second
}
return String(data: data, encoding: String.Encoding.utf32LittleEndian) ?? ""
}
private final class PhoneAndCountryNode: ASDisplayNode { private final class PhoneAndCountryNode: ASDisplayNode {
let strings: PresentationStrings let strings: PresentationStrings
let countryButton: ASButtonNode let countryButton: ASButtonNode
@ -128,25 +110,12 @@ private final class PhoneAndCountryNode: ASDisplayNode {
self.countryButton.addTarget(self, action: #selector(self.countryPressed), forControlEvents: .touchUpInside) self.countryButton.addTarget(self, action: #selector(self.countryPressed), forControlEvents: .touchUpInside)
func removePlus(_ text: String?) -> String {
var result = ""
if let text = text {
for c in text {
if c != "+" {
result += String(c)
}
}
}
return result
}
let processNumberChange: (String) -> Bool = { [weak self] number in let processNumberChange: (String) -> Bool = { [weak self] number in
guard let strongSelf = self else { guard let strongSelf = self else {
return false return false
} }
let number = removePlus(number)
if let (country, _) = AuthorizationSequenceCountrySelectionController.lookupCountryIdByNumber(number, preferredCountries: strongSelf.preferredCountryIdForCode) { if let (country, _) = AuthorizationSequenceCountrySelectionController.lookupCountryIdByNumber(number, preferredCountries: strongSelf.preferredCountryIdForCode) {
let flagString = emojiFlagForISOCountryCode(country.id as NSString) let flagString = emojiFlagForISOCountryCode(country.id)
let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(country.id, strings: strongSelf.strings) ?? country.name let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(country.id, strings: strongSelf.strings) ?? country.name
strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(20.0), with: theme.list.itemPrimaryTextColor, for: []) strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(20.0), with: theme.list.itemPrimaryTextColor, for: [])
@ -178,12 +147,12 @@ private final class PhoneAndCountryNode: ASDisplayNode {
if processNumberChange(strongSelf.phoneInputNode.number) { if processNumberChange(strongSelf.phoneInputNode.number) {
} else if let code = Int(code), let name = name, let countryName = countryCodeAndIdToName[CountryCodeAndId(code: code, id: name)] { } else if let code = Int(code), let name = name, let countryName = countryCodeAndIdToName[CountryCodeAndId(code: code, id: name)] {
let flagString = emojiFlagForISOCountryCode(name as NSString) let flagString = emojiFlagForISOCountryCode(name)
let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(name, strings: strongSelf.strings) ?? countryName let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(name, strings: strongSelf.strings) ?? countryName
strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(20.0), with: theme.list.itemPrimaryTextColor, for: []) strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(20.0), with: theme.list.itemPrimaryTextColor, for: [])
strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor) strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor)
} else if let code = Int(code), let (countryId, countryName) = countryCodeToIdAndName[code] { } else if let code = Int(code), let (countryId, countryName) = countryCodeToIdAndName[code] {
let flagString = emojiFlagForISOCountryCode(countryId as NSString) let flagString = emojiFlagForISOCountryCode(countryId)
let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(countryId, strings: strongSelf.strings) ?? countryName let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(countryId, strings: strongSelf.strings) ?? countryName
strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(20.0), with: theme.list.itemPrimaryTextColor, for: []) strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(20.0), with: theme.list.itemPrimaryTextColor, for: [])
strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor) strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor)
@ -195,6 +164,14 @@ private final class PhoneAndCountryNode: ASDisplayNode {
} }
} }
self.phoneInputNode.customFormatter = { number in
if let (_, code) = AuthorizationSequenceCountrySelectionController.lookupCountryIdByNumber(number, preferredCountries: [:]) {
return code.code
} else {
return nil
}
}
self.phoneInputNode.number = "+1" self.phoneInputNode.number = "+1"
self.phoneInputNode.returnAction = { [weak self] in self.phoneInputNode.returnAction = { [weak self] in
self?.checkPhone?() self?.checkPhone?()
@ -366,10 +343,6 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
self.tokenEventsDisposable.dispose() self.tokenEventsDisposable.dispose()
} }
func updateCountryCode() {
self.phoneAndCountryNode.phoneInputNode.codeAndNumber = self.codeAndNumber
}
override func didLoad() { override func didLoad() {
super.didLoad() super.didLoad()
@ -379,6 +352,10 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
#endif #endif
} }
func updateCountryCode() {
self.phoneAndCountryNode.phoneInputNode.codeAndNumber = self.codeAndNumber
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
var insets = layout.insets(options: []) var insets = layout.insets(options: [])
insets.top = navigationBarHeight insets.top = navigationBarHeight