Swiftgram/TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift
2017-08-15 14:44:14 +03:00

491 lines
18 KiB
Swift

import Foundation
import AsyncDisplayKit
import Display
import TelegramCore
let countryCodeToName: [Int: String] = [
1876: "Jamaica",
1869: "Saint Kitts & Nevis",
1868: "Trinidad & Tobago",
1784: "Saint Vincent & the Grenadines",
1767: "Dominica",
1758: "Saint Lucia",
1721: "Sint Maarten",
1684: "American Samoa",
1671: "Guam",
1670: "Northern Mariana Islands",
1664: "Montserrat",
1649: "Turks & Caicos Islands",
1473: "Grenada",
1441: "Bermuda",
1345: "Cayman Islands",
1340: "US Virgin Islands",
1284: "British Virgin Islands",
1268: "Antigua & Barbuda",
1264: "Anguilla",
1246: "Barbados",
1242: "Bahamas",
998: "Uzbekistan",
996: "Kyrgyzstan",
995: "Georgia",
994: "Azerbaijan",
993: "Turkmenistan",
992: "Tajikistan",
977: "Nepal",
976: "Mongolia",
975: "Bhutan",
974: "Qatar",
973: "Bahrain",
972: "Israel",
971: "United Arab Emirates",
970: "Palestine",
968: "Oman",
967: "Yemen",
966: "Saudi Arabia",
965: "Kuwait",
964: "Iraq",
963: "Syrian Arab Republic",
962: "Jordan",
961: "Lebanon",
960: "Maldives",
886: "Taiwan",
880: "Bangladesh",
856: "Laos",
855: "Cambodia",
853: "Macau",
852: "Hong Kong",
850: "North Korea",
692: "Marshall Islands",
691: "Micronesia",
690: "Tokelau",
689: "French Polynesia",
688: "Tuvalu",
687: "New Caledonia",
686: "Kiribati",
685: "Samoa",
683: "Niue",
682: "Cook Islands",
681: "Wallis & Futuna",
680: "Palau",
679: "Fiji",
678: "Vanuatu",
677: "Solomon Islands",
676: "Tonga",
675: "Papua New Guinea",
674: "Nauru",
673: "Brunei Darussalam",
672: "Norfolk Island",
670: "Timor-Leste",
599: "Bonaire, Sint Eustatius & Saba",
//599: "Curaçao",
598: "Uruguay",
597: "Suriname",
596: "Martinique",
595: "Paraguay",
594: "French Guiana",
593: "Ecuador",
592: "Guyana",
591: "Bolivia",
590: "Guadeloupe",
509: "Haiti",
508: "Saint Pierre & Miquelon",
507: "Panama",
506: "Costa Rica",
505: "Nicaragua",
504: "Honduras",
503: "El Salvador",
502: "Guatemala",
501: "Belize",
500: "Falkland Islands",
423: "Liechtenstein",
421: "Slovakia",
420: "Czech Republic",
389: "Macedonia",
387: "Bosnia & Herzegovina",
386: "Slovenia",
385: "Croatia",
382: "Montenegro",
381: "Serbia",
380: "Ukraine",
378: "San Marino",
377: "Monaco",
376: "Andorra",
375: "Belarus",
374: "Armenia",
373: "Moldova",
372: "Estonia",
371: "Latvia",
370: "Lithuania",
359: "Bulgaria",
358: "Finland",
357: "Cyprus",
356: "Malta",
355: "Albania",
354: "Iceland",
353: "Ireland",
352: "Luxembourg",
351: "Portugal",
350: "Gibraltar",
299: "Greenland",
298: "Faroe Islands",
297: "Aruba",
291: "Eritrea",
290: "Saint Helena",
269: "Comoros",
268: "Swaziland",
267: "Botswana",
266: "Lesotho",
265: "Malawi",
264: "Namibia",
263: "Zimbabwe",
262: "Réunion",
261: "Madagascar",
260: "Zambia",
258: "Mozambique",
257: "Burundi",
256: "Uganda",
255: "Tanzania",
254: "Kenya",
253: "Djibouti",
252: "Somalia",
251: "Ethiopia",
250: "Rwanda",
249: "Sudan",
248: "Seychelles",
247: "Saint Helena",
246: "Diego Garcia",
245: "Guinea-Bissau",
244: "Angola",
243: "Congo (Dem. Rep.)",
242: "Congo (Rep.)",
241: "Gabon",
240: "Equatorial Guinea",
239: "São Tomé & Príncipe",
238: "Cape Verde",
237: "Cameroon",
236: "Central African Rep.",
235: "Chad",
234: "Nigeria",
233: "Ghana",
232: "Sierra Leone",
231: "Liberia",
230: "Mauritius",
229: "Benin",
228: "Togo",
227: "Niger",
226: "Burkina Faso",
225: "Côte d`Ivoire",
224: "Guinea",
223: "Mali",
222: "Mauritania",
221: "Senegal",
220: "Gambia",
218: "Libya",
216: "Tunisia",
213: "Algeria",
212: "Morocco",
211: "South Sudan",
98: "Iran",
95: "Myanmar",
94: "Sri Lanka",
93: "Afghanistan",
92: "Pakistan",
91: "India",
90: "Turkey",
86: "China",
84: "Vietnam",
82: "South Korea",
81: "Japan",
66: "Thailand",
65: "Singapore",
64: "New Zealand",
63: "Philippines",
62: "Indonesia",
61: "Australia",
60: "Malaysia",
58: "Venezuela",
57: "Colombia",
56: "Chile",
55: "Brazil",
54: "Argentina",
53: "Cuba",
52: "Mexico",
51: "Peru",
49: "Germany",
48: "Poland",
47: "Norway",
46: "Sweden",
45: "Denmark",
44: "United Kingdom",
43: "Austria",
41: "Switzerland",
40: "Romania",
39: "Italy",
36: "Hungary",
34: "Spain",
33: "France",
32: "Belgium",
31: "Netherlands",
30: "Greece",
27: "South Africa",
20: "Egypt",
7: "Russian Federation",
// 7: "Kazakhstan",
1: "USA",
// 1: "Puerto Rico",
// 1: "Dominican Rep.",
// 1: "Canada"
]
private let countryButtonBackground = generateImage(CGSize(width: 61.0, height: 67.0), rotatedContext: { size, context in
let arrowSize: CGFloat = 10.0
let lineWidth = UIScreenPixel
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(UIColor(rgb: 0xbcbbc1).cgColor)
context.setLineWidth(lineWidth)
context.move(to: CGPoint(x: size.width, y: size.height - arrowSize - lineWidth / 2.0))
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - arrowSize - lineWidth / 2.0))
context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize, y: size.height - lineWidth / 2.0))
context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize - arrowSize, y: size.height - arrowSize - lineWidth / 2.0))
context.addLine(to: CGPoint(x: 15.0, y: size.height - arrowSize - lineWidth / 2.0))
context.strokePath()
})?.stretchableImage(withLeftCapWidth: 61, topCapHeight: 1)
private let countryButtonHighlightedBackground = generateImage(CGSize(width: 60.0, height: 67.0), rotatedContext: { size, context in
let arrowSize: CGFloat = 10.0
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor(rgb: 0xbcbbc1).cgColor)
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height - arrowSize)))
context.move(to: CGPoint(x: size.width, y: size.height - arrowSize))
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - arrowSize))
context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize, y: size.height))
context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize - arrowSize, y: size.height - arrowSize))
context.closePath()
context.fillPath()
})?.stretchableImage(withLeftCapWidth: 61, topCapHeight: 2)
private let phoneInputBackground = generateImage(CGSize(width: 85.0, height: 57.0), rotatedContext: { size, context in
let arrowSize: CGFloat = 10.0
let lineWidth = UIScreenPixel
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(UIColor(rgb: 0xbcbbc1).cgColor)
context.setLineWidth(lineWidth)
context.move(to: CGPoint(x: 15.0, y: size.height - lineWidth / 2.0))
context.addLine(to: CGPoint(x: size.width, y: size.height - lineWidth / 2.0))
context.strokePath()
context.move(to: CGPoint(x: size.width - 2.0 + lineWidth / 2.0, y: size.height - lineWidth / 2.0))
context.addLine(to: CGPoint(x: size.width - 2.0 + lineWidth / 2.0, y: 0.0))
context.strokePath()
})?.stretchableImage(withLeftCapWidth: 84, topCapHeight: 2)
final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
private let navigationBackgroundNode: ASDisplayNode
private let stripeNode: ASDisplayNode
private let titleNode: ASTextNode
private let noticeNode: ASTextNode
private let termsOfServiceNode: ASTextNode
private let countryButton: ASButtonNode
private let phoneBackground: ASImageNode
private let phoneInputNode: PhoneInputNode
var currentNumber: String {
return self.phoneInputNode.number
}
var codeAndNumber: (Int32?, String) {
get {
return self.phoneInputNode.codeAndNumber
} set(value) {
self.phoneInputNode.codeAndNumber = value
}
}
var selectCountryCode: (() -> Void)?
var inProgress: Bool = false {
didSet {
self.phoneInputNode.enableEditing = !self.inProgress
self.phoneInputNode.alpha = self.inProgress ? 0.6 : 1.0
self.countryButton.isEnabled = !self.inProgress
}
}
override init() {
self.navigationBackgroundNode = ASDisplayNode()
self.navigationBackgroundNode.isLayerBacked = true
self.navigationBackgroundNode.backgroundColor = UIColor(rgb: 0xefefef)
self.stripeNode = ASDisplayNode()
self.stripeNode.isLayerBacked = true
self.stripeNode.backgroundColor = UIColor(rgb: 0xbcbbc1)
self.titleNode = ASTextNode()
self.titleNode.isLayerBacked = true
self.titleNode.displaysAsynchronously = false
self.titleNode.attributedText = NSAttributedString(string: "Your Phone", font: Font.light(30.0), textColor: UIColor.black)
self.noticeNode = ASTextNode()
self.noticeNode.isLayerBacked = true
self.noticeNode.displaysAsynchronously = false
self.noticeNode.attributedText = NSAttributedString(string: "Please confirm your country code and enter your phone number.", font: Font.regular(16.0), textColor: UIColor(rgb: 0x878787), paragraphAlignment: .center)
self.termsOfServiceNode = ASTextNode()
self.termsOfServiceNode.isLayerBacked = true
self.termsOfServiceNode.displaysAsynchronously = false
let termsString = NSMutableAttributedString()
termsString.append(NSAttributedString(string: "By signing up,\nyou agree to the ", font: Font.regular(16.0), textColor: UIColor.black))
termsString.append(NSAttributedString(string: "Terms of Service", font: Font.regular(16.0), textColor: UIColor(rgb: 0x007ee5)))
termsString.append(NSAttributedString(string: ".", font: Font.regular(16.0), textColor: UIColor.black))
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
termsString.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: NSMakeRange(0, termsString.length))
self.termsOfServiceNode.attributedText = termsString
self.countryButton = ASButtonNode()
self.countryButton.setBackgroundImage(countryButtonBackground, for: [])
self.countryButton.setBackgroundImage(countryButtonHighlightedBackground, for: .highlighted)
self.phoneBackground = ASImageNode()
self.phoneBackground.image = phoneInputBackground
self.phoneBackground.displaysAsynchronously = false
self.phoneBackground.displayWithoutProcessing = true
self.phoneBackground.isLayerBacked = true
self.phoneInputNode = PhoneInputNode()
super.init()
self.setViewBlock({
return UITracingLayerView()
})
self.backgroundColor = UIColor.white
self.addSubnode(self.navigationBackgroundNode)
self.addSubnode(self.stripeNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.termsOfServiceNode)
self.addSubnode(self.noticeNode)
self.addSubnode(self.phoneBackground)
self.addSubnode(self.countryButton)
self.addSubnode(self.phoneInputNode)
self.countryButton.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 15.0, bottom: 10.0, right: 0.0)
self.countryButton.contentHorizontalAlignment = .left
self.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: "Your phone number", font: Font.regular(20.0), textColor: UIColor(rgb: 0xbcbcc3))
self.countryButton.addTarget(self, action: #selector(self.countryPressed), forControlEvents: .touchUpInside)
self.phoneInputNode.countryCodeUpdated = { [weak self] code in
if let strongSelf = self {
if let code = Int(code), let countryName = countryCodeToName[code] {
strongSelf.countryButton.setTitle(countryName, with: Font.regular(20.0), with: .black, for: [])
} else {
strongSelf.countryButton.setTitle("Select Country", with: Font.regular(20.0), with: .black, for: [])
}
}
}
self.phoneInputNode.number = "+1"
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
let insets = layout.insets(options: [.statusBar, .input])
let availableHeight = max(1.0, layout.size.height - insets.top - insets.bottom)
if max(layout.size.width, layout.size.height) > 1023.0 {
self.titleNode.attributedText = NSAttributedString(string: "Your Phone", font: Font.light(40.0), textColor: UIColor.black)
} else {
self.titleNode.attributedText = NSAttributedString(string: "Your Phone", font: Font.light(30.0), textColor: UIColor.black)
}
let titleSize = self.titleNode.measure(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude))
let minimalTitleSpacing: CGFloat = 10.0
let maxTitleSpacing: CGFloat = 28.0
let countryButtonHeight: CGFloat = 57.0
let inputFieldsHeight: CGFloat = 57.0
let minimalNoticeSpacing: CGFloat = 11.0
let maxNoticeSpacing: CGFloat = 35.0
let noticeSize = self.noticeNode.measure(CGSize(width: layout.size.width - 28.0, height: CGFloat.greatestFiniteMagnitude))
let minimalTermsOfServiceSpacing: CGFloat = 6.0
let maxTermsOfServiceSpacing: CGFloat = 20.0
let termsOfServiceSize = self.termsOfServiceNode.measure(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude))
let minTrailingSpacing: CGFloat = 10.0
let inputHeight = countryButtonHeight + inputFieldsHeight
let essentialHeight = titleSize.height + minimalTitleSpacing + inputHeight
let additionalHeight = minimalNoticeSpacing + noticeSize.height + minimalTermsOfServiceSpacing + termsOfServiceSize.height + minTrailingSpacing
let navigationHeight: CGFloat
if essentialHeight + additionalHeight > availableHeight || availableHeight * 0.66 - inputHeight < additionalHeight {
transition.updateAlpha(node: self.noticeNode, alpha: 0.0)
transition.updateAlpha(node: self.termsOfServiceNode, alpha: 0.0)
navigationHeight = min(floor(availableHeight * 0.3), availableHeight - countryButtonHeight - inputFieldsHeight)
} else {
transition.updateAlpha(node: self.noticeNode, alpha: 1.0)
transition.updateAlpha(node: self.termsOfServiceNode, alpha: 1.0)
navigationHeight = floor(availableHeight * 0.3)
}
transition.updateFrame(node: self.navigationBackgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: navigationHeight)))
transition.updateFrame(node: self.stripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
let titleOffset: CGFloat
if navigationHeight * 0.5 < titleSize.height + minimalTitleSpacing {
titleOffset = floor((navigationHeight - titleSize.height) / 2.0)
} else {
titleOffset = max(navigationHeight * 0.5, navigationHeight - maxTitleSpacing - titleSize.height)
}
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: titleOffset), size: titleSize))
transition.updateFrame(node: self.countryButton, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: layout.size.width, height: 67.0)))
transition.updateFrame(node: self.phoneBackground, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationHeight + 57.0), size: CGSize(width: layout.size.width, height: 57.0)))
let countryCodeFrame = CGRect(origin: CGPoint(x: 18.0, y: navigationHeight + 58.0), size: CGSize(width: 60.0, height: 57.0))
let numberFrame = CGRect(origin: CGPoint(x: 96.0, y: navigationHeight + 58.0), size: CGSize(width: layout.size.width - 96.0 - 8.0, height: 57.0))
let phoneInputFrame = countryCodeFrame.union(numberFrame)
transition.updateFrame(node: self.phoneInputNode, frame: phoneInputFrame)
transition.updateFrame(node: self.phoneInputNode.countryCodeField, frame: countryCodeFrame.offsetBy(dx: -phoneInputFrame.minX, dy: -phoneInputFrame.minY))
transition.updateFrame(node: self.phoneInputNode.numberField, frame: numberFrame.offsetBy(dx: -phoneInputFrame.minX, dy: -phoneInputFrame.minY))
let additionalAvailableHeight = max(1.0, availableHeight - phoneInputFrame.maxY)
let additionalAvailableSpacing = max(1.0, additionalAvailableHeight - noticeSize.height - termsOfServiceSize.height)
let noticeSpacingFactor = maxNoticeSpacing / (maxNoticeSpacing + maxTermsOfServiceSpacing + minTrailingSpacing)
let termsOfServiceSpacingFactor = maxTermsOfServiceSpacing / (maxNoticeSpacing + maxTermsOfServiceSpacing + minTrailingSpacing)
let noticeSpacing: CGFloat
let termsOfServiceSpacing: CGFloat
if additionalAvailableHeight <= maxNoticeSpacing + noticeSize.height + maxTermsOfServiceSpacing + termsOfServiceSize.height + minTrailingSpacing {
termsOfServiceSpacing = min(floor(termsOfServiceSpacingFactor * additionalAvailableSpacing), maxTermsOfServiceSpacing)
noticeSpacing = floor((additionalAvailableHeight - termsOfServiceSpacing - noticeSize.height - termsOfServiceSize.height) / 2.0)
} else {
noticeSpacing = min(floor(noticeSpacingFactor * additionalAvailableSpacing), maxNoticeSpacing)
termsOfServiceSpacing = min(floor(termsOfServiceSpacingFactor * additionalAvailableSpacing), maxTermsOfServiceSpacing)
}
let noticeFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - noticeSize.width) / 2.0), y: phoneInputFrame.maxY + noticeSpacing), size: noticeSize)
let termsOfServiceFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - termsOfServiceSize.width) / 2.0), y: noticeFrame.maxY + termsOfServiceSpacing), size: termsOfServiceSize)
transition.updateFrame(node: self.noticeNode, frame: noticeFrame)
transition.updateFrame(node: self.termsOfServiceNode, frame: termsOfServiceFrame)
}
func activateInput() {
self.phoneInputNode.numberField.textField.becomeFirstResponder()
}
func animateError() {
self.phoneInputNode.countryCodeField.layer.addShakeAnimation()
self.phoneInputNode.numberField.layer.addShakeAnimation()
}
@objc func countryPressed() {
self.selectCountryCode?()
}
}