mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
312 lines
12 KiB
Swift
312 lines
12 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import AsyncDisplayKit
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
import TelegramPresentationData
|
|
import TelegramStringFormatting
|
|
import SearchBarNode
|
|
import AppBundle
|
|
import TelegramCore
|
|
|
|
private func loadCountryCodes() -> [Country] {
|
|
guard let filePath = getAppBundle().path(forResource: "PhoneCountries", ofType: "txt") else {
|
|
return []
|
|
}
|
|
guard let stringData = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
|
|
return []
|
|
}
|
|
guard let data = String(data: stringData, encoding: .utf8) else {
|
|
return []
|
|
}
|
|
|
|
let delimiter = ";"
|
|
let endOfLine = "\n"
|
|
|
|
var result: [Country] = []
|
|
var countriesByPrefix: [String: (Country, Country.CountryCode)] = [:]
|
|
|
|
var currentLocation = data.startIndex
|
|
|
|
let locale = Locale(identifier: "en-US")
|
|
|
|
while true {
|
|
guard let codeRange = data.range(of: delimiter, options: [], range: currentLocation ..< data.endIndex) else {
|
|
break
|
|
}
|
|
|
|
let countryCode = String(data[currentLocation ..< codeRange.lowerBound])
|
|
|
|
guard let idRange = data.range(of: delimiter, options: [], range: codeRange.upperBound ..< data.endIndex) else {
|
|
break
|
|
}
|
|
|
|
let countryId = String(data[codeRange.upperBound ..< idRange.lowerBound])
|
|
|
|
let maybeNameRange = data.range(of: endOfLine, options: [], range: idRange.upperBound ..< data.endIndex)
|
|
|
|
let countryName = locale.localizedString(forIdentifier: countryId) ?? ""
|
|
if let _ = Int(countryCode) {
|
|
let code = Country.CountryCode(code: countryCode, prefixes: [], patterns: [])
|
|
let country = Country(id: countryId, name: countryName, localizedName: nil, countryCodes: [code], hidden: false)
|
|
result.append(country)
|
|
countriesByPrefix["\(code.code)"] = (country, code)
|
|
}
|
|
|
|
if let maybeNameRange = maybeNameRange {
|
|
currentLocation = maybeNameRange.upperBound
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
countryCodesByPrefix = countriesByPrefix
|
|
|
|
return result
|
|
}
|
|
|
|
private var countryCodes: [Country] = loadCountryCodes()
|
|
private var countryCodesByPrefix: [String: (Country, Country.CountryCode)] = [:]
|
|
|
|
public func loadServerCountryCodes(accountManager: AccountManager, network: Network, completion: @escaping () -> Void) {
|
|
let _ = (getCountriesList(accountManager: accountManager, network: network, langCode: nil)
|
|
|> deliverOnMainQueue).start(next: { countries in
|
|
countryCodes = countries
|
|
|
|
var countriesByPrefix: [String: (Country, Country.CountryCode)] = [:]
|
|
for country in countries {
|
|
for code in country.countryCodes {
|
|
if !code.prefixes.isEmpty {
|
|
for prefix in code.prefixes {
|
|
countriesByPrefix["\(code.code)\(prefix)"] = (country, code)
|
|
}
|
|
} else {
|
|
countriesByPrefix[code.code] = (country, code)
|
|
}
|
|
}
|
|
}
|
|
countryCodesByPrefix = countriesByPrefix
|
|
Queue.mainQueue().async {
|
|
completion()
|
|
}
|
|
})
|
|
}
|
|
|
|
private final class AuthorizationSequenceCountrySelectionNavigationContentNode: NavigationBarContentNode {
|
|
private let theme: PresentationTheme
|
|
private let strings: PresentationStrings
|
|
|
|
private let cancel: () -> Void
|
|
|
|
private let searchBar: SearchBarNode
|
|
|
|
private var queryUpdated: ((String) -> Void)?
|
|
|
|
init(theme: PresentationTheme, strings: PresentationStrings, cancel: @escaping () -> Void) {
|
|
self.theme = theme
|
|
self.strings = strings
|
|
|
|
self.cancel = cancel
|
|
|
|
self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme), strings: strings, fieldStyle: .modern)
|
|
let placeholderText = strings.Common_Search
|
|
let searchBarFont = Font.regular(17.0)
|
|
|
|
self.searchBar.placeholderString = NSAttributedString(string: placeholderText, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputPlaceholderTextColor)
|
|
|
|
super.init()
|
|
|
|
self.addSubnode(self.searchBar)
|
|
|
|
self.searchBar.cancel = { [weak self] in
|
|
//self?.searchBar.deactivate(clear: false)
|
|
self?.cancel()
|
|
}
|
|
|
|
self.searchBar.textUpdated = { [weak self] query, _ in
|
|
self?.queryUpdated?(query)
|
|
}
|
|
}
|
|
|
|
func setQueryUpdated(_ f: @escaping (String) -> Void) {
|
|
self.queryUpdated = f
|
|
}
|
|
|
|
override var nominalHeight: CGFloat {
|
|
return 54.0
|
|
}
|
|
|
|
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
|
let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 54.0))
|
|
self.searchBar.frame = searchBarFrame
|
|
self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition)
|
|
}
|
|
|
|
func activate() {
|
|
self.searchBar.activate()
|
|
}
|
|
|
|
func deactivate() {
|
|
self.searchBar.deactivate(clear: false)
|
|
}
|
|
}
|
|
|
|
public final class AuthorizationSequenceCountrySelectionController: ViewController {
|
|
static func countries() -> [Country] {
|
|
return countryCodes
|
|
}
|
|
|
|
public static func lookupCountryNameById(_ id: String, strings: PresentationStrings) -> String? {
|
|
for country in self.countries() {
|
|
if id == country.id {
|
|
let locale = localeWithStrings(strings)
|
|
if let countryName = locale.localizedString(forRegionCode: id) {
|
|
return countryName
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
static func lookupCountryById(_ id: String) -> Country? {
|
|
return countryCodes.first { $0.id == id }
|
|
}
|
|
|
|
public static func lookupCountryIdByNumber(_ number: String, preferredCountries: [String: String]) -> (Country, Country.CountryCode)? {
|
|
var results: [(Country, Country.CountryCode)]? = nil
|
|
if number.count == 1, let preferredCountryId = preferredCountries[number], let country = lookupCountryById(preferredCountryId), let code = country.countryCodes.first {
|
|
return (country, code)
|
|
}
|
|
|
|
for i in 0..<number.count {
|
|
let prefix = String(number.prefix(number.count - i))
|
|
if let country = countryCodesByPrefix[prefix] {
|
|
if var currentResults = results {
|
|
if let result = currentResults.first, result.1.code.count > country.1.code.count {
|
|
break
|
|
} else {
|
|
currentResults.append(country)
|
|
}
|
|
} else {
|
|
results = [country]
|
|
}
|
|
}
|
|
}
|
|
if let results = results {
|
|
if !preferredCountries.isEmpty, let (_, code) = results.first {
|
|
if let preferredCountry = preferredCountries[code.code] {
|
|
for (country, code) in results {
|
|
if country.id == preferredCountry {
|
|
return (country, code)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return results.first
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
public static func lookupCountryIdByCode(_ code: Int) -> String? {
|
|
for country in self.countries() {
|
|
for countryCode in country.countryCodes {
|
|
if countryCode.code == "\(code)" {
|
|
return country.id
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
public static func lookupPatternByNumber(_ number: String, preferredCountries: [String: String]) -> String? {
|
|
if let (_, code) = lookupCountryIdByNumber(number, preferredCountries: preferredCountries), !code.patterns.isEmpty {
|
|
var prefixes: [String: String] = [:]
|
|
for pattern in code.patterns {
|
|
let cleanPattern = pattern.replacingOccurrences(of: " ", with: "").replacingOccurrences(of: "X", with: "")
|
|
let cleanPrefix = "\(code.code)\(cleanPattern)"
|
|
prefixes[cleanPrefix] = pattern
|
|
}
|
|
for i in 0..<number.count {
|
|
let prefix = String(number.prefix(number.count - i))
|
|
if let pattern = prefixes[prefix] {
|
|
return pattern
|
|
}
|
|
}
|
|
return code.patterns.first
|
|
}
|
|
return nil
|
|
}
|
|
|
|
private let theme: PresentationTheme
|
|
private let strings: PresentationStrings
|
|
private let displayCodes: Bool
|
|
|
|
private var navigationContentNode: AuthorizationSequenceCountrySelectionNavigationContentNode?
|
|
|
|
private var controllerNode: AuthorizationSequenceCountrySelectionControllerNode {
|
|
return self.displayNode as! AuthorizationSequenceCountrySelectionControllerNode
|
|
}
|
|
|
|
public var completeWithCountryCode: ((Int, String) -> Void)?
|
|
public var dismissed: (() -> Void)?
|
|
|
|
public init(strings: PresentationStrings, theme: PresentationTheme, displayCodes: Bool = true) {
|
|
self.theme = theme
|
|
self.strings = strings
|
|
self.displayCodes = displayCodes
|
|
|
|
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: theme), strings: NavigationBarStrings(presentationStrings: strings)))
|
|
|
|
self.navigationPresentation = .modal
|
|
|
|
self.statusBar.statusBarStyle = theme.rootController.statusBarStyle.style
|
|
|
|
let navigationContentNode = AuthorizationSequenceCountrySelectionNavigationContentNode(theme: theme, strings: strings, cancel: { [weak self] in
|
|
self?.dismissed?()
|
|
self?.dismiss()
|
|
})
|
|
self.navigationContentNode = navigationContentNode
|
|
navigationContentNode.setQueryUpdated { [weak self] query in
|
|
guard let strongSelf = self, strongSelf.isNodeLoaded else {
|
|
return
|
|
}
|
|
strongSelf.controllerNode.updateSearchQuery(query)
|
|
}
|
|
self.navigationBar?.setContentNode(navigationContentNode, animated: false)
|
|
}
|
|
|
|
required public init(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override public func loadDisplayNode() {
|
|
self.displayNode = AuthorizationSequenceCountrySelectionControllerNode(theme: self.theme, strings: self.strings, displayCodes: self.displayCodes, itemSelected: { [weak self] args in
|
|
let (_, countryId, code) = args
|
|
self?.completeWithCountryCode?(code, countryId)
|
|
self?.dismiss()
|
|
})
|
|
self.displayNodeDidLoad()
|
|
}
|
|
|
|
override public func viewDidAppear(_ animated: Bool) {
|
|
super.viewDidAppear(animated)
|
|
|
|
self.navigationContentNode?.activate()
|
|
}
|
|
|
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
|
super.containerLayoutUpdated(layout, transition: transition)
|
|
|
|
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
|
|
}
|
|
|
|
private func cancelPressed() {
|
|
self.dismissed?()
|
|
self.dismiss(completion: nil)
|
|
}
|
|
}
|