diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 20d65681dc..41f4a528a3 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12063,7 +12063,14 @@ Sorry for the inconvenience."; "Login.EnterPhraseBeginningText" = "We've sent you an SMS with a secret phrase that starts with **\"%1$@\"** to your phone **%2$@**."; "Login.EnterPhrasePlaceholder" = "Enter Phrase from SMS"; +"Login.EnterPhraseHint" = "You can copy and paste the phrase here."; +"Login.BackToWord" = "Back to entering word"; +"Login.BackToPhrase" = "Back to entering phrase"; + "Login.WrongPhraseError" = "Incorrect, please try again."; +"Login.Paste" = "Paste"; + +"Login.ReturnToCode" = "Return to entering the code"; "Map.LiveLocationPrivateNewDescription" = "Choose for how long **%@** will see your accurate location, including when the app is closed."; "Map.LiveLocationGroupNewDescription" = "Choose for how long people in this chat will see your accurate location, including when the app is closed."; @@ -12077,3 +12084,5 @@ Sorry for the inconvenience."; "Map.LiveLocationForHours_any" = "For %@ hours"; "Map.LiveLocationIndefinite" = "Until I turn it off"; + +"Channel.AdminLog.Settings" = "Settings"; diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryController.swift index acd0e16dca..f125c86064 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryController.swift @@ -23,10 +23,11 @@ public final class AuthorizationSequenceCodeEntryController: ViewController { var reset: (() -> Void)? public var requestNextOption: (() -> Void)? + public var requestPreviousOption: (() -> Void)? var resetEmail: (() -> Void)? var retryResetEmail: (() -> Void)? - var data: (String, String?, SentAuthorizationCodeType, AuthorizationCodeNextType?, Int32?)? + var data: (String, String?, SentAuthorizationCodeType, AuthorizationCodeNextType?, Int32?, Bool, Bool)? var termsOfService: (UnauthorizedAccountTermsOfService, Bool)? private let hapticFeedback = HapticFeedback() @@ -40,6 +41,19 @@ public final class AuthorizationSequenceCodeEntryController: ViewController { } } + var isWordOrPhrase: Bool { + if let type = self.data?.2 { + switch type { + case .word, .phrase: + return true + default: + return false + } + } else { + return false + } + } + public init(presentationData: PresentationData, back: @escaping () -> Void) { self.strings = presentationData.strings self.theme = presentationData.theme @@ -60,7 +74,7 @@ public final class AuthorizationSequenceCodeEntryController: ViewController { let proceed: String let stop: String - if let (_, _, type, _, _) = self?.data, case .email = type { + if let (_, _, type, _, _, _, _) = self?.data, case .email = type { text = presentationData.strings.Login_CancelEmailVerification proceed = presentationData.strings.Login_CancelEmailVerificationContinue stop = presentationData.strings.Login_CancelEmailVerificationStop @@ -107,6 +121,10 @@ public final class AuthorizationSequenceCodeEntryController: ViewController { self?.requestNextOption?() } + self.controllerNode.requestPreviousOption = { [weak self] in + self?.requestPreviousOption?() + } + self.controllerNode.updateNextEnabled = { [weak self] value in self?.navigationItem.rightBarButtonItem?.isEnabled = value } @@ -123,12 +141,12 @@ public final class AuthorizationSequenceCodeEntryController: ViewController { self?.present(c, in: .window(.root), with: a) } - if let (number, email, codeType, nextType, timeout) = self.data { + if let (number, email, codeType, nextType, timeout, hasPreviousCode, previousIsPhrase) = self.data { var appleSignInAllowed = false if case let .email(_, _, _, _, appleSignInAllowedValue, _) = codeType { appleSignInAllowed = appleSignInAllowedValue } - self.controllerNode.updateData(number: number, email: email, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed) + self.controllerNode.updateData(number: number, email: email, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed, hasPreviousCode: hasPreviousCode, previousIsPhrase: previousIsPhrase) } } @@ -155,6 +173,10 @@ public final class AuthorizationSequenceCodeEntryController: ViewController { self.controllerNode.animateError(text: text) } + func updateAppIsActive(_ isActive: Bool) { + self.controllerNode.updatePasteVisibility() + } + func updateNavigationItems() { guard let layout = self.validLayout, layout.size.width < 360.0 else { return @@ -168,10 +190,10 @@ public final class AuthorizationSequenceCodeEntryController: ViewController { } } - public func updateData(number: String, email: String?, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?) { + public func updateData(number: String, email: String?, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?, hasPreviousCode: Bool, previousIsPhrase: Bool) { self.termsOfService = termsOfService - if self.data?.0 != number || self.data?.1 != email || self.data?.2 != codeType || self.data?.3 != nextType || self.data?.4 != timeout { - self.data = (number, email, codeType, nextType, timeout) + if self.data?.0 != number || self.data?.1 != email || self.data?.2 != codeType || self.data?.3 != nextType || self.data?.4 != timeout || self.data?.5 != hasPreviousCode || self.data?.6 != previousIsPhrase { + self.data = (number, email, codeType, nextType, timeout, hasPreviousCode, previousIsPhrase) var appleSignInAllowed = false if case let .email(_, _, _, _, appleSignInAllowedValue, _) = codeType { @@ -179,7 +201,7 @@ public final class AuthorizationSequenceCodeEntryController: ViewController { } if self.isNodeLoaded { - self.controllerNode.updateData(number: number, email: email, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed) + self.controllerNode.updateData(number: number, email: email, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed, hasPreviousCode: hasPreviousCode, previousIsPhrase: previousIsPhrase) self.requestLayout(transition: .immediate) } } @@ -199,7 +221,7 @@ public final class AuthorizationSequenceCodeEntryController: ViewController { } @objc private func nextPressed() { - guard let (_, _, type, _, _) = self.data else { + guard let (_, _, type, _, _, _, _) = self.data else { return } diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift index 9bca28b0f0..d56702e7e6 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift @@ -31,6 +31,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF private let currentOptionInfoActivateAreaNode: AccessibilityAreaNode private let nextOptionTitleNode: ImmediateTextNode private let nextOptionButtonNode: HighlightableButtonNode + private let nextOptionArrowNode: ASImageNode private let resetTextNode: ImmediateTextNode private let resetNode: HighlightableButtonNode @@ -41,10 +42,15 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF private let textField: TextFieldNode private let textSeparatorNode: ASDisplayNode + private let pasteButton: HighlightableButtonNode private let codeInputView: CodeInputView private let errorTextNode: ImmediateTextNode + private let hintButtonNode: HighlightTrackingButtonNode + private let hintTextNode: ImmediateTextNode + private let hintArrowNode: ASImageNode + private var codeType: SentAuthorizationCodeType? private let countdownDisposable = MetaDisposable() @@ -53,6 +59,8 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF private var layoutArguments: (ContainerViewLayout, CGFloat)? private var appleSignInAllowed = false + private var hasPreviousCode = false + private var previousIsPhrase = false var phoneNumber: String = "" { didSet { @@ -82,6 +90,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF var requestNextOption: (() -> Void)? var requestAnotherOption: (() -> Void)? + var requestPreviousOption: (() -> Void)? var updateNextEnabled: ((Bool) -> Void)? var reset: (() -> Void)? var retryReset: (() -> Void)? @@ -128,6 +137,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF self.titleIconNode.displaysAsynchronously = false self.currentOptionNode = ImmediateTextNodeWithEntities() + self.currentOptionNode.balancedTextLayout = true self.currentOptionNode.isUserInteractionEnabled = false self.currentOptionNode.displaysAsynchronously = false self.currentOptionNode.lineSpacing = 0.1 @@ -159,6 +169,10 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF } self.nextOptionButtonNode.addSubnode(self.nextOptionTitleNode) + self.nextOptionArrowNode = ASImageNode() + self.nextOptionArrowNode.displaysAsynchronously = false + self.nextOptionArrowNode.isUserInteractionEnabled = false + self.codeInputView = CodeInputView() self.codeInputView.textField.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance self.codeInputView.textField.returnKeyType = .done @@ -194,10 +208,25 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF self.textField.textField.disableAutomaticKeyboardHandling = [.forward, .backward] self.textField.textField.tintColor = self.theme.list.itemAccentColor + self.pasteButton = HighlightableButtonNode() + self.errorTextNode = ImmediateTextNode() self.errorTextNode.alpha = 0.0 self.errorTextNode.displaysAsynchronously = false self.errorTextNode.textAlignment = .center + self.errorTextNode.isUserInteractionEnabled = false + + self.hintButtonNode = HighlightableButtonNode() + self.hintButtonNode.alpha = 0.0 + self.hintButtonNode.isUserInteractionEnabled = false + + self.hintTextNode = ImmediateTextNode() + self.hintTextNode.displaysAsynchronously = false + self.hintTextNode.textAlignment = .center + self.hintTextNode.isUserInteractionEnabled = false + + self.hintArrowNode = ASImageNode() + self.hintArrowNode.displaysAsynchronously = false self.resetNode = HighlightableButtonNode() self.resetNode.displaysAsynchronously = false @@ -238,6 +267,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF self.addSubnode(self.codeInputView) self.addSubnode(self.textSeparatorNode) self.addSubnode(self.textField) + self.addSubnode(self.pasteButton) self.addSubnode(self.titleNode) self.addSubnode(self.titleActivateAreaNode) self.addSubnode(self.titleIconNode) @@ -245,11 +275,15 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF self.addSubnode(self.currentOptionActivateAreaNode) self.addSubnode(self.currentOptionInfoNode) self.addSubnode(self.nextOptionButtonNode) + self.nextOptionButtonNode.addSubnode(self.nextOptionArrowNode) self.addSubnode(self.animationNode) self.addSubnode(self.resetNode) self.addSubnode(self.resetTextNode) self.addSubnode(self.dividerNode) self.addSubnode(self.errorTextNode) + self.addSubnode(self.hintButtonNode) + self.hintButtonNode.addSubnode(self.hintTextNode) + self.hintButtonNode.addSubnode(self.hintArrowNode) self.addSubnode(self.proceedNode) self.codeInputView.updated = { [weak self] in @@ -295,6 +329,18 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF } self.signInWithAppleButton?.addTarget(self, action: #selector(self.signInWithApplePressed), for: .touchUpInside) self.resetNode.addTarget(self, action: #selector(self.resetPressed), forControlEvents: .touchUpInside) + + self.pasteButton.setTitle(strings.Login_Paste, with: Font.medium(13.0), with: theme.list.itemAccentColor, for: .normal) + self.pasteButton.setBackgroundImage(generateStretchableFilledCircleImage(radius: 12.0, color: theme.list.itemAccentColor.withAlphaComponent(0.1)), for: .normal) + self.pasteButton.addTarget(self, action: #selector(self.pastePressed), forControlEvents: .touchUpInside) + + self.hintButtonNode.addTarget(self, action: #selector(self.previousOptionNodePressed), forControlEvents: .touchUpInside) + + self.hintArrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: theme.list.itemAccentColor) + self.hintArrowNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0) + self.nextOptionArrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: theme.list.itemAccentColor) + + self.updatePasteVisibility() } deinit { @@ -308,7 +354,19 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF self.view.addSubview(signInWithAppleButton) } } + + @objc private func pastePressed() { + if let text = UIPasteboard.general.string, !text.isEmpty { + if checkValidity(text: text) { + self.textField.textField.text = text + } + } + } + func updatePasteVisibility() { + self.pasteButton.isHidden = !UIPasteboard.general.hasStrings + } + func updateCode(_ code: String) { self.codeInputView.text = code self.codeChanged(text: code) @@ -343,10 +401,12 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF self.codeInputView.text = "" } - func updateData(number: String, email: String?, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, appleSignInAllowed: Bool) { + func updateData(number: String, email: String?, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, appleSignInAllowed: Bool, hasPreviousCode: Bool, previousIsPhrase: Bool) { self.codeType = codeType self.phoneNumber = number self.email = email + self.hasPreviousCode = hasPreviousCode + self.previousIsPhrase = previousIsPhrase var appleSignInAllowed = appleSignInAllowed if #available(iOS 13.0, *) { @@ -369,6 +429,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF self.currentOptionInfoActivateAreaNode.removeFromSupernode() } } + if let timeout = timeout { #if DEBUG let timeout = min(timeout, 5) @@ -378,7 +439,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF if let strongSelf = self { if let currentTimeoutTime = strongSelf.currentTimeoutTime, currentTimeoutTime > 0 { strongSelf.currentTimeoutTime = currentTimeoutTime - 1 - let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: codeType, nextType: nextType, timeout: strongSelf.currentTimeoutTime, strings: strongSelf.strings, primaryColor: strongSelf.theme.list.itemPrimaryTextColor, accentColor: strongSelf.theme.list.itemAccentColor) + let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: codeType, nextType: nextType, returnToCode: hasPreviousCode, timeout: strongSelf.currentTimeoutTime, strings: strongSelf.strings, primaryColor: strongSelf.theme.list.itemPrimaryTextColor, accentColor: strongSelf.theme.list.itemAccentColor) strongSelf.nextOptionTitleNode.attributedText = nextOptionText strongSelf.nextOptionButtonNode.isUserInteractionEnabled = nextOptionActive strongSelf.nextOptionButtonNode.accessibilityLabel = nextOptionText.string @@ -418,7 +479,8 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF self.currentTimeoutTime = nil self.countdownDisposable.set(nil) } - let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: codeType, nextType: nextType, timeout: self.currentTimeoutTime, strings: self.strings, primaryColor: self.theme.list.itemPrimaryTextColor, accentColor: self.theme.list.itemAccentColor) + + let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: codeType, nextType: nextType, returnToCode: hasPreviousCode, timeout: self.currentTimeoutTime, strings: self.strings, primaryColor: self.theme.list.itemPrimaryTextColor, accentColor: self.theme.list.itemAccentColor) self.nextOptionTitleNode.attributedText = nextOptionText self.nextOptionButtonNode.isUserInteractionEnabled = nextOptionActive self.nextOptionButtonNode.accessibilityLabel = nextOptionText.string @@ -427,6 +489,14 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF } else { self.nextOptionButtonNode.accessibilityTraits = [.button, .notEnabled] } + + switch codeType { + case .word, .phrase: + self.nextOptionArrowNode.isHidden = !hasPreviousCode + default: + self.nextOptionArrowNode.isHidden = true + } + if let layoutArguments = self.layoutArguments { self.containerLayoutUpdated(layoutArguments.0, navigationBarHeight: layoutArguments.1, transition: .immediate) } @@ -699,7 +769,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF switch codeType { case .word, .phrase: - additionalBottomInset = 120.0 + additionalBottomInset = 100.0 self.nextOptionButtonNode.isHidden = false items.append(AuthorizationLayoutItem(node: self.nextOptionButtonNode, size: nextOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 120.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) @@ -719,13 +789,6 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF self.proceedNode.isHidden = true items.append(AuthorizationLayoutItem(node: self.nextOptionButtonNode, size: nextOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 120.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) } - - if case .email = codeType { - self.nextOptionButtonNode.isHidden = true - } else { - self.nextOptionButtonNode.isHidden = false - items.append(AuthorizationLayoutItem(node: self.nextOptionButtonNode, size: nextOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 120.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) - } } } } else { @@ -740,8 +803,57 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF let _ = layoutAuthorizationItems(bounds: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - insets.bottom - additionalBottomInset)), items: items, transition: transition, failIfDoesNotFit: false) + if let codeType = self.codeType { + let yOffset: CGFloat = layout.size.width > 320.0 ? 18.0 : 5.0 + if case .phrase = codeType { + self.hintButtonNode.alpha = 1.0 + self.hintButtonNode.isUserInteractionEnabled = false + + self.hintTextNode.attributedText = NSAttributedString(string: self.strings.Login_EnterPhraseHint, font: Font.regular(13.0), textColor: self.theme.list.itemSecondaryTextColor, paragraphAlignment: .center) + + let hintTextSize = self.hintTextNode.updateLayout(CGSize(width: layout.size.width - 48.0, height: .greatestFiniteMagnitude)) + transition.updateFrame(node: self.hintButtonNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - hintTextSize.width) / 2.0), y: self.textField.frame.maxY + yOffset), size: hintTextSize)) + self.hintTextNode.frame = CGRect(origin: .zero, size: hintTextSize) + + let pasteSize = self.pasteButton.measure(layout.size) + let pasteButtonSize = CGSize(width: pasteSize.width + 16.0, height: 24.0) + transition.updateFrame(node: self.pasteButton, frame: CGRect(origin: CGPoint(x: layout.size.width - 40.0 - pasteButtonSize.width, y: self.textField.frame.midY - pasteButtonSize.height / 2.0), size: pasteButtonSize)) + self.hintArrowNode.isHidden = true + } else if case .word = codeType { + self.hintButtonNode.alpha = 0.0 + self.hintButtonNode.isUserInteractionEnabled = false + self.hintArrowNode.isHidden = true + } else if self.hasPreviousCode { + self.hintButtonNode.alpha = 1.0 + self.hintButtonNode.isUserInteractionEnabled = true + + self.hintTextNode.attributedText = NSAttributedString(string: self.previousIsPhrase ? self.strings.Login_BackToPhrase : self.strings.Login_BackToWord, font: Font.regular(13.0), textColor: self.theme.list.itemAccentColor, paragraphAlignment: .center) + + let hintTextSize = self.hintTextNode.updateLayout(CGSize(width: layout.size.width - 48.0, height: .greatestFiniteMagnitude)) + transition.updateFrame(node: self.hintButtonNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - hintTextSize.width) / 2.0), y: self.codeInputView.frame.maxY + yOffset + 6.0), size: hintTextSize)) + self.hintTextNode.frame = CGRect(origin: .zero, size: hintTextSize) + + if let icon = self.hintArrowNode.image { + self.hintArrowNode.frame = CGRect(origin: CGPoint(x: self.hintTextNode.frame.minX - icon.size.width - 5.0, y: self.hintTextNode.frame.midY - icon.size.height / 2.0), size: icon.size) + } + self.hintArrowNode.isHidden = false + } else { + self.hintButtonNode.alpha = 0.0 + self.hintButtonNode.isUserInteractionEnabled = false + self.hintArrowNode.isHidden = true + } + } else { + self.hintButtonNode.alpha = 0.0 + self.hintButtonNode.isUserInteractionEnabled = false + self.hintArrowNode.isHidden = true + } + self.nextOptionTitleNode.frame = self.nextOptionButtonNode.bounds + if let icon = self.nextOptionArrowNode.image { + self.nextOptionArrowNode.frame = CGRect(origin: CGPoint(x: self.nextOptionTitleNode.frame.maxX + 7.0, y: self.nextOptionTitleNode.frame.midY - icon.size.height / 2.0), size: icon.size) + } + self.titleActivateAreaNode.frame = self.titleNode.frame self.currentOptionActivateAreaNode.frame = self.currentOptionNode.frame self.currentOptionInfoActivateAreaNode.frame = self.currentOptionInfoNode.frame @@ -792,12 +904,18 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF } self.errorTextNode.alpha = 1.0 self.errorTextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) - self.errorTextNode.layer.addShakeAnimation(amplitude: -8.0, duration: 0.5, count: 6, decay: true) + + let previousHintAlpha = self.hintButtonNode.alpha + self.hintButtonNode.alpha = 0.0 + self.hintButtonNode.layer.animateAlpha(from: previousHintAlpha, to: 0.0, duration: 0.1) Queue.mainQueue().after(1.6) { self.errorTextNode.alpha = 0.0 self.errorTextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15) + self.hintButtonNode.alpha = previousHintAlpha + self.hintButtonNode.layer.animateAlpha(from: 0.0, to: previousHintAlpha, duration: 0.1) + let transition: ContainedViewLayoutTransition = .animated(duration: 0.15, curve: .easeInOut) transition.updateBackgroundColor(node: self.textSeparatorNode, color: self.theme.list.itemPlainSeparatorColor) } @@ -852,23 +970,25 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF var updated = textField.text ?? "" updated.replaceSubrange(updated.index(updated.startIndex, offsetBy: range.lowerBound) ..< updated.index(updated.startIndex, offsetBy: range.upperBound), with: string) + return checkValidity(text: updated) + } + + func checkValidity(text: String) -> Bool { if let codeType = self.codeType { switch codeType { case let .word(startsWith): - if let startsWith, startsWith.count == 1, !updated.isEmpty && !updated.hasPrefix(startsWith) { + if let startsWith, startsWith.count == 1, !text.isEmpty && !text.hasPrefix(startsWith) { if self.errorTextNode.alpha.isZero { - //TODO:localize - self.animateError(text: "Incorrect, please try again") + self.animateError(text: self.strings.Login_WrongPhraseError) } return false } case let .phrase(startsWith): - if let startsWith, !updated.isEmpty { - let firstWord = updated.components(separatedBy: " ").first ?? "" + if let startsWith, !text.isEmpty { + let firstWord = text.components(separatedBy: " ").first ?? "" if !firstWord.isEmpty && !startsWith.hasPrefix(firstWord) { if self.errorTextNode.alpha.isZero { - //TODO:localize - self.animateError(text: "Incorrect, please try again") + self.animateError(text: self.strings.Login_WrongPhraseError) } return false } @@ -877,7 +997,6 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF break } } - return true } @@ -885,6 +1004,10 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF self.requestAnotherOption?() } + @objc func previousOptionNodePressed() { + self.requestPreviousOption?() + } + @objc func proceedPressed() { switch self.codeType { case let .fragment(url, _): diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift index 9b55924488..7f472c2bc5 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift @@ -45,6 +45,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth private var stateDisposable: Disposable? private let actionDisposable = MetaDisposable() + private var applicationStateDisposable: Disposable? private var didPlayPresentationAnimation = false @@ -92,6 +93,18 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth |> deliverOnMainQueue).startStrict(next: { [weak self] state in self?.updateState(state: state) }).strict() + + self.applicationStateDisposable = (self.sharedContext.applicationBindings.applicationIsActive + |> deliverOnMainQueue).start(next: { [weak self] isActive in + guard let self else { + return + } + for viewController in self.viewControllers { + if let codeController = viewController as? AuthorizationSequenceCodeEntryController { + codeController.updateAppIsActive(isActive) + } + } + }) } required public init(coder aDecoder: NSCoder) { @@ -101,6 +114,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth deinit { self.stateDisposable?.dispose() self.actionDisposable.dispose() + self.applicationStateDisposable?.dispose() } override public func loadView() { @@ -296,7 +310,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth return controller } - private func codeEntryController(number: String, phoneCodeHash: String, email: String?, type: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?) -> AuthorizationSequenceCodeEntryController { + private func codeEntryController(number: String, phoneCodeHash: String, email: String?, type: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, hasPreviousCode: Bool, previousIsPhrase: Bool, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?) -> AuthorizationSequenceCodeEntryController { var currentController: AuthorizationSequenceCodeEntryController? for c in self.viewControllers { if let c = c as? AuthorizationSequenceCodeEntryController { @@ -568,6 +582,16 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth } controller.requestNextOption = { [weak self, weak controller] in if let strongSelf = self { + switch type { + case .word, .phrase: + if hasPreviousCode { + strongSelf.actionDisposable.set(togglePreviousCodeEntry(account: strongSelf.account).start()) + return + } + default: + break + } + if nextType == nil { if let controller { let carrier = CTCarrier() @@ -616,11 +640,17 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth } } } - controller.reset = { [weak self] in - if let strongSelf = self { - let account = strongSelf.account - let _ = strongSelf.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .empty)).startStandalone() + controller.requestPreviousOption = { [weak self] in + guard let self else { + return } + self.actionDisposable.set(togglePreviousCodeEntry(account: self.account).start()) + } + controller.reset = { [weak self] in + guard let self else { + return + } + let _ = self.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: self.account.testingEnvironment, masterDatacenterId: self.account.masterDatacenterId, contents: .empty)).startStandalone() } controller.signInWithApple = { [weak self] in guard let strongSelf = self else { @@ -645,7 +675,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth strongSelf.sharedContext.applicationBindings.openUrl(url) } } - controller.updateData(number: formatPhoneNumber(number), email: email, codeType: type, nextType: nextType, timeout: timeout, termsOfService: termsOfService) + controller.updateData(number: formatPhoneNumber(number), email: email, codeType: type, nextType: nextType, timeout: timeout, termsOfService: termsOfService, hasPreviousCode: hasPreviousCode, previousIsPhrase: previousIsPhrase) return controller } @@ -1189,12 +1219,14 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth controllers.append(self.phoneEntryController(countryCode: countryCode, number: number, splashController: previousSplashController)) self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty && (previousSplashController == nil || self.viewControllers.count > 2)) - case let .confirmationCodeEntry(number, type, phoneCodeHash, timeout, nextType, _): + case let .confirmationCodeEntry(number, type, phoneCodeHash, timeout, nextType, _, previousCodeEntry, usePrevious): var controllers: [ViewController] = [] if !self.otherAccountPhoneNumbers.1.isEmpty { controllers.append(self.splashController()) } controllers.append(self.phoneEntryController(countryCode: AuthorizationSequenceController.defaultCountryCode(), number: "", splashController: nil)) + + var isGoingBack = false if case let .emailSetupRequired(appleSignInAllowed) = type { self.appleSignInAllowed = appleSignInAllowed controllers.append(self.emailSetupController(number: number, appleSignInAllowed: appleSignInAllowed)) @@ -1202,9 +1234,35 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth if let _ = self.currentEmail { controllers.append(self.emailSetupController(number: number, appleSignInAllowed: self.appleSignInAllowed)) } - controllers.append(self.codeEntryController(number: number, phoneCodeHash: phoneCodeHash, email: self.currentEmail, type: type, nextType: nextType, timeout: timeout, termsOfService: nil)) + + if let previousCodeEntry, case let .confirmationCodeEntry(number, type, phoneCodeHash, timeout, nextType, _, _, _) = previousCodeEntry, usePrevious { + var previousIsPhrase = false + if case .phrase = type { + previousIsPhrase = true + } + controllers.append(self.codeEntryController(number: number, phoneCodeHash: phoneCodeHash, email: self.currentEmail, type: type, nextType: nextType, timeout: timeout, hasPreviousCode: true, previousIsPhrase: previousIsPhrase, termsOfService: nil)) + isGoingBack = true + } else { + var previousIsPhrase = false + if let previousCodeEntry, case let .confirmationCodeEntry(_, type, _, _, _, _, _, _) = previousCodeEntry { + if case .phrase = type { + previousIsPhrase = true + } + } + controllers.append(self.codeEntryController(number: number, phoneCodeHash: phoneCodeHash, email: self.currentEmail, type: type, nextType: nextType, timeout: timeout, hasPreviousCode: previousCodeEntry != nil, previousIsPhrase: previousIsPhrase, termsOfService: nil)) + } + } + + if isGoingBack, let currentLastController = self.viewControllers.last as? AuthorizationSequenceCodeEntryController, !currentLastController.isWordOrPhrase { + var tempControllers = controllers + tempControllers.append(currentLastController) + self.setViewControllers(tempControllers, animated: false) + Queue.mainQueue().justDispatch { + self.setViewControllers(controllers, animated: true) + } + } else { + self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty) } - self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty) case let .passwordEntry(hint, _, _, suggestReset, syncContacts): var controllers: [ViewController] = [] if !self.otherAccountPhoneNumbers.1.isEmpty { diff --git a/submodules/AuthorizationUtils/Sources/AuthorizationOptionText.swift b/submodules/AuthorizationUtils/Sources/AuthorizationOptionText.swift index 95d94bd958..a5e86a01e4 100644 --- a/submodules/AuthorizationUtils/Sources/AuthorizationOptionText.swift +++ b/submodules/AuthorizationUtils/Sources/AuthorizationOptionText.swift @@ -56,9 +56,18 @@ public func authorizationCurrentOptionText(_ type: SentAuthorizationCodeType, ph } } -public func authorizationNextOptionText(currentType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, strings: PresentationStrings, primaryColor: UIColor, accentColor: UIColor) -> (NSAttributedString, Bool) { +public func authorizationNextOptionText(currentType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, returnToCode: Bool = false, timeout: Int32?, strings: PresentationStrings, primaryColor: UIColor, accentColor: UIColor) -> (NSAttributedString, Bool) { let font = Font.regular(16.0) + switch currentType { + case .word, .phrase: + if returnToCode { + return (NSAttributedString(string: strings.Login_ReturnToCode, font: font, textColor: accentColor, paragraphAlignment: .center), true) + } + default: + break + } + if let nextType = nextType, let timeout = timeout, timeout > 0 { let minutes = timeout / 60 let seconds = timeout % 60 diff --git a/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift b/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift index 45dfe6526c..6ff6799495 100644 --- a/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift +++ b/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift @@ -108,7 +108,7 @@ public func ChangePhoneNumberController(context: AccountContext) -> ViewControll codeController.openFragment = { url in context.sharedContext.applicationBindings.openUrl(url) } - codeController.updateData(number: formatPhoneNumber(context: context, number: phoneNumber), email: nil, codeType: next.type, nextType: nil, timeout: next.timeout, termsOfService: nil) + codeController.updateData(number: formatPhoneNumber(context: context, number: phoneNumber), email: nil, codeType: next.type, nextType: nil, timeout: next.timeout, termsOfService: nil, hasPreviousCode: false, previousIsPhrase: false) dismissImpl = { [weak codeController] in codeController?.dismiss() } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift index 19ec37b50b..14ef94ac64 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift @@ -1446,7 +1446,7 @@ public func privacyAndSecurityController( emailChangeCompletion(codeController) })) } - codeController.updateData(number: "", email: email, codeType: .email(emailPattern: "", length: data.length, resetAvailablePeriod: nil, resetPendingDate: nil, appleSignInAllowed: false, setup: true), nextType: nil, timeout: nil, termsOfService: nil) + codeController.updateData(number: "", email: email, codeType: .email(emailPattern: "", length: data.length, resetAvailablePeriod: nil, resetPendingDate: nil, appleSignInAllowed: false, setup: true), nextType: nil, timeout: nil, termsOfService: nil, hasPreviousCode: false, previousIsPhrase: false) pushControllerImpl?(codeController, true) dismissCodeControllerImpl = { [weak codeController] in codeController?.dismiss() diff --git a/submodules/TelegramCore/Sources/Authorization.swift b/submodules/TelegramCore/Sources/Authorization.swift index 3eb5474c0f..e2cf3449f8 100644 --- a/submodules/TelegramCore/Sources/Authorization.swift +++ b/submodules/TelegramCore/Sources/Authorization.swift @@ -306,7 +306,20 @@ public func sendAuthorizationCode(accountManager: AccountManager mapToSignal { success -> Signal in if success { return account.postbox.transaction { transaction -> SendAuthorizationCodeResult in - transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts))) + var previousCodeEntry: UnauthorizedAccountStateContents? + if let state = transaction.getState() as? UnauthorizedAccountState, case let .confirmationCodeEntry(_, type, _, _, _, _, previousCodeEntryValue, _) = state.contents { + if let previousCodeEntryValue { + previousCodeEntry = previousCodeEntryValue + } else { + switch type { + case .word, .phrase: + previousCodeEntry = state.contents + default: + break + } + } + } + transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false))) return .sentCode(account) } @@ -318,7 +331,20 @@ public func sendAuthorizationCode(accountManager: AccountManager mapToSignal { success -> Signal in if success { return account.postbox.transaction { transaction -> SendAuthorizationCodeResult in - transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts))) + var previousCodeEntry: UnauthorizedAccountStateContents? + if let state = transaction.getState() as? UnauthorizedAccountState, case let .confirmationCodeEntry(_, type, _, _, _, _, previousCodeEntryValue, _) = state.contents { + if let previousCodeEntryValue { + previousCodeEntry = previousCodeEntryValue + } else { + switch type { + case .word, .phrase: + previousCodeEntry = state.contents + default: + break + } + } + } + transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false))) return .sentCode(account) } @@ -427,7 +466,20 @@ private func internalResendAuthorizationCode(accountManager: AccountManager Signal in if let state = transaction.getState() as? UnauthorizedAccountState { switch state.contents { - case let .confirmationCodeEntry(number, _, hash, _, nextType, syncContacts): + case let .confirmationCodeEntry(number, type, hash, _, nextType, syncContacts, previousCodeEntryValue, _): if nextType != nil { + var previousCodeEntry: UnauthorizedAccountStateContents? + if let previousCodeEntryValue { + previousCodeEntry = previousCodeEntryValue + } else { + switch type { + case .word, .phrase: + previousCodeEntry = state.contents + default: + break + } + } return account.network.request(Api.functions.auth.resendCode(phoneNumber: number, phoneCodeHash: hash), automaticFloodWait: false) |> mapError { error -> AuthorizationCodeRequestError in if error.errorDescription.hasPrefix("FLOOD_WAIT") { @@ -503,7 +566,7 @@ public func resendAuthorizationCode(accountManager: AccountManager mapToSignal { success -> Signal in if success { return account.postbox.transaction { transaction -> SendAuthorizationCodeResult in - transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts))) + transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false))) return .sentCode(account) } @@ -516,7 +579,7 @@ public func resendAuthorizationCode(accountManager: AccountManager map { _ -> Void in return Void() } } - transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts))) + transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false))) case .sentCodeSuccess: break } @@ -675,7 +738,7 @@ public func sendLoginEmailCode(account: UnauthorizedAccount, email: String) -> S return account.postbox.transaction { transaction -> Signal in if let state = transaction.getState() as? UnauthorizedAccountState { switch state.contents { - case let .confirmationCodeEntry(phoneNumber, _, phoneCodeHash, _, _, syncContacts): + case let .confirmationCodeEntry(phoneNumber, _, phoneCodeHash, _, _, syncContacts, _, _): return account.network.request(Api.functions.account.sendVerifyEmailCode(purpose: .emailVerifyPurposeLoginSetup(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), email: email), automaticFloodWait: false) |> `catch` { error -> Signal in let errorDescription = error.errorDescription ?? "" @@ -694,8 +757,8 @@ public func sendLoginEmailCode(account: UnauthorizedAccount, email: String) -> S |> mapToSignal { result -> Signal in return account.postbox.transaction { transaction -> Signal in switch result { - case let .sentEmailCode(emailPattern, length): - transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: .email(emailPattern: emailPattern, length: length, resetAvailablePeriod: nil, resetPendingDate: nil, appleSignInAllowed: false, setup: true), hash: phoneCodeHash, timeout: nil, nextType: nil, syncContacts: syncContacts))) + case let .sentEmailCode(emailPattern, length): + transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: .email(emailPattern: emailPattern, length: length, resetAvailablePeriod: nil, resetPendingDate: nil, appleSignInAllowed: false, setup: true), hash: phoneCodeHash, timeout: nil, nextType: nil, syncContacts: syncContacts, previousCodeEntry: nil, usePrevious: false))) } return .complete() } @@ -754,7 +817,7 @@ public func verifyLoginEmailSetup(account: UnauthorizedAccount, code: Authorizat return account.postbox.transaction { transaction -> Signal in if let state = transaction.getState() as? UnauthorizedAccountState { switch state.contents { - case let .confirmationCodeEntry(phoneNumber, _, phoneCodeHash, _, _, syncContacts): + case let .confirmationCodeEntry(phoneNumber, _, phoneCodeHash, _, _, syncContacts, _, _): let verification: Api.EmailVerification switch code { case let .emailCode(code): @@ -793,7 +856,7 @@ public func verifyLoginEmailSetup(account: UnauthorizedAccount, code: Authorizat 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))) + 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, previousCodeEntry: nil, usePrevious: false))) case .sentCodeSuccess: break } @@ -831,7 +894,7 @@ public func resetLoginEmail(account: UnauthorizedAccount, phoneNumber: String, p return account.postbox.transaction { transaction -> Signal in if let state = transaction.getState() as? UnauthorizedAccountState { switch state.contents { - case let .confirmationCodeEntry(phoneNumber, _, phoneCodeHash, _, _, syncContacts): + case let .confirmationCodeEntry(phoneNumber, _, phoneCodeHash, _, _, syncContacts, _, _): return account.network.request(Api.functions.auth.resetLoginEmail(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), automaticFloodWait: false) |> `catch` { error -> Signal in let errorDescription = error.errorDescription ?? "" @@ -854,7 +917,7 @@ public func resetLoginEmail(account: UnauthorizedAccount, phoneNumber: String, p parsedNextType = AuthorizationCodeNextType(apiType: nextType) } - transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts))) + transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: nil, usePrevious: false))) return .complete() case .sentCodeSuccess: @@ -883,7 +946,7 @@ public func authorizeWithCode(accountManager: AccountManager Signal in if let state = transaction.getState() as? UnauthorizedAccountState { switch state.contents { - case let .confirmationCodeEntry(number, _, hash, _, _, syncContacts): + case let .confirmationCodeEntry(number, _, hash, _, _, syncContacts, _, _): var flags: Int32 = 0 var phoneCode: String? var emailVerification: Api.EmailVerification? @@ -1326,6 +1389,17 @@ public func resetAuthorizationState(account: UnauthorizedAccount, to value: Auth } } +public func togglePreviousCodeEntry(account: UnauthorizedAccount) -> Signal { + return account.postbox.transaction { transaction -> Void in + if let state = transaction.getState() as? UnauthorizedAccountState { + if case let .confirmationCodeEntry(number, type, hash, timeout, nextType, syncContacts, previousCodeEntry, usePrevious) = state.contents, let previousCodeEntry { + transaction.setState(UnauthorizedAccountState(isTestingEnvironment: state.isTestingEnvironment, masterDatacenterId: state.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: type, hash: hash, timeout: timeout, nextType: nextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: !usePrevious))) + } + } + } + |> ignoreValues +} + public enum ReportMissingCodeError { case generic } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_UnauthorizedAccountState.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_UnauthorizedAccountState.swift index 7d2625685b..9bab8302f8 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_UnauthorizedAccountState.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_UnauthorizedAccountState.swift @@ -173,10 +173,10 @@ public struct UnauthorizedAccountTermsOfService: PostboxCoding, Equatable { } } -public enum UnauthorizedAccountStateContents: PostboxCoding, Equatable { +public indirect enum UnauthorizedAccountStateContents: PostboxCoding, Equatable { case empty case phoneEntry(countryCode: Int32, number: String) - case confirmationCodeEntry(number: String, type: SentAuthorizationCodeType, hash: String, timeout: Int32?, nextType: AuthorizationCodeNextType?, syncContacts: Bool) + case confirmationCodeEntry(number: String, type: SentAuthorizationCodeType, hash: String, timeout: Int32?, nextType: AuthorizationCodeNextType?, syncContacts: Bool, previousCodeEntry: UnauthorizedAccountStateContents?, usePrevious: Bool) case passwordEntry(hint: String, number: String?, code: AuthorizationCode?, suggestReset: Bool, syncContacts: Bool) case passwordRecovery(hint: String, number: String?, code: AuthorizationCode?, emailPattern: String, syncContacts: Bool) case awaitingAccountReset(protectedUntil: Int32, number: String?, syncContacts: Bool) @@ -193,7 +193,7 @@ public enum UnauthorizedAccountStateContents: PostboxCoding, Equatable { if let value = decoder.decodeOptionalInt32ForKey("nt") { nextType = AuthorizationCodeNextType(rawValue: value) } - self = .confirmationCodeEntry(number: decoder.decodeStringForKey("num", orElse: ""), type: decoder.decodeObjectForKey("t", decoder: { SentAuthorizationCodeType(decoder: $0) }) as! SentAuthorizationCodeType, hash: decoder.decodeStringForKey("h", orElse: ""), timeout: decoder.decodeOptionalInt32ForKey("tm"), nextType: nextType, syncContacts: decoder.decodeInt32ForKey("syncContacts", orElse: 1) != 0) + self = .confirmationCodeEntry(number: decoder.decodeStringForKey("num", orElse: ""), type: decoder.decodeObjectForKey("t", decoder: { SentAuthorizationCodeType(decoder: $0) }) as! SentAuthorizationCodeType, hash: decoder.decodeStringForKey("h", orElse: ""), timeout: decoder.decodeOptionalInt32ForKey("tm"), nextType: nextType, syncContacts: decoder.decodeInt32ForKey("syncContacts", orElse: 1) != 0, previousCodeEntry: decoder.decodeObjectForKey("previousCodeEntry", decoder: { UnauthorizedAccountStateContents(decoder: $0) }) as? UnauthorizedAccountStateContents, usePrevious: decoder.decodeInt32ForKey("usePrevious", orElse: 1) != 0) case UnauthorizedAccountStateContentsValue.passwordEntry.rawValue: var code: AuthorizationCode? if let modernCode = decoder.decodeObjectForKey("modernCode", decoder: { AuthorizationCode(decoder: $0) }) as? AuthorizationCode { @@ -228,7 +228,7 @@ public enum UnauthorizedAccountStateContents: PostboxCoding, Equatable { encoder.encodeInt32(UnauthorizedAccountStateContentsValue.phoneEntry.rawValue, forKey: "v") encoder.encodeInt32(countryCode, forKey: "cc") encoder.encodeString(number, forKey: "n") - case let .confirmationCodeEntry(number, type, hash, timeout, nextType, syncContacts): + case let .confirmationCodeEntry(number, type, hash, timeout, nextType, syncContacts, previousCodeEntry, usePrevious): encoder.encodeInt32(UnauthorizedAccountStateContentsValue.confirmationCodeEntry.rawValue, forKey: "v") encoder.encodeString(number, forKey: "num") encoder.encodeObject(type, forKey: "t") @@ -244,6 +244,14 @@ public enum UnauthorizedAccountStateContents: PostboxCoding, Equatable { encoder.encodeNil(forKey: "nt") } encoder.encodeInt32(syncContacts ? 1 : 0, forKey: "syncContacts") + + if let previousCodeEntry = previousCodeEntry { + encoder.encodeObject(previousCodeEntry, forKey: "previousCodeEntry") + encoder.encodeInt32(usePrevious ? 1 : 0, forKey: "usePrevious") + } else { + encoder.encodeNil(forKey: "previousCodeEntry") + encoder.encodeInt32(0, forKey: "usePrevious") + } case let .passwordEntry(hint, number, code, suggestReset, syncContacts): encoder.encodeInt32(UnauthorizedAccountStateContentsValue.passwordEntry.rawValue, forKey: "v") encoder.encodeString(hint, forKey: "h") @@ -312,8 +320,8 @@ public enum UnauthorizedAccountStateContents: PostboxCoding, Equatable { } else { return false } - case let .confirmationCodeEntry(lhsNumber, lhsType, lhsHash, lhsTimeout, lhsNextType, lhsSyncContacts): - if case let .confirmationCodeEntry(rhsNumber, rhsType, rhsHash, rhsTimeout, rhsNextType, rhsSyncContacts) = rhs { + case let .confirmationCodeEntry(lhsNumber, lhsType, lhsHash, lhsTimeout, lhsNextType, lhsSyncContacts, lhsPreviousCodeEntry, lhsUsePrevious): + if case let .confirmationCodeEntry(rhsNumber, rhsType, rhsHash, rhsTimeout, rhsNextType, rhsSyncContacts, rhsPreviousCodeEntry, rhsUsePrevious) = rhs { if lhsNumber != rhsNumber { return false } @@ -332,6 +340,12 @@ public enum UnauthorizedAccountStateContents: PostboxCoding, Equatable { if lhsSyncContacts != rhsSyncContacts { return false } + if lhsPreviousCodeEntry != rhsPreviousCodeEntry { + return false + } + if lhsUsePrevious != rhsUsePrevious { + return false + } return true } else { return false