From 42b5d078883f25a3b6497f2bb5f2cc55f376175b Mon Sep 17 00:00:00 2001 From: Ali <> Date: Wed, 24 Nov 2021 00:42:18 +0400 Subject: [PATCH] Updated code input --- .../Sources/AuthorizationLayout.swift | 3 +- .../Sources/AuthorizationOptionText.swift | 6 +- .../Sources/CalendarMessageScreen.swift | 2 +- submodules/CodeInputView/BUILD | 21 ++ .../CodeInputView/Sources/CodeInputView.swift | 293 ++++++++++++++++++ submodules/TelegramApi/Sources/Api0.swift | 2 +- submodules/TelegramApi/Sources/Api3.swift | 28 +- .../Sources/Account/AccountManager.swift | 20 +- .../AccountManager/AccountManagerImpl.swift | 25 ++ .../TelegramCore/Sources/Authorization.swift | 144 +++++---- .../TelegramEngine/Auth/AuthTransfer.swift | 4 +- submodules/TelegramUI/BUILD | 1 + ...orizationSequenceCodeEntryController.swift | 5 +- ...ationSequenceCodeEntryControllerNode.swift | 269 ++++++++++++---- 14 files changed, 683 insertions(+), 140 deletions(-) create mode 100644 submodules/CodeInputView/BUILD create mode 100644 submodules/CodeInputView/Sources/CodeInputView.swift diff --git a/submodules/AuthorizationUI/Sources/AuthorizationLayout.swift b/submodules/AuthorizationUI/Sources/AuthorizationLayout.swift index 29e62e2b1a..1fddcb52e4 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationLayout.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationLayout.swift @@ -144,7 +144,8 @@ public func layoutAuthorizationItems(bounds: CGRect, items: [AuthorizationLayout } var verticalOrigin: CGFloat = bounds.minY + floor((bounds.size.height - totalHeight) / 2.0) - for item in solvedItems { + for i in 0 ..< solvedItems.count { + let item = solvedItems[i] verticalOrigin += item.spacingBefore! transition.updateFrame(node: item.item.node, frame: CGRect(origin: CGPoint(x: floor((bounds.size.width - item.item.size.width) / 2.0), y: verticalOrigin), size: item.item.size)) verticalOrigin += item.item.size.height diff --git a/submodules/AuthorizationUI/Sources/AuthorizationOptionText.swift b/submodules/AuthorizationUI/Sources/AuthorizationOptionText.swift index 1b9df36546..a3c8f2f70b 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationOptionText.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationOptionText.swift @@ -14,7 +14,11 @@ public func authorizationCurrentOptionText(_ type: SentAuthorizationCodeType, st let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: primaryColor) let bold = MarkdownAttributeSet(font: Font.semibold(16.0), textColor: primaryColor) return parseMarkdownIntoAttributedString(strings.Login_CodeSentInternal, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil }), textAlignment: .center) - case .call, .flashCall, .missedCall: + case .missedCall: + let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: primaryColor) + let bold = MarkdownAttributeSet(font: Font.semibold(16.0), textColor: primaryColor) + return parseMarkdownIntoAttributedString("Within a few seconds you should\nreceive a short call from:", attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil }), textAlignment: .center) + case .call, .flashCall: return NSAttributedString(string: strings.ChangePhoneNumberCode_Called, font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center) } } diff --git a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift index 94c3b9431e..22b76c3ea9 100644 --- a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift +++ b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift @@ -909,7 +909,7 @@ private final class MonthComponent: CombinedComponent { if case .none = transition.animation { return } - let delay = Double(delayIndex) * 0.1 + let delay = Double(min(delayIndex, 6)) * 0.1 view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.05, delay: delay) view.layer.animateFrame(from: CGRect(origin: view.frame.origin, size: CGSize(width: leftRadius, height: view.frame.height)), to: view.frame, duration: 0.25, delay: delay, timingFunction: kCAMediaTimingFunctionSpring) }) diff --git a/submodules/CodeInputView/BUILD b/submodules/CodeInputView/BUILD new file mode 100644 index 0000000000..072955c32d --- /dev/null +++ b/submodules/CodeInputView/BUILD @@ -0,0 +1,21 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "CodeInputView", + module_name = "CodeInputView", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/PhoneNumberFormat:PhoneNumberFormat", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/CodeInputView/Sources/CodeInputView.swift b/submodules/CodeInputView/Sources/CodeInputView.swift new file mode 100644 index 0000000000..e8fe0e5571 --- /dev/null +++ b/submodules/CodeInputView/Sources/CodeInputView.swift @@ -0,0 +1,293 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import PhoneNumberFormat + +public final class CodeInputView: ASDisplayNode, UITextFieldDelegate { + public struct Theme: Equatable { + public var inactiveBorder: UInt32 + public var activeBorder: UInt32 + public var foreground: UInt32 + public var isDark: Bool + + public init( + inactiveBorder: UInt32, + activeBorder: UInt32, + foreground: UInt32, + isDark: Bool + ) { + self.inactiveBorder = inactiveBorder + self.activeBorder = activeBorder + self.foreground = foreground + self.isDark = isDark + } + } + + private final class ItemView: ASDisplayNode { + private let backgroundView: UIImageView + private let textNode: ImmediateTextNode + + private var borderColorValue: UInt32? + + private var text: String = "" + + override init() { + self.backgroundView = UIImageView() + self.textNode = ImmediateTextNode() + + super.init() + + self.addSubnode(self.textNode) + self.view.addSubview(self.backgroundView) + + self.clipsToBounds = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(borderColor: UInt32) { + if self.borderColorValue != borderColor { + self.borderColorValue = borderColor + + self.backgroundView.image = generateStretchableFilledCircleImage(diameter: 10.0, color: nil, strokeColor: UIColor(argb: borderColor), strokeWidth: 1.0, backgroundColor: nil) + } + } + + func update(textColor: UInt32, text: String, size: CGSize, animated: Bool) { + let previousText = self.text + self.text = text + + if animated && previousText.isEmpty != text.isEmpty { + if !text.isEmpty { + self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + self.textNode.layer.animatePosition(from: CGPoint(x: 0.0, y: size.height / 2.0), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + } else { + if let copyView = self.textNode.view.snapshotContentTree() { + self.view.insertSubview(copyView, at: 0) + copyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak copyView] _ in + copyView?.removeFromSuperview() + }) + copyView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: size.height / 2.0), duration: 0.2, removeOnCompletion: false, additive: true) + } + } + } + + self.textNode.attributedText = NSAttributedString(string: text, font: Font.monospace(21.0), textColor: UIColor(argb: textColor)) + let textSize = self.textNode.updateLayout(size) + self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) + + self.backgroundView.frame = CGRect(origin: CGPoint(), size: size) + } + } + + private let prefixLabel: ImmediateTextNode + private let textField: UITextField + + private var focusIndex: Int? + private var itemViews: [ItemView] = [] + + public var updated: (() -> Void)? + + private var theme: Theme? + private var count: Int? + + private var textValue: String = "" + public var text: String { + get { + return self.textValue + } set(value) { + self.textValue = value + self.textField.text = value + } + } + + override public init() { + self.prefixLabel = ImmediateTextNode() + self.textField = UITextField() + + if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { + self.textField.keyboardType = .asciiCapableNumberPad + } else { + self.textField.keyboardType = .numberPad + } + if #available(iOSApplicationExtension 12.0, iOS 12.0, *) { + self.textField.textContentType = .oneTimeCode + } + self.textField.returnKeyType = .done + self.textField.disableAutomaticKeyboardHandling = [.forward, .backward] + + super.init() + + self.addSubnode(self.prefixLabel) + self.view.addSubview(self.textField) + + self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) + self.textField.delegate = self + self.textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged) + } + + required public init?(coder: NSCoder) { + preconditionFailure() + } + + @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.textField.becomeFirstResponder() + } + } + + @objc func textFieldChanged(_ textField: UITextField) { + self.textValue = textField.text ?? "" + self.updateItemViews(animated: true) + self.updated?() + } + + public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + guard let count = self.count else { + return false + } + var text = textField.text ?? "" + guard let stringRange = Range(range, in: text) else { + return false + } + text.replaceSubrange(stringRange, with: string) + + if !text.allSatisfy({ $0.isNumber && $0.isASCII }) { + return false + } + + if text.count > count { + return false + } + + return true + } + + private func currentCaretIndex() -> Int? { + if let selectedTextRange = self.textField.selectedTextRange { + let index = self.textField.offset(from: self.textField.beginningOfDocument, to: selectedTextRange.end) + return index + } else { + return nil + } + } + + public func textFieldDidBeginEditing(_ textField: UITextField) { + self.focusIndex = self.currentCaretIndex() + self.updateItemViews(animated: true) + } + + public func textFieldDidEndEditing(_ textField: UITextField) { + self.focusIndex = nil + self.updateItemViews(animated: true) + } + + public func textFieldDidChangeSelection(_ textField: UITextField) { + self.focusIndex = self.currentCaretIndex() + self.updateItemViews(animated: true) + } + + public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + return false + } + + private func updateItemViews(animated: Bool) { + guard let theme = self.theme else { + return + } + + for i in 0 ..< self.itemViews.count { + let itemView = self.itemViews[i] + let itemSize = itemView.bounds.size + + itemView.update(borderColor: self.focusIndex == i ? theme.activeBorder : theme.inactiveBorder) + let itemText: String + if i < self.textValue.count { + itemText = String(self.textValue[self.textValue.index(self.textValue.startIndex, offsetBy: i)]) + } else { + itemText = "" + } + itemView.update(textColor: theme.foreground, text: itemText, size: itemSize, animated: animated) + } + } + + public func update(theme: Theme, prefix: String, count: Int, width: CGFloat) -> CGSize { + self.theme = theme + self.count = count + + if theme.isDark { + self.textField.keyboardAppearance = .dark + } else { + self.textField.keyboardAppearance = .light + } + + let height: CGFloat = 28.0 + self.prefixLabel.attributedText = NSAttributedString(string: prefix, font: Font.monospace(21.0), textColor: UIColor(argb: theme.foreground)) + let prefixSize = self.prefixLabel.updateLayout(CGSize(width: width, height: 100.0)) + let prefixSpacing: CGFloat = prefix.isEmpty ? 0.0 : 8.0 + + let itemSize = CGSize(width: 25.0, height: height) + let itemSpacing: CGFloat = 5.0 + let itemsWidth: CGFloat = itemSize.width * CGFloat(count) + itemSpacing * CGFloat(count - 1) + + let contentWidth: CGFloat = prefixSize.width + prefixSpacing + itemsWidth + let contentOriginX: CGFloat = floor((width - contentWidth) / 2.0) + + self.prefixLabel.frame = CGRect(origin: CGPoint(x: contentOriginX, y: floorToScreenPixels((height - prefixSize.height) / 2.0)), size: prefixSize) + + for i in 0 ..< count { + let itemView: ItemView + if self.itemViews.count > i { + itemView = self.itemViews[i] + } else { + itemView = ItemView() + self.itemViews.append(itemView) + self.addSubnode(itemView) + } + itemView.update(borderColor: self.focusIndex == i ? theme.activeBorder : theme.inactiveBorder) + let itemText: String + if i < self.textValue.count { + itemText = String(self.textValue[self.textValue.index(self.textValue.startIndex, offsetBy: i)]) + } else { + itemText = "" + } + itemView.update(textColor: theme.foreground, text: itemText, size: itemSize, animated: false) + itemView.frame = CGRect(origin: CGPoint(x: contentOriginX + prefixSize.width + prefixSpacing + CGFloat(i) * (itemSize.width + itemSpacing), y: 0.0), size: itemSize) + } + if self.itemViews.count > count { + for i in count ..< self.itemViews.count { + self.itemViews[i].removeFromSupernode() + } + self.itemViews.removeSubrange(count...) + } + + return CGSize(width: width, height: height) + } + + public override func becomeFirstResponder() -> Bool { + return self.textField.becomeFirstResponder() + } + + public override func canBecomeFirstResponder() -> Bool { + return self.textField.canBecomeFirstResponder + } + + public override func resignFirstResponder() -> Bool { + return self.textField.resignFirstResponder() + } + + public override func canResignFirstResponder() -> Bool { + return self.textField.canResignFirstResponder + } + + public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.bounds.contains(point) { + return self.view + } else { + return nil + } + } +} diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 8df71aae5d..786909a8c6 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -148,7 +148,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1802240206] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedFile($0) } dict[289586518] = { return Api.SavedContact.parse_savedPhoneContact($0) } dict[1571494644] = { return Api.ExportedMessageLink.parse_exportedMessageLink($0) } - dict[-855308010] = { return Api.auth.Authorization.parse_authorization($0) } + dict[872119224] = { return Api.auth.Authorization.parse_authorization($0) } dict[1148485274] = { return Api.auth.Authorization.parse_authorizationSignUpRequired($0) } dict[-181407105] = { return Api.InputFile.parse_inputFile($0) } dict[-95482955] = { return Api.InputFile.parse_inputFileBig($0) } diff --git a/submodules/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index 1adbf6e282..0ca13fb397 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -1037,16 +1037,17 @@ public struct auth { } public enum Authorization: TypeConstructorDescription { - case authorization(flags: Int32, tmpSessions: Int32?, user: Api.User) + case authorization(flags: Int32, otherwiseReloginDays: Int32?, tmpSessions: Int32?, user: Api.User) case authorizationSignUpRequired(flags: Int32, termsOfService: Api.help.TermsOfService?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .authorization(let flags, let tmpSessions, let user): + case .authorization(let flags, let otherwiseReloginDays, let tmpSessions, let user): if boxed { - buffer.appendInt32(-855308010) + buffer.appendInt32(872119224) } serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(otherwiseReloginDays!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeInt32(tmpSessions!, buffer: buffer, boxed: false)} user.serialize(buffer, true) break @@ -1062,8 +1063,8 @@ public struct auth { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .authorization(let flags, let tmpSessions, let user): - return ("authorization", [("flags", flags), ("tmpSessions", tmpSessions), ("user", user)]) + case .authorization(let flags, let otherwiseReloginDays, let tmpSessions, let user): + return ("authorization", [("flags", flags), ("otherwiseReloginDays", otherwiseReloginDays), ("tmpSessions", tmpSessions), ("user", user)]) case .authorizationSignUpRequired(let flags, let termsOfService): return ("authorizationSignUpRequired", [("flags", flags), ("termsOfService", termsOfService)]) } @@ -1073,16 +1074,19 @@ public struct auth { var _1: Int32? _1 = reader.readInt32() var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } - var _3: Api.User? + if Int(_1!) & Int(1 << 1) != 0 {_2 = reader.readInt32() } + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + var _4: Api.User? if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.User + _4 = Api.parse(reader, signature: signature) as? Api.User } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.auth.Authorization.authorization(flags: _1!, tmpSessions: _2, user: _3!) + let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.auth.Authorization.authorization(flags: _1!, otherwiseReloginDays: _2, tmpSessions: _3, user: _4!) } else { return nil diff --git a/submodules/TelegramCore/Sources/Account/AccountManager.swift b/submodules/TelegramCore/Sources/Account/AccountManager.swift index 1db3b79e63..6cdd30fefc 100644 --- a/submodules/TelegramCore/Sources/Account/AccountManager.swift +++ b/submodules/TelegramCore/Sources/Account/AccountManager.swift @@ -429,7 +429,25 @@ private func cleanupAccount(networkArguments: NetworkInitializationArguments, ac |> `catch` { _ -> Signal in return .single(nil) } - |> mapToSignal { _ -> Signal in + |> mapToSignal { result -> Signal in + let _ = (accountManager.transaction { transaction -> Void in + var tokens = transaction.getStoredLoginTokens() + switch result { + case let .loggedOut(_, futureAuthToken, futureAuthExpires): + if let futureAuthToken = futureAuthToken { + tokens.insert(futureAuthToken.makeData(), at: 0) + } + let _ = futureAuthExpires + default: + break + } + + if tokens.count > 20 { + tokens.removeLast(tokens.count - 20) + } + + transaction.setStoredLoginTokens(tokens) + }).start() account.shouldBeServiceTaskMaster.set(.single(.never)) return accountManager.transaction { transaction -> Void in transaction.updateRecord(id, { _ in diff --git a/submodules/TelegramCore/Sources/AccountManager/AccountManagerImpl.swift b/submodules/TelegramCore/Sources/AccountManager/AccountManagerImpl.swift index 33922021d2..ae63354bc5 100644 --- a/submodules/TelegramCore/Sources/AccountManager/AccountManagerImpl.swift +++ b/submodules/TelegramCore/Sources/AccountManager/AccountManagerImpl.swift @@ -24,12 +24,15 @@ public struct AccountManagerModifier { public let getNotice: (NoticeEntryKey) -> CodableEntry? public let setNotice: (NoticeEntryKey, CodableEntry?) -> Void public let clearNotices: () -> Void + public let getStoredLoginTokens: () -> [Data] + public let setStoredLoginTokens: ([Data]) -> Void } final class AccountManagerImpl { private let queue: Queue private let basePath: String private let atomicStatePath: String + private let loginTokensPath: String private let temporarySessionId: Int64 private let guardValueBox: ValueBox? private let valueBox: ValueBox @@ -76,6 +79,7 @@ final class AccountManagerImpl { self.queue = queue self.basePath = basePath self.atomicStatePath = "\(basePath)/atomic-state" + self.loginTokensPath = "\(basePath)/login-tokens" self.temporarySessionId = temporarySessionId let _ = try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true, attributes: nil) guard let guardValueBox = SqliteValueBox(basePath: basePath + "/guard_db", queue: queue, isTemporary: isTemporary, isReadOnly: false, useCaches: useCaches, encryptionParameters: nil, upgradeProgress: { _ in }) else { @@ -209,6 +213,10 @@ final class AccountManagerImpl { self.currentUpdatedNoticeEntryKeys.insert(key) }, clearNotices: { self.noticeTable.clear() + }, getStoredLoginTokens: { + return self.getLoginTokens() + }, setStoredLoginTokens: { list in + self.setLoginTokens(list: list) }) let result = f(transaction) @@ -243,6 +251,23 @@ final class AccountManagerImpl { } } + private func getLoginTokens() -> [Data] { + guard let data = try? Data(contentsOf: URL(fileURLWithPath: self.loginTokensPath)) else { + return [] + } + guard let list = try? JSONDecoder().decode([Data].self, from: data) else { + return [] + } + return list + } + + private func setLoginTokens(list: [Data]) { + if let data = try? JSONEncoder().encode(list) { + if let _ = try? data.write(to: URL(fileURLWithPath: self.loginTokensPath), options: [.atomic]) { + } + } + } + private func beforeCommit() { if self.currentAtomicStateUpdated { self.syncAtomicStateToFile() diff --git a/submodules/TelegramCore/Sources/Authorization.swift b/submodules/TelegramCore/Sources/Authorization.swift index eef1d711d1..0545b3dc20 100644 --- a/submodules/TelegramCore/Sources/Authorization.swift +++ b/submodules/TelegramCore/Sources/Authorization.swift @@ -72,61 +72,99 @@ private func ~=(pattern: Regex, matchable: T) -> } public func sendAuthorizationCode(accountManager: AccountManager, account: UnauthorizedAccount, phoneNumber: String, apiId: Int32, apiHash: String, syncContacts: Bool) -> Signal { - var flags: Int32 = 0 - flags |= 1 << 5 //allowMissedCall - let sendCode = Api.functions.auth.sendCode(phoneNumber: phoneNumber, apiId: apiId, apiHash: apiHash, settings: .codeSettings(flags: flags, logoutTokens: nil)) - - let codeAndAccount = account.network.request(sendCode, automaticFloodWait: false) - |> map { result in - return (result, account) + return accountManager.transaction { transaction -> [Data] in + return transaction.getStoredLoginTokens() } - |> `catch` { error -> Signal<(Api.auth.SentCode, UnauthorizedAccount), MTRpcError> in - switch MatchString(error.errorDescription ?? "") { - case Regex("(PHONE_|USER_|NETWORK_)MIGRATE_(\\d+)"): - let range = error.errorDescription.range(of: "MIGRATE_")! - let updatedMasterDatacenterId = Int32(error.errorDescription[range.upperBound ..< error.errorDescription.endIndex])! - let updatedAccount = account.changedMasterDatacenterId(accountManager: accountManager, masterDatacenterId: updatedMasterDatacenterId) - return updatedAccount - |> mapToSignalPromotingError { updatedAccount -> Signal<(Api.auth.SentCode, UnauthorizedAccount), MTRpcError> in - return updatedAccount.network.request(sendCode, automaticFloodWait: false) - |> map { sentCode in - return (sentCode, updatedAccount) + |> castError(AuthorizationCodeRequestError.self) + |> mapToSignal { authTokens -> Signal in + var flags: Int32 = 0 + flags |= 1 << 5 //allowMissedCall + flags |= 1 << 6 //tokens + let sendCode = Api.functions.auth.sendCode(phoneNumber: phoneNumber, apiId: apiId, apiHash: apiHash, settings: .codeSettings(flags: flags, logoutTokens: authTokens.map { Buffer(data: $0) })) + + enum SendCodeResult { + case password(hint: String?) + case sentCode(Api.auth.SentCode) + } + + let codeAndAccount = account.network.request(sendCode, automaticFloodWait: false) + |> map { result -> (SendCodeResult, UnauthorizedAccount) in + return (.sentCode(result), account) + } + |> `catch` { error -> Signal<(SendCodeResult, UnauthorizedAccount), MTRpcError> in + switch MatchString(error.errorDescription ?? "") { + case Regex("(PHONE_|USER_|NETWORK_)MIGRATE_(\\d+)"): + let range = error.errorDescription.range(of: "MIGRATE_")! + let updatedMasterDatacenterId = Int32(error.errorDescription[range.upperBound ..< error.errorDescription.endIndex])! + let updatedAccount = account.changedMasterDatacenterId(accountManager: accountManager, masterDatacenterId: updatedMasterDatacenterId) + return updatedAccount + |> mapToSignalPromotingError { updatedAccount -> Signal<(SendCodeResult, UnauthorizedAccount), MTRpcError> in + return updatedAccount.network.request(sendCode, automaticFloodWait: false) + |> map { sentCode in + return (.sentCode(sentCode), updatedAccount) + } + } + case _: + return .fail(error) + } + } + |> `catch` { error -> Signal<(SendCodeResult, UnauthorizedAccount), AuthorizationCodeRequestError> in + if error.errorDescription.hasPrefix("FLOOD_WAIT") { + return .fail(.limitExceeded) + } else if error.errorDescription == "PHONE_NUMBER_INVALID" { + return .fail(.invalidPhoneNumber) + } else if error.errorDescription == "PHONE_NUMBER_FLOOD" { + return .fail(.phoneLimitExceeded) + } else if error.errorDescription == "PHONE_NUMBER_BANNED" { + return .fail(.phoneBanned) + } else if error.errorDescription == "SESSION_PASSWORD_NEEDED" { + return account.network.request(Api.functions.account.getPassword(), automaticFloodWait: false) + |> mapError { error -> AuthorizationCodeRequestError in + if error.errorDescription.hasPrefix("FLOOD_WAIT") { + return .limitExceeded + } else { + return .generic(info: (Int(error.errorCode), error.errorDescription)) } } - case _: - return .fail(error) - } - } - |> mapError { error -> AuthorizationCodeRequestError in - if error.errorDescription.hasPrefix("FLOOD_WAIT") { - return .limitExceeded - } else if error.errorDescription == "PHONE_NUMBER_INVALID" { - return .invalidPhoneNumber - } else if error.errorDescription == "PHONE_NUMBER_FLOOD" { - return .phoneLimitExceeded - } else if error.errorDescription == "PHONE_NUMBER_BANNED" { - return .phoneBanned - } else { - return .generic(info: (Int(error.errorCode), error.errorDescription)) - } - } - |> timeout(20.0, queue: Queue.concurrentDefaultQueue(), alternate: .fail(.timeout)) - - return codeAndAccount - |> mapToSignal { sentCode, account -> Signal in - return account.postbox.transaction { transaction -> UnauthorizedAccount in - switch sentCode { - case let .sentCode(_, type, phoneCodeHash, nextType, timeout): - var parsedNextType: AuthorizationCodeNextType? - if let nextType = nextType { - parsedNextType = AuthorizationCodeNextType(apiType: nextType) + |> mapToSignal { result -> Signal<(SendCodeResult, UnauthorizedAccount), AuthorizationCodeRequestError> in + switch result { + case let .password(_, _, _, _, hint, _, _, _, _, _): + return .single((.password(hint: hint), account)) } - - transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType, syncContacts: syncContacts))) + } + } else { + return .fail(.generic(info: (Int(error.errorCode), error.errorDescription))) } - return account } - |> mapError { _ -> AuthorizationCodeRequestError in + |> timeout(20.0, queue: Queue.concurrentDefaultQueue(), alternate: .fail(.timeout)) + + return codeAndAccount + |> mapToSignal { result, account -> Signal in + return account.postbox.transaction { transaction -> UnauthorizedAccount in + switch result { + case let .password(hint): + transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .passwordEntry(hint: hint ?? "", number: nil, code: nil, suggestReset: false, syncContacts: syncContacts))) + case let .sentCode(sentCode): + switch sentCode { + case let .sentCode(_, type, phoneCodeHash, nextType, timeout): + /*#if DEBUG + var type = type + type = .sentCodeTypeMissedCall(prefix: "+44 1234 8", length: 5) + var nextType = nextType + nextType = nil + #endif*/ + var parsedNextType: AuthorizationCodeNextType? + if let nextType = nextType { + parsedNextType = AuthorizationCodeNextType(apiType: nextType) + } + + transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType, syncContacts: syncContacts))) + } + } + return account + } + |> mapError { _ -> AuthorizationCodeRequestError in + } } } } @@ -257,7 +295,7 @@ public func authorizeWithCode(accountManager: AccountManager mapToSignal { result -> Signal in return account.postbox.transaction { transaction -> Signal in switch result { - case let .authorization(_, _, user): + case let .authorization(_, _, _, user): let user = TelegramUser(user: user) let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil) /*transaction.updatePeersInternal([user], update: { current, peer -> Peer? in @@ -380,7 +418,7 @@ public final class RecoveredAccountData { public func loginWithRecoveredAccountData(accountManager: AccountManager, account: UnauthorizedAccount, recoveredAccountData: RecoveredAccountData, syncContacts: Bool) -> Signal { return account.postbox.transaction { transaction -> Signal in switch recoveredAccountData.authorization { - case let .authorization(_, _, user): + case let .authorization(_, _, _, user): let user = TelegramUser(user: user) let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil) @@ -516,7 +554,7 @@ public func signUpWithName(accountManager: AccountManager mapToSignal { result -> Signal in switch result { - case let .authorization(_, _, user): + case let .authorization(_, _, _, user): let user = TelegramUser(user: user) let appliedState = account.postbox.transaction { transaction -> Void in let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Auth/AuthTransfer.swift b/submodules/TelegramCore/Sources/TelegramEngine/Auth/AuthTransfer.swift index f12c3b4d16..1b2b6751f8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Auth/AuthTransfer.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Auth/AuthTransfer.swift @@ -92,7 +92,7 @@ func _internal_exportAuthTransferToken(accountManager: AccountManager Signal in let user = TelegramUser(user: user) let state = AuthorizedAccountState(isTestingEnvironment: updatedAccount.testingEnvironment, masterDatacenterId: updatedAccount.masterDatacenterId, peerId: user.id, state: nil) @@ -116,7 +116,7 @@ func _internal_exportAuthTransferToken(accountManager: AccountManager Signal in let user = TelegramUser(user: user) let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil) diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 6c303a3abe..9514e8bea8 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -246,6 +246,7 @@ swift_library( "//submodules/LottieMeshSwift:LottieMeshSwift", "//submodules/MeshAnimationCache:MeshAnimationCache", "//submodules/DirectMediaImageCache:DirectMediaImageCache", + "//submodules/CodeInputView:CodeInputView", ] + select({ "@build_bazel_rules_apple//apple:ios_armv7": [], "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, diff --git a/submodules/TelegramUI/Sources/AuthorizationSequenceCodeEntryController.swift b/submodules/TelegramUI/Sources/AuthorizationSequenceCodeEntryController.swift index cc3979a465..30c35f2231 100644 --- a/submodules/TelegramUI/Sources/AuthorizationSequenceCodeEntryController.swift +++ b/submodules/TelegramUI/Sources/AuthorizationSequenceCodeEntryController.swift @@ -102,9 +102,10 @@ final class AuthorizationSequenceCodeEntryController: ViewController { func updateData(number: String, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?) { self.termsOfService = termsOfService if self.data?.0 != number || self.data?.1 != codeType || self.data?.2 != nextType || self.data?.3 != timeout { - if case .otherSession = codeType { + switch codeType { + case .otherSession, .missedCall: self.title = number - } else { + default: self.title = nil } self.data = (number, codeType, nextType, timeout) diff --git a/submodules/TelegramUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift b/submodules/TelegramUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift index 4f59fe7b92..e6afb5e742 100644 --- a/submodules/TelegramUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift +++ b/submodules/TelegramUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift @@ -7,6 +7,7 @@ import SwiftSignalKit import TelegramPresentationData import TextFormat import AuthorizationUI +import CodeInputView final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextFieldDelegate { private let strings: PresentationStrings @@ -15,10 +16,12 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF private let titleNode: ImmediateTextNode private let titleIconNode: ASImageNode private let currentOptionNode: ASTextNode + private let currentOptionInfoNode: ASTextNode private let nextOptionNode: HighlightableButtonNode - private let codeField: TextFieldNode - private let codeSeparatorNode: ASDisplayNode + private let codeInputView: CodeInputView + //private let codeField: TextFieldNode + //private let codeSeparatorNode: ASDisplayNode private var codeType: SentAuthorizationCodeType? @@ -38,7 +41,8 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF } var currentCode: String { - return self.codeField.textField.text ?? "" + return self.codeInputView.text + //return self.codeField.textField.text ?? "" } var loginWithCode: ((String) -> Void)? @@ -48,7 +52,8 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF var inProgress: Bool = false { didSet { - self.codeField.alpha = self.inProgress ? 0.6 : 1.0 + self.codeInputView.alpha = self.inProgress ? 0.6 : 1.0 + //self.codeField.alpha = self.inProgress ? 0.6 : 1.0 } } @@ -66,46 +71,28 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF self.titleIconNode.isLayerBacked = true self.titleIconNode.displayWithoutProcessing = true self.titleIconNode.displaysAsynchronously = false - self.titleIconNode.image = generateImage(CGSize(width: 81.0, height: 52.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - context.setFillColor(theme.list.itemPrimaryTextColor.cgColor) - context.setStrokeColor(theme.list.itemPrimaryTextColor.cgColor) - context.setLineWidth(2.97) - let _ = try? drawSvgPath(context, path: "M9.87179487,9.04664384 C9.05602951,9.04664384 8.39525641,9.70682916 8.39525641,10.5205479 L8.39525641,44.0547945 C8.39525641,44.8685133 9.05602951,45.5286986 9.87179487,45.5286986 L65.1538462,45.5286986 C65.9696115,45.5286986 66.6303846,44.8685133 66.6303846,44.0547945 L66.6303846,10.5205479 C66.6303846,9.70682916 65.9696115,9.04664384 65.1538462,9.04664384 L9.87179487,9.04664384 S ") - - let _ = try? drawSvgPath(context, path: "M0,44.0547945 L75.025641,44.0547945 C75.025641,45.2017789 74.2153348,46.1893143 73.0896228,46.4142565 L66.1123641,47.8084669 C65.4749109,47.9358442 64.8264231,48 64.1763458,48 L10.8492952,48 C10.1992179,48 9.55073017,47.9358442 8.91327694,47.8084669 L1.93601826,46.4142565 C0.810306176,46.1893143 0,45.2017789 0,44.0547945 Z ") - - let _ = try? drawSvgPath(context, path: "M2.96153846,16.4383562 L14.1495726,16.4383562 C15.7851852,16.4383562 17.1111111,17.7631027 17.1111111,19.3972603 L17.1111111,45.0410959 C17.1111111,46.6752535 15.7851852,48 14.1495726,48 L2.96153846,48 C1.32592593,48 0,46.6752535 0,45.0410959 L0,19.3972603 C0,17.7631027 1.32592593,16.4383562 2.96153846,16.4383562 Z ") - - context.setStrokeColor(theme.list.plainBackgroundColor.cgColor) - context.setLineWidth(1.65) - let _ = try? drawSvgPath(context, path: "M2.96153846,15.6133562 L14.1495726,15.6133562 C16.2406558,15.6133562 17.9361111,17.3073033 17.9361111,19.3972603 L17.9361111,45.0410959 C17.9361111,47.1310529 16.2406558,48.825 14.1495726,48.825 L2.96153846,48.825 C0.870455286,48.825 -0.825,47.1310529 -0.825,45.0410959 L-0.825,19.3972603 C-0.825,17.3073033 0.870455286,15.6133562 2.96153846,15.6133562 S ") - - context.setFillColor(theme.list.plainBackgroundColor.cgColor) - let _ = try? drawSvgPath(context, path: "M1.64529915,20.3835616 L15.465812,20.3835616 L15.465812,44.0547945 L1.64529915,44.0547945 Z ") - - context.setFillColor(theme.list.itemAccentColor.cgColor) - let _ = try? drawSvgPath(context, path: "M66.4700855,0.0285884455 C60.7084674,0.0285884455 55.9687848,4.08259697 55.9687848,9.14830256 C55.9687848,12.0875991 57.5993165,14.6795278 60.0605723,16.3382966 C60.0568181,16.4358994 60.0611217,16.5884309 59.9318097,17.067302 C59.7721478,17.6586615 59.4575977,18.4958519 58.8015608,19.4258487 L58.3294314,20.083383 L59.1449275,20.0976772 C61.9723538,20.1099725 63.6110772,18.2528913 63.8662207,17.9535438 C64.7014993,18.1388449 65.5698144,18.2680167 66.4700855,18.2680167 C72.2312622,18.2680167 76.9713861,14.2140351 76.9713861,9.14830256 C76.9713861,4.08256999 72.2312622,0.0285884455 66.4700855,0.0285884455 Z ") - - let _ = try? drawSvgPath(context, path: "M64.1551769,18.856071 C63.8258967,19.1859287 63.4214479,19.5187 62.9094963,19.840779 C61.8188563,20.5269227 60.5584776,20.9288319 59.1304689,20.9225505 L56.7413094,20.8806727 L57.6592902,19.6022014 L58.127415,18.9502938 C58.6361919,18.2290526 58.9525079,17.5293964 59.1353377,16.8522267 C59.1487516,16.8025521 59.1603548,16.7584153 59.1703974,16.7187893 C56.653362,14.849536 55.1437848,12.1128655 55.1437848,9.14830256 C55.1437848,3.61947515 60.2526259,-0.796411554 66.4700855,-0.796411554 C72.6872626,-0.796411554 77.7963861,3.61958236 77.7963861,9.14830256 C77.7963861,14.6770228 72.6872626,19.0930167 66.4700855,19.0930167 C65.7185957,19.0930167 64.9627196,19.0118067 64.1551769,18.856071 S ") - }) self.currentOptionNode = ASTextNode() self.currentOptionNode.isUserInteractionEnabled = false self.currentOptionNode.displaysAsynchronously = false + self.currentOptionInfoNode = ASTextNode() + self.currentOptionInfoNode.isUserInteractionEnabled = false + self.currentOptionInfoNode.displaysAsynchronously = false + self.nextOptionNode = HighlightableButtonNode() self.nextOptionNode.displaysAsynchronously = false let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: .sms(length: 5), nextType: .call, timeout: 60, strings: self.strings, primaryColor: self.theme.list.itemPrimaryTextColor, accentColor: self.theme.list.itemAccentColor) self.nextOptionNode.setAttributedTitle(nextOptionText, for: []) self.nextOptionNode.isUserInteractionEnabled = nextOptionActive - self.codeSeparatorNode = ASDisplayNode() + /*self.codeSeparatorNode = ASDisplayNode() self.codeSeparatorNode.isLayerBacked = true - self.codeSeparatorNode.backgroundColor = self.theme.list.itemPlainSeparatorColor + self.codeSeparatorNode.backgroundColor = self.theme.list.itemPlainSeparatorColor*/ - self.codeField = TextFieldNode() + self.codeInputView = CodeInputView() + + /*self.codeField = TextFieldNode() self.codeField.textField.font = Font.regular(24.0) self.codeField.textField.textAlignment = .center if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { @@ -113,16 +100,14 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF } else { self.codeField.textField.keyboardType = .numberPad } - #if swift(>=4.2) if #available(iOSApplicationExtension 12.0, iOS 12.0, *) { self.codeField.textField.textContentType = .oneTimeCode } - #endif self.codeField.textField.returnKeyType = .done self.codeField.textField.textColor = self.theme.list.itemPrimaryTextColor self.codeField.textField.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance self.codeField.textField.disableAutomaticKeyboardHandling = [.forward, .backward] - self.codeField.textField.tintColor = self.theme.list.itemAccentColor + self.codeField.textField.tintColor = self.theme.list.itemAccentColor*/ super.init() @@ -132,18 +117,26 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF self.backgroundColor = self.theme.list.plainBackgroundColor - self.addSubnode(self.codeSeparatorNode) - self.addSubnode(self.codeField) + self.addSubnode(self.codeInputView) + //self.addSubnode(self.codeSeparatorNode) + //self.addSubnode(self.codeField) self.addSubnode(self.titleNode) self.addSubnode(self.titleIconNode) self.addSubnode(self.currentOptionNode) + self.addSubnode(self.currentOptionInfoNode) self.addSubnode(self.nextOptionNode) - self.codeField.textField.delegate = self - self.codeField.textField.addTarget(self, action: #selector(self.codeFieldTextChanged(_:)), for: .editingChanged) + self.codeInputView.updated = { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.textChanged(text: strongSelf.codeInputView.text) + } - self.codeField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_Code, font: Font.regular(24.0), textColor: self.theme.list.itemPlaceholderTextColor - ) + //self.codeField.textField.delegate = self + //self.codeField.textField.addTarget(self, action: #selector(self.codeFieldTextChanged(_:)), for: .editingChanged) + + //self.codeField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_Code, font: Font.regular(24.0), textColor: self.theme.list.itemPlaceholderTextColor) self.nextOptionNode.addTarget(self, action: #selector(self.nextOptionNodePressed), forControlEvents: .touchUpInside) } @@ -153,8 +146,10 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF } func updateCode(_ code: String) { - self.codeField.textField.text = code - self.codeFieldTextChanged(self.codeField.textField) + self.codeInputView.text = code + self.textChanged(text: code) + //self.codeField.textField.text = code + //self.codeFieldTextChanged(self.codeField.textField) if let codeType = self.codeType { var codeLength: Int32? switch codeType { @@ -162,6 +157,8 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF codeLength = length case let .otherSession(length): codeLength = length + case let .missedCall(_, length): + codeLength = length case let .sms(length): codeLength = length default: @@ -178,6 +175,12 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF self.phoneNumber = number self.currentOptionNode.attributedText = authorizationCurrentOptionText(codeType, strings: self.strings, primaryColor: self.theme.list.itemPrimaryTextColor, accentColor: self.theme.list.itemAccentColor) + if case .missedCall = codeType { + //TODO:localize + self.currentOptionInfoNode.attributedText = NSAttributedString(string: "Please enter the last five digits\nof the missed call number.", font: Font.regular(16.0), textColor: self.theme.list.itemPrimaryTextColor, paragraphAlignment: .center) + } else { + self.currentOptionInfoNode.attributedText = NSAttributedString(string: "", font: Font.regular(15.0), textColor: self.theme.list.itemPrimaryTextColor) + } if let timeout = timeout { self.currentTimeoutTime = timeout let disposable = ((Signal.single(1) |> delay(1.0, queue: Queue.mainQueue())) |> restart).start(next: { [weak self] _ in @@ -218,20 +221,42 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF } if max(layout.size.width, layout.size.height) > 1023.0 { - if let codeType = self.codeType, case .otherSession = codeType { - self.titleNode.attributedText = NSAttributedString(string: self.strings.Login_CheckOtherSessionMessages, font: Font.medium(32.0), textColor: self.theme.list.itemPrimaryTextColor) + if let codeType = self.codeType { + switch codeType { + case .otherSession: + self.titleNode.attributedText = NSAttributedString(string: self.strings.Login_CheckOtherSessionMessages, font: Font.medium(32.0), textColor: self.theme.list.itemPrimaryTextColor) + case .missedCall: + //TODO:localize + self.titleNode.attributedText = NSAttributedString(string: "Enter the missing digits", font: Font.medium(32.0), textColor: self.theme.list.itemPrimaryTextColor) + default: + self.titleNode.attributedText = NSAttributedString(string: self.phoneNumber, font: Font.light(40.0), textColor: self.theme.list.itemPrimaryTextColor) + } } else { self.titleNode.attributedText = NSAttributedString(string: self.phoneNumber, font: Font.light(40.0), textColor: self.theme.list.itemPrimaryTextColor) } } else { - if let codeType = self.codeType, case .otherSession = codeType { - let fontSize: CGFloat - if layout.size.width > 330.0 { - fontSize = 22.0 - } else { - fontSize = 18.0 + if let codeType = self.codeType { + switch codeType { + case .otherSession: + let fontSize: CGFloat + if layout.size.width > 330.0 { + fontSize = 22.0 + } else { + fontSize = 18.0 + } + self.titleNode.attributedText = NSAttributedString(string: self.strings.Login_CheckOtherSessionMessages, font: Font.semibold(fontSize), textColor: self.theme.list.itemPrimaryTextColor) + case .missedCall: + let fontSize: CGFloat + if layout.size.width > 330.0 { + fontSize = 22.0 + } else { + fontSize = 18.0 + } + //TODO:localize + self.titleNode.attributedText = NSAttributedString(string: "Enter the missing digits", font: Font.semibold(fontSize), textColor: self.theme.list.itemPrimaryTextColor) + default: + self.titleNode.attributedText = NSAttributedString(string: self.phoneNumber, font: Font.light(30.0), textColor: self.theme.list.itemPrimaryTextColor) } - self.titleNode.attributedText = NSAttributedString(string: self.strings.Login_CheckOtherSessionMessages, font: Font.semibold(fontSize), textColor: self.theme.list.itemPrimaryTextColor) } else { self.titleNode.attributedText = NSAttributedString(string: self.phoneNumber, font: Font.light(30.0), textColor: self.theme.list.itemPrimaryTextColor) } @@ -240,24 +265,128 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude)) let currentOptionSize = self.currentOptionNode.measure(CGSize(width: layout.size.width - 28.0, height: CGFloat.greatestFiniteMagnitude)) + let currentOptionInfoSize = self.currentOptionInfoNode.measure(CGSize(width: layout.size.width - 28.0, height: CGFloat.greatestFiniteMagnitude)) let nextOptionSize = self.nextOptionNode.measure(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude)) + let codeLength: Int + var codePrefix: String = "" + switch self.codeType { + case .flashCall: + codeLength = 6 + case let .call(length): + codeLength = Int(length) + case let .otherSession(length): + codeLength = Int(length) + case let .missedCall(prefix, length): + codePrefix = prefix + codeLength = Int(length) + case let .sms(length): + codeLength = Int(length) + case .none: + codeLength = 6 + } + + let codeFieldSize = self.codeInputView.update( + theme: CodeInputView.Theme( + inactiveBorder: self.theme.list.itemPlainSeparatorColor.argb, + activeBorder: self.theme.list.itemAccentColor.argb, + foreground: self.theme.list.itemPrimaryTextColor.argb, + isDark: self.theme.overallDarkAppearance + ), + prefix: codePrefix, + count: codeLength, + width: layout.size.width - 28.0 + ) + var items: [AuthorizationLayoutItem] = [] - if let codeType = self.codeType, case .otherSession = codeType { - self.titleIconNode.isHidden = false - items.append(AuthorizationLayoutItem(node: self.titleIconNode, size: self.titleIconNode.image!.size, spacingBefore: AuthorizationLayoutItemSpacing(weight: 41.0, maxValue: 41.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) - items.append(AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) - items.append(AuthorizationLayoutItem(node: self.currentOptionNode, size: currentOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) - items.append(AuthorizationLayoutItem(node: self.codeField, size: CGSize(width: layout.size.width - 88.0, height: 44.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 40.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) - items.append(AuthorizationLayoutItem(node: self.codeSeparatorNode, size: CGSize(width: layout.size.width - 88.0, height: UIScreenPixel), spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) - - items.append(AuthorizationLayoutItem(node: self.nextOptionNode, size: nextOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 120.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + if let codeType = self.codeType { + switch codeType { + case .otherSession: + self.titleIconNode.isHidden = false + + if self.titleIconNode.image == nil { + self.titleIconNode.image = generateImage(CGSize(width: 81.0, height: 52.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setFillColor(theme.list.itemPrimaryTextColor.cgColor) + context.setStrokeColor(theme.list.itemPrimaryTextColor.cgColor) + context.setLineWidth(2.97) + let _ = try? drawSvgPath(context, path: "M9.87179487,9.04664384 C9.05602951,9.04664384 8.39525641,9.70682916 8.39525641,10.5205479 L8.39525641,44.0547945 C8.39525641,44.8685133 9.05602951,45.5286986 9.87179487,45.5286986 L65.1538462,45.5286986 C65.9696115,45.5286986 66.6303846,44.8685133 66.6303846,44.0547945 L66.6303846,10.5205479 C66.6303846,9.70682916 65.9696115,9.04664384 65.1538462,9.04664384 L9.87179487,9.04664384 S ") + + let _ = try? drawSvgPath(context, path: "M0,44.0547945 L75.025641,44.0547945 C75.025641,45.2017789 74.2153348,46.1893143 73.0896228,46.4142565 L66.1123641,47.8084669 C65.4749109,47.9358442 64.8264231,48 64.1763458,48 L10.8492952,48 C10.1992179,48 9.55073017,47.9358442 8.91327694,47.8084669 L1.93601826,46.4142565 C0.810306176,46.1893143 0,45.2017789 0,44.0547945 Z ") + + let _ = try? drawSvgPath(context, path: "M2.96153846,16.4383562 L14.1495726,16.4383562 C15.7851852,16.4383562 17.1111111,17.7631027 17.1111111,19.3972603 L17.1111111,45.0410959 C17.1111111,46.6752535 15.7851852,48 14.1495726,48 L2.96153846,48 C1.32592593,48 0,46.6752535 0,45.0410959 L0,19.3972603 C0,17.7631027 1.32592593,16.4383562 2.96153846,16.4383562 Z ") + + context.setStrokeColor(theme.list.plainBackgroundColor.cgColor) + context.setLineWidth(1.65) + let _ = try? drawSvgPath(context, path: "M2.96153846,15.6133562 L14.1495726,15.6133562 C16.2406558,15.6133562 17.9361111,17.3073033 17.9361111,19.3972603 L17.9361111,45.0410959 C17.9361111,47.1310529 16.2406558,48.825 14.1495726,48.825 L2.96153846,48.825 C0.870455286,48.825 -0.825,47.1310529 -0.825,45.0410959 L-0.825,19.3972603 C-0.825,17.3073033 0.870455286,15.6133562 2.96153846,15.6133562 S ") + + context.setFillColor(theme.list.plainBackgroundColor.cgColor) + let _ = try? drawSvgPath(context, path: "M1.64529915,20.3835616 L15.465812,20.3835616 L15.465812,44.0547945 L1.64529915,44.0547945 Z ") + + context.setFillColor(theme.list.itemAccentColor.cgColor) + let _ = try? drawSvgPath(context, path: "M66.4700855,0.0285884455 C60.7084674,0.0285884455 55.9687848,4.08259697 55.9687848,9.14830256 C55.9687848,12.0875991 57.5993165,14.6795278 60.0605723,16.3382966 C60.0568181,16.4358994 60.0611217,16.5884309 59.9318097,17.067302 C59.7721478,17.6586615 59.4575977,18.4958519 58.8015608,19.4258487 L58.3294314,20.083383 L59.1449275,20.0976772 C61.9723538,20.1099725 63.6110772,18.2528913 63.8662207,17.9535438 C64.7014993,18.1388449 65.5698144,18.2680167 66.4700855,18.2680167 C72.2312622,18.2680167 76.9713861,14.2140351 76.9713861,9.14830256 C76.9713861,4.08256999 72.2312622,0.0285884455 66.4700855,0.0285884455 Z ") + + let _ = try? drawSvgPath(context, path: "M64.1551769,18.856071 C63.8258967,19.1859287 63.4214479,19.5187 62.9094963,19.840779 C61.8188563,20.5269227 60.5584776,20.9288319 59.1304689,20.9225505 L56.7413094,20.8806727 L57.6592902,19.6022014 L58.127415,18.9502938 C58.6361919,18.2290526 58.9525079,17.5293964 59.1353377,16.8522267 C59.1487516,16.8025521 59.1603548,16.7584153 59.1703974,16.7187893 C56.653362,14.849536 55.1437848,12.1128655 55.1437848,9.14830256 C55.1437848,3.61947515 60.2526259,-0.796411554 66.4700855,-0.796411554 C72.6872626,-0.796411554 77.7963861,3.61958236 77.7963861,9.14830256 C77.7963861,14.6770228 72.6872626,19.0930167 66.4700855,19.0930167 C65.7185957,19.0930167 64.9627196,19.0118067 64.1551769,18.856071 S ") + }) + } + + items.append(AuthorizationLayoutItem(node: self.titleIconNode, size: self.titleIconNode.image!.size, spacingBefore: AuthorizationLayoutItemSpacing(weight: 41.0, maxValue: 41.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + items.append(AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + items.append(AuthorizationLayoutItem(node: self.currentOptionNode, size: currentOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + + items.append(AuthorizationLayoutItem(node: self.codeInputView, size: codeFieldSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 40.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + //items.append(AuthorizationLayoutItem(node: self.codeField, size: CGSize(width: layout.size.width - 88.0, height: 44.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 40.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + //items.append(AuthorizationLayoutItem(node: self.codeSeparatorNode, size: CGSize(width: layout.size.width - 88.0, height: UIScreenPixel), spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + + items.append(AuthorizationLayoutItem(node: self.nextOptionNode, size: nextOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 120.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + case .missedCall: + self.titleIconNode.isHidden = false + + if self.titleIconNode.image == nil { + self.titleIconNode.image = generateImage(CGSize(width: 72.0, height: 72.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setFillColor(theme.list.itemAccentColor.cgColor) + let _ = try? drawSvgPath(context, path: "M42,10.5 C41.1716,10.5 40.5,11.1716 40.5,12 C40.5,12.8284 41.1716,13.5 42,13.5 L51.3787,13.5 L36,28.8787 L19.0607,11.9393 C18.4749,11.3536 17.5251,11.3536 16.9393,11.9393 C16.3536,12.5251 16.3536,13.4749 16.9393,14.0607 L34.9393,32.0607 C35.5251,32.6464 36.4749,32.6464 37.0607,32.0607 L53.5,15.6213 L53.5,25 C53.5,25.8284 54.1716,26.5 55,26.5 C55.8284,26.5 56.5,25.8284 56.5,25 L56.5,12 C56.5,11.1716 55.8284,10.5 55,10.5 L42,10.5 Z ") + + context.setFillColor(theme.list.itemPrimaryTextColor.cgColor) + + let _ = try? drawSvgPath(context, path: "M35.9832,37.4038 C46.3353,37.4066 56.7252,39.7842 62.0325,45.0915 C64.3893,47.4483 65.7444,50.3613 65.6897,53.8677 C65.6717,56.0012 64.9858,57.8376 63.8173,59.0061 C62.8158,60.0076 61.4987,60.5082 59.9403,60.248 L51.6994,58.3061 C49.2077,57.719 47.3333,55.6605 46.9816,53.1249 L46.264,47.9528 C46.2639,47.5446 46.1154,47.2478 45.8742,47.0065 C45.6515,46.7838 45.3175,46.6353 45.0206,46.5239 C43.3508,45.9298 39.7701,45.5763 35.9855,45.5753 C32.2194,45.5557 28.6389,45.9815 26.9694,46.5005 C26.6726,46.6117 26.3387,46.76 26.079,47.0197 C25.8194,47.2793 25.6525,47.5947 25.6526,48.0028 L24.9872,53.09 C24.6524,55.6494 22.7664,57.7335 20.253,58.3214 L11.8346,60.2905 C10.2949,60.5684 9.1074,60.0486 8.2166,59.1579 C6.9733,57.9145 6.3791,55.9107 6.3229,53.9628 C6.1921,50.4193 7.4343,47.5069 9.8639,45.0773 C15.1684,39.7728 25.6683,37.401 35.9832,37.4038 Z ") + }) + } + + items.append(AuthorizationLayoutItem(node: self.titleIconNode, size: self.titleIconNode.image!.size, spacingBefore: AuthorizationLayoutItemSpacing(weight: 41.0, maxValue: 41.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + items.append(AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + items.append(AuthorizationLayoutItem(node: self.currentOptionNode, size: currentOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + + items.append(AuthorizationLayoutItem(node: self.codeInputView, size: codeFieldSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 40.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + + /*items.append(AuthorizationLayoutItem(node: self.codeField, size: CGSize(width: layout.size.width - 88.0, height: 44.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 40.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + items.append(AuthorizationLayoutItem(node: self.codeSeparatorNode, size: CGSize(width: layout.size.width - 88.0, height: UIScreenPixel), spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))*/ + + items.append(AuthorizationLayoutItem(node: self.currentOptionInfoNode, size: currentOptionInfoSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 60.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + + items.append(AuthorizationLayoutItem(node: self.nextOptionNode, size: nextOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 120.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + default: + self.titleIconNode.isHidden = true + items.append(AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + items.append(AuthorizationLayoutItem(node: self.currentOptionNode, size: currentOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + + items.append(AuthorizationLayoutItem(node: self.codeInputView, size: codeFieldSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 40.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + /*items.append(AuthorizationLayoutItem(node: self.codeField, size: CGSize(width: layout.size.width - 88.0, height: 44.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 40.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + items.append(AuthorizationLayoutItem(node: self.codeSeparatorNode, size: CGSize(width: layout.size.width - 88.0, height: UIScreenPixel), spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))*/ + + items.append(AuthorizationLayoutItem(node: self.nextOptionNode, size: nextOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 120.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + } } else { self.titleIconNode.isHidden = true items.append(AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) items.append(AuthorizationLayoutItem(node: self.currentOptionNode, size: currentOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) - items.append(AuthorizationLayoutItem(node: self.codeField, size: CGSize(width: layout.size.width - 88.0, height: 44.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 40.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) - items.append(AuthorizationLayoutItem(node: self.codeSeparatorNode, size: CGSize(width: layout.size.width - 88.0, height: UIScreenPixel), spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + + items.append(AuthorizationLayoutItem(node: self.codeInputView, size: codeFieldSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 40.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + /*items.append(AuthorizationLayoutItem(node: self.codeField, size: CGSize(width: layout.size.width - 88.0, height: 44.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 40.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + items.append(AuthorizationLayoutItem(node: self.codeSeparatorNode, size: CGSize(width: layout.size.width - 88.0, height: UIScreenPixel), spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))*/ items.append(AuthorizationLayoutItem(node: self.nextOptionNode, size: nextOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 120.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) } @@ -266,15 +395,21 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF } func activateInput() { - self.codeField.textField.becomeFirstResponder() + let _ = self.codeInputView.becomeFirstResponder() + //self.codeField.textField.becomeFirstResponder() } func animateError() { - self.codeField.layer.addShakeAnimation() + self.codeInputView.layer.addShakeAnimation() + //self.codeField.layer.addShakeAnimation() } @objc func codeFieldTextChanged(_ textField: UITextField) { - self.updateNextEnabled?(!(textField.text ?? "").isEmpty) + self.textChanged(text: textField.text ?? "") + } + + private func textChanged(text: String) { + self.updateNextEnabled?(!text.isEmpty) if let codeType = self.codeType { var codeLength: Int32? switch codeType { @@ -282,12 +417,14 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF codeLength = length case let .otherSession(length): codeLength = length + case let .missedCall(_, length): + codeLength = length case let .sms(length): codeLength = length default: break } - if let codeLength = codeLength, let text = textField.text, text.count == Int(codeLength) { + if let codeLength = codeLength, text.count == Int(codeLength) { self.loginWithCode?(text) } }