Password remembering improvements

This commit is contained in:
Ilya Laktyushin 2021-07-09 20:02:42 +03:00
parent b28b67eef2
commit 3519a216c5
11 changed files with 4663 additions and 4225 deletions

View File

@ -6560,3 +6560,13 @@ Sorry for the inconvenience.";
"TwoFactorSetup.ResetDone.TextNoPassword" = "You can always set a new password in\n\n\nSettings>Privacy & Security>Two-Step Verification";
"TwoFactorSetup.ResetFloodWait" = "You have recently requested a password reset that was canceled. Please wait for %@ before making a new request.";
"TwoFactorRemember.Title" = "Enter Your Password";
"TwoFactorRemember.Text" = "Do you still remeber your password?";
"TwoFactorRemember.Placeholder" = "Password";
"TwoFactorRemember.Forgot" = "Forgot Password?";
"TwoFactorRemember.CheckPassword" = "Check Password";
"TwoFactorRemember.WrongPassword" = "You entered a wrong password.";
"TwoFactorRemember.Done.Title" = "Perfect!";
"TwoFactorRemember.Done.Text" = "You still remember your password.";
"TwoFactorRemember.Done.Action" = "Back to Settings";

View File

@ -212,8 +212,8 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
self.contentContainerNode.addSubnode(self.contentGridNode)
self.contentContainerNode.addSubnode(self.createActionSeparatorNode)
self.contentContainerNode.addSubnode(self.createActionButtonNode)
self.contentContainerNode.addSubnode(self.addToExistingActionSeparatorNode)
self.contentContainerNode.addSubnode(self.addToExistingActionButtonNode)
// self.contentContainerNode.addSubnode(self.addToExistingActionSeparatorNode)
// self.contentContainerNode.addSubnode(self.addToExistingActionButtonNode)
self.wrappingScrollNode.addSubnode(self.contentTitleNode)
self.wrappingScrollNode.addSubnode(self.contentSeparatorNode)

View File

@ -11,6 +11,7 @@ import TelegramPresentationData
import PresentationDataUtils
import TelegramCore
import AnimatedStickerNode
import ActivityIndicator
public enum TwoFactorDataInputMode {
public struct Recovery {
@ -40,6 +41,7 @@ public enum TwoFactorDataInputMode {
case updateEmailAddress(password: String)
case emailConfirmation(passwordAndHint: (String, String)?, emailPattern: String, codeLength: Int?)
case passwordHint(recovery: Recovery?, password: String)
case rememberPassword
}
public final class TwoFactorDataInputScreen: ViewController {
@ -50,8 +52,15 @@ public final class TwoFactorDataInputScreen: ViewController {
private let stateUpdated: (SetupTwoStepVerificationStateUpdate) -> Void
private let actionDisposable = MetaDisposable()
private let forgotDataDisposable = MetaDisposable()
private let forgotDataPromise = Promise<TwoStepVerificationConfiguration?>(nil)
private var didSetupForgotData = false
public var passwordRecoveryFailed: (() -> Void)?
public var passwordRemembered: (() -> Void)?
public var twoStepAuthSettingsController: ((TwoStepVerificationConfiguration) -> ViewController)?
public init(sharedContext: SharedAccountContext, engine: SomeTelegramEngine, mode: TwoFactorDataInputMode, stateUpdated: @escaping (SetupTwoStepVerificationStateUpdate) -> Void, presentation: ViewControllerNavigationPresentation = .modalInLargeLayout) {
self.sharedContext = sharedContext
self.engine = engine
@ -79,12 +88,21 @@ public final class TwoFactorDataInputScreen: ViewController {
deinit {
self.actionDisposable.dispose()
self.forgotDataDisposable.dispose()
}
@objc private func backPressed() {
self.dismiss()
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if case .rememberPassword = self.mode {
(self.displayNode as? TwoFactorDataInputScreenNode)?.focus()
}
}
override public func loadDisplayNode() {
self.displayNode = TwoFactorDataInputScreenNode(presentationData: self.presentationData, mode: self.mode, action: { [weak self] in
guard let strongSelf = self else {
@ -394,6 +412,63 @@ public final class TwoFactorDataInputScreen: ViewController {
} else {
strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailAddress(password: password, hint: value), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation))
}
case .rememberPassword:
guard case let .authorized(engine) = strongSelf.engine else {
return
}
guard let value = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !value.isEmpty else {
return
}
(strongSelf.displayNode as? TwoFactorDataInputScreenNode)?.isLoading = true
let _ = (engine.auth.requestTwoStepVerifiationSettings(password: value)
|> deliverOnMainQueue).start(error: { [weak self] error in
guard let strongSelf = self else {
return
}
(strongSelf.displayNode as? TwoFactorDataInputScreenNode)?.isLoading = false
let presentationData = strongSelf.presentationData
let text: String?
switch error {
case .limitExceeded:
text = presentationData.strings.LoginPassword_FloodError
case .invalidPassword:
text = nil
case .generic:
text = presentationData.strings.Login_UnknownError
}
if let text = text {
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
} else {
(strongSelf.displayNode as? TwoFactorDataInputScreenNode)?.onAction(success: false)
}
}, completed: { [weak self] in
guard let strongSelf = self else {
return
}
(strongSelf.displayNode as? TwoFactorDataInputScreenNode)?.isLoading = false
strongSelf.passwordRemembered?()
guard let navigationController = strongSelf.navigationController as? NavigationController else {
return
}
var controllers = navigationController.viewControllers.filter { controller in
if controller is TwoFactorAuthSplashScreen {
return false
}
if controller is TwoFactorDataInputScreen {
return false
}
return true
}
controllers.append(TwoFactorAuthSplashScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .remember))
navigationController.setViewControllers(controllers, animated: true)
})
}
}, skipAction: { [weak self] in
guard let strongSelf = self else {
@ -472,6 +547,156 @@ public final class TwoFactorDataInputScreen: ViewController {
}),
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})
]), in: .window(.root))
case .rememberPassword:
guard case let .authorized(engine) = strongSelf.engine else {
return
}
strongSelf.view.endEditing(true)
let sharedContext = strongSelf.sharedContext
let presentationData = sharedContext.currentPresentationData.with { $0 }
let navigationController = strongSelf.navigationController as? NavigationController
if !strongSelf.didSetupForgotData {
strongSelf.didSetupForgotData = true
strongSelf.forgotDataPromise.set(engine.auth.twoStepVerificationConfiguration() |> map(Optional.init))
}
let disposable = strongSelf.forgotDataDisposable
disposable.set((strongSelf.forgotDataPromise.get()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] configuration in
if let strongSelf = self, let configuration = configuration {
switch configuration {
case let .set(_, hasRecoveryEmail, _, _, pendingResetTimestamp):
if hasRecoveryEmail {
disposable.set((engine.auth.requestTwoStepVerificationPasswordRecoveryCode()
|> deliverOnMainQueue).start(next: { emailPattern in
var stateUpdated: ((SetupTwoStepVerificationStateUpdate) -> Void)?
let controller = TwoFactorDataInputScreen(sharedContext: sharedContext, engine: .authorized(engine), mode: .passwordRecoveryEmail(emailPattern: emailPattern, mode: .authorized), stateUpdated: { state in
stateUpdated?(state)
})
stateUpdated = { [weak controller] state in
controller?.view.endEditing(true)
controller?.dismiss()
switch state {
case .noPassword, .awaitingEmailConfirmation, .passwordSet:
controller?.dismiss()
navigationController?.filterController(strongSelf, animated: true)
case .pendingPasswordReset:
let _ = (engine.auth.twoStepVerificationConfiguration()
|> deliverOnMainQueue).start(next: { [weak self] configuration in
if let strongSelf = self {
if let navigationController = navigationController, let twoStepAuthSettingsController = strongSelf.twoStepAuthSettingsController?(configuration) {
var controllers = navigationController.viewControllers.filter { controller in
if controller is TwoFactorDataInputScreen {
return false
}
return true
}
controllers.append(twoStepAuthSettingsController)
navigationController.setViewControllers(controllers, animated: true)
}
}
})
}
}
strongSelf.push(controller)
}, error: { _ in
strongSelf.present(textAlertController(sharedContext: sharedContext, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}))
} else {
if let pendingResetTimestamp = pendingResetTimestamp {
let remainingSeconds = pendingResetTimestamp - Int32(Date().timeIntervalSince1970)
if remainingSeconds <= 0 {
let _ = (engine.auth.requestTwoStepPasswordReset()
|> deliverOnMainQueue).start(next: { result in
switch result {
case .done, .waitingForReset:
let _ = (engine.auth.twoStepVerificationConfiguration()
|> deliverOnMainQueue).start(next: { [weak self] configuration in
if let strongSelf = self {
if let navigationController = navigationController, let twoStepAuthSettingsController = strongSelf.twoStepAuthSettingsController?(configuration) {
var controllers = navigationController.viewControllers.filter { controller in
if controller is TwoFactorDataInputScreen {
return false
}
return true
}
controllers.append(twoStepAuthSettingsController)
navigationController.setViewControllers(controllers, animated: true)
}
}
})
case .declined:
break
case let .error(reason):
let text: String
switch reason {
case let .limitExceeded(retryAtTimestamp):
if let retryAtTimestamp = retryAtTimestamp {
let remainingSeconds = retryAtTimestamp - Int32(Date().timeIntervalSince1970)
text = presentationData.strings.TwoFactorSetup_ResetFloodWait(timeIntervalString(strings: presentationData.strings, value: remainingSeconds)).0
} else {
text = presentationData.strings.TwoStepAuth_FloodError
}
case .generic:
text = presentationData.strings.Login_UnknownError
}
strongSelf.present(textAlertController(sharedContext: sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
})
}
} else {
strongSelf.present(textAlertController(sharedContext: sharedContext, title: presentationData.strings.TwoStepAuth_RecoveryUnavailableResetTitle, text: presentationData.strings.TwoStepAuth_RecoveryUnavailableResetText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.TwoStepAuth_RecoveryUnavailableResetAction, action: {
let _ = (engine.auth.requestTwoStepPasswordReset()
|> deliverOnMainQueue).start(next: { result in
switch result {
case .done, .waitingForReset:
let _ = (engine.auth.twoStepVerificationConfiguration()
|> deliverOnMainQueue).start(next: { [weak self] configuration in
if let strongSelf = self {
if let navigationController = navigationController, let twoStepAuthSettingsController = strongSelf.twoStepAuthSettingsController?(configuration) {
var controllers = navigationController.viewControllers.filter { controller in
if controller is TwoFactorDataInputScreen {
return false
}
return true
}
controllers.append(twoStepAuthSettingsController)
navigationController.setViewControllers(controllers, animated: true)
}
}
})
case .declined:
break
case let .error(reason):
let text: String
switch reason {
case let .limitExceeded(retryAtTimestamp):
if let retryAtTimestamp = retryAtTimestamp {
let remainingSeconds = retryAtTimestamp - Int32(Date().timeIntervalSince1970)
text = presentationData.strings.TwoFactorSetup_ResetFloodWait(timeIntervalString(strings: presentationData.strings, value: remainingSeconds)).0
} else {
text = presentationData.strings.TwoStepAuth_FloodError
}
case .generic:
text = presentationData.strings.Login_UnknownError
}
strongSelf.present(textAlertController(sharedContext: sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
})
})]), in: .window(.root))
}
}
case .notSet:
break
}
}
}))
default:
break
}
@ -654,7 +879,7 @@ public final class TwoFactorDataInputScreen: ViewController {
}
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}, completed: { [weak self, weak statusController] in
}, completed: { [weak statusController] in
statusController?.dismiss()
})
case let .authorized(engine):
@ -762,11 +987,18 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega
private let hideButtonNode: HighlightableButtonNode
private let clearButtonNode: HighlightableButtonNode
private var activityIndicator: ActivityIndicator?
fileprivate var ignoreTextChanged: Bool = false
var isFocused: Bool {
get {
return self.inputNode.textField.isFirstResponder
}
set {
let _ = self.inputNode.textField.becomeFirstResponder()
}
}
var text: String {
get {
@ -777,6 +1009,43 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega
}
}
var isFailed: Bool = false {
didSet {
if self.isFailed != oldValue {
UIView.transition(with: self.view, duration: 0.2, options: [.transitionCrossDissolve, .curveEaseInOut]) {
self.inputNode.textField.textColor = self.isFailed ? self.theme.list.itemDestructiveColor : self.theme.list.freePlainInputField.primaryColor
self.hideButtonNode.setImage(generateTextHiddenImage(color: self.isFailed ? self.theme.list.itemDestructiveColor : self.theme.actionSheet.inputClearButtonColor, on: !self.inputNode.textField.isSecureTextEntry), for: [])
self.backgroundNode.image = self.isFailed ? generateStretchableFilledCircleImage(diameter: 20.0, color: self.theme.list.itemDestructiveColor.withAlphaComponent(0.1)) : generateStretchableFilledCircleImage(diameter: 20.0, color: self.theme.list.freePlainInputField.backgroundColor)
} completion: { _ in
}
}
}
}
var isLoading: Bool = false {
didSet {
if self.isLoading != oldValue {
if self.isLoading {
if self.activityIndicator == nil {
let activityIndicator = ActivityIndicator(type: .custom(self.theme.actionSheet.inputClearButtonColor, 24.0, 1.0, false))
self.activityIndicator = activityIndicator
self.addSubnode(activityIndicator)
if let size = self.validLayout {
self.updateLayout(size: size, transition: .immediate)
}
}
} else if let activityIndicator = self.activityIndicator {
self.activityIndicator = nil
activityIndicator.removeFromSupernode()
}
self.hideButtonNode.isHidden = self.isLoading
}
}
}
private var validLayout: CGSize?
init(theme: PresentationTheme, mode: TwoFactorDataInputTextNodeType, placeholder: String, focusUpdated: @escaping (TwoFactorDataInputTextNode, Bool) -> Void, next: @escaping (TwoFactorDataInputTextNode) -> Void, updated: @escaping (TwoFactorDataInputTextNode) -> Void, toggleTextHidden: @escaping (TwoFactorDataInputTextNode) -> Void) {
self.theme = theme
self.mode = mode
@ -874,6 +1143,9 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if self.isLoading {
return false
}
return true
}
@ -881,7 +1153,9 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega
if !self.ignoreTextChanged {
switch self.mode {
case .password:
break
if self.isFailed {
self.isFailed = false
}
default:
self.clearButtonNode.isHidden = self.text.isEmpty
}
@ -899,6 +1173,8 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
self.validLayout = size
let leftInset: CGFloat = 16.0
let rightInset: CGFloat = 38.0
@ -906,6 +1182,11 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega
transition.updateFrame(node: self.inputNode, frame: CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: size.width - leftInset - rightInset, height: size.height)))
transition.updateFrame(node: self.hideButtonNode, frame: CGRect(origin: CGPoint(x: size.width - rightInset - 4.0, y: 0.0), size: CGSize(width: rightInset + 4.0, height: size.height)))
transition.updateFrame(node: self.clearButtonNode, frame: CGRect(origin: CGPoint(x: size.width - rightInset - 4.0, y: 0.0), size: CGSize(width: rightInset + 4.0, height: size.height)))
if let activityIndicator = self.activityIndicator {
let indicatorSize = activityIndicator.measure(CGSize(width: 24.0, height: 24.0))
transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: size.width - rightInset + 6.0, y: floorToScreenPixels((size.height - indicatorSize.height) / 2.0) + 1.0), size: indicatorSize))
}
}
func focus() {
@ -913,7 +1194,7 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega
}
func updateTextHidden(_ value: Bool) {
self.hideButtonNode.setImage(generateTextHiddenImage(color: self.theme.actionSheet.inputClearButtonColor, on: !value), for: [])
self.hideButtonNode.setImage(generateTextHiddenImage(color: self.isFailed ? self.theme.list.itemDestructiveColor : self.theme.actionSheet.inputClearButtonColor, on: !value), for: [])
let text = self.inputNode.textField.text ?? ""
self.inputNode.textField.isSecureTextEntry = value
if value {
@ -954,12 +1235,18 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
private let inputNodes: [TwoFactorDataInputTextNode]
private let buttonNode: SolidRoundedButtonNode
private var navigationHeight: CGFloat?
private var validLayout: (ContainerViewLayout, CGFloat)?
var inputText: [String] {
return self.inputNodes.map { $0.text }
}
var isLoading: Bool = false {
didSet {
self.inputNodes.first?.isLoading = self.isLoading
}
}
init(presentationData: PresentationData, mode: TwoFactorDataInputMode, action: @escaping () -> Void, skipAction: @escaping () -> Void, changeEmailAction: @escaping () -> Void, resendCodeAction: @escaping () -> Void) {
self.presentationData = presentationData
self.mode = mode
@ -994,6 +1281,13 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
animatedStickerNode.visibility = true
self.animatedStickerNode = animatedStickerNode
}
case .rememberPassword:
if let path = getAppBundle().path(forResource: "TwoFactorSetupRemember", ofType: "tgs") {
let animatedStickerNode = AnimatedStickerNode()
animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 272, height: 272, playbackMode: .count(3), mode: .direct(cachePathPrefix: nil))
animatedStickerNode.visibility = true
self.animatedStickerNode = animatedStickerNode
}
}
let title: String
@ -1156,6 +1450,24 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
toggleTextHidden?(node)
}),
]
case .rememberPassword:
title = presentationData.strings.TwoFactorRemember_Title
text = NSAttributedString(string: presentationData.strings.TwoFactorRemember_Text, font: Font.regular(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
buttonText = presentationData.strings.TwoFactorRemember_CheckPassword
skipActionText = presentationData.strings.TwoFactorRemember_Forgot
changeEmailActionText = ""
resendCodeActionText = ""
inputNodes = [
TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(confirmation: false), placeholder: presentationData.strings.TwoFactorRemember_Placeholder, focusUpdated: { node, focused in
focusUpdated?(node, focused)
}, next: { node in
next?(node)
}, updated: { node in
updated?(node)
}, toggleTextHidden: { node in
toggleTextHidden?(node)
})
]
}
self.titleNode = ImmediateTextNode()
@ -1200,6 +1512,10 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
super.init()
if case .rememberPassword = mode {
self.buttonNode.alpha = 0.0
}
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.addSubnode(self.scrollNode)
@ -1374,6 +1690,30 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
strongSelf.skipActionButtonNode.isHidden = hasText
case .password:
break
case .rememberPassword:
let hasText = strongSelf.inputNodes.contains(where: { !$0.text.isEmpty })
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
transition.updateAlpha(node: strongSelf.buttonNode, alpha: hasText ? 1.0 : 0.0)
transition.updateAlpha(node: strongSelf.skipActionTitleNode, alpha: hasText ? 0.0 : 1.0)
strongSelf.skipActionButtonNode.isHidden = hasText
if strongSelf.textNode.attributedText?.string != strongSelf.presentationData.strings.TwoFactorRemember_Text {
if let snapshotView = strongSelf.textNode.view.snapshotContentTree() {
snapshotView.frame = strongSelf.textNode.view.frame
strongSelf.textNode.view.superview?.addSubview(snapshotView)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
strongSelf.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
strongSelf.textNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.TwoFactorRemember_Text, font: Font.regular(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate)
}
}
}
updateAnimations()
}
@ -1382,7 +1722,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
return
}
switch strongSelf.mode {
case .password, .passwordRecovery:
case .password, .passwordRecovery, .rememberPassword:
textHidden = !textHidden
for node in strongSelf.inputNodes {
node.updateTextHidden(textHidden)
@ -1395,6 +1735,10 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
self.inputNodes.first.flatMap { updated?($0) }
}
func focus() {
self.inputNodes.first?.isFocused = true
}
@objc private func skipActionPressed() {
self.skipAction()
}
@ -1426,8 +1770,42 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
func scrollViewDidScroll(_ scrollView: UIScrollView) {
}
func onAction(success: Bool) {
switch self.mode {
case .rememberPassword:
if !success {
self.skipActionTitleNode.isHidden = false
self.skipActionButtonNode.isHidden = false
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
transition.updateAlpha(node: self.buttonNode, alpha: 0.0)
transition.updateAlpha(node: self.skipActionTitleNode, alpha: 1.0)
if let snapshotView = self.textNode.view.snapshotContentTree() {
snapshotView.frame = self.textNode.view.frame
self.textNode.view.superview?.addSubview(snapshotView)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.TwoFactorRemember_WrongPassword, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemDestructiveColor)
self.inputNodes.first?.isFailed = true
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate)
}
}
default:
break
}
}
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.navigationHeight = navigationHeight
self.validLayout = (layout, navigationHeight)
let contentAreaSize = layout.size
let availableAreaSize = CGSize(width: layout.size.width, height: layout.size.height - layout.insets(options: [.input]).bottom)
@ -1538,7 +1916,8 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
let _ = self.buttonNode.updateLayout(width: buttonFrame.width, transition: transition)
var skipButtonFrame = buttonFrame
if !self.buttonNode.isHidden {
if case .rememberPassword = self.mode {
} else if !self.buttonNode.isHidden {
skipButtonFrame.origin.y += skipButtonFrame.height
}

View File

@ -16,6 +16,7 @@ public enum TwoFactorAuthSplashMode {
case intro
case done
case recoveryDone(recoveredAccountData: RecoveredAccountData?, syncContacts: Bool, isPasswordSet: Bool)
case remember
}
public final class TwoFactorAuthSplashScreen: ViewController {
@ -42,7 +43,18 @@ public final class TwoFactorAuthSplashScreen: ViewController {
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.navigationBar?.intrinsicCanTransitionInline = false
let hasBackButton: Bool
switch mode {
case .done, .remember:
hasBackButton = false
default:
hasBackButton = true
}
if hasBackButton {
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
} else {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: ASDisplayNode())
}
}
required init(coder aDecoder: NSCoder) {
@ -61,7 +73,7 @@ public final class TwoFactorAuthSplashScreen: ViewController {
case .intro:
strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .password, stateUpdated: { _ in
}, presentation: strongSelf.navigationPresentation))
case .done:
case .done, .remember:
guard let navigationController = strongSelf.navigationController as? NavigationController else {
return
}
@ -169,6 +181,16 @@ private final class TwoFactorAuthSplashScreenNode: ViewControllerTracingNode {
self.animationSize = CGSize(width: 124.0, height: 124.0)
self.animationNode.visibility = true
}
case .remember:
title = self.presentationData.strings.TwoFactorRemember_Done_Title
texts = [NSAttributedString(string: self.presentationData.strings.TwoFactorRemember_Done_Text, font: textFont, textColor: textColor)]
buttonText = self.presentationData.strings.TwoFactorRemember_Done_Action
if let path = getAppBundle().path(forResource: "TwoFactorSetupRememberSuccess", ofType: "tgs") {
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 248, height: 248, mode: .direct(cachePathPrefix: nil))
self.animationSize = CGSize(width: 124.0, height: 124.0)
self.animationNode.visibility = true
}
}
self.titleNode = ImmediateTextNode()

View File

@ -253,21 +253,21 @@ private func twoStepVerificationUnlockSettingsControllerEntries(presentationData
return entries
}
enum TwoStepVerificationUnlockSettingsControllerMode {
public enum TwoStepVerificationUnlockSettingsControllerMode {
case access(intro: Bool, data: Signal<TwoStepVerificationUnlockSettingsControllerData, NoError>?)
case manage(password: String, email: String, pendingEmail: TwoStepVerificationPendingEmail?, hasSecureValues: Bool)
}
struct TwoStepVerificationPendingEmailState: Equatable {
public struct TwoStepVerificationPendingEmailState: Equatable {
let password: String?
let email: TwoStepVerificationPendingEmail
}
enum TwoStepVerificationAccessConfiguration: Equatable {
public enum TwoStepVerificationAccessConfiguration: Equatable {
case notSet(pendingEmail: TwoStepVerificationPendingEmailState?)
case set(hint: String, hasRecoveryEmail: Bool, hasSecureValues: Bool, pendingResetTimestamp: Int32?)
init(configuration: TwoStepVerificationConfiguration, password: String?) {
public init(configuration: TwoStepVerificationConfiguration, password: String?) {
switch configuration {
case let .notSet(pendingEmail):
self = .notSet(pendingEmail: pendingEmail.flatMap({ TwoStepVerificationPendingEmailState(password: password, email: $0) }))
@ -277,12 +277,12 @@ enum TwoStepVerificationAccessConfiguration: Equatable {
}
}
enum TwoStepVerificationUnlockSettingsControllerData: Equatable {
public enum TwoStepVerificationUnlockSettingsControllerData: Equatable {
case access(configuration: TwoStepVerificationAccessConfiguration?)
case manage(password: String, emailSet: Bool, pendingEmail: TwoStepVerificationPendingEmail?, hasSecureValues: Bool)
}
func twoStepVerificationUnlockSettingsController(context: AccountContext, mode: TwoStepVerificationUnlockSettingsControllerMode, openSetupPasswordImmediately: Bool = false) -> ViewController {
public func twoStepVerificationUnlockSettingsController(context: AccountContext, mode: TwoStepVerificationUnlockSettingsControllerMode, openSetupPasswordImmediately: Bool = false) -> ViewController {
let initialState = TwoStepVerificationUnlockSettingsControllerState()
let statePromise = ValuePromise(initialState, ignoreRepeated: true)

View File

@ -120,7 +120,11 @@ private final class PeerInfoScreenActionItemNode: PeerInfoScreenItemNode {
self.iconNode.removeFromSupernode()
}
if self.textNode.frame.width != textFrame.width {
self.textNode.frame = textFrame
} else {
transition.updateFrame(node: self.textNode, frame: textFrame)
}
let highlightNodeOffset: CGFloat = topItem == nil ? 0.0 : UIScreenPixel
self.selectionNode.update(size: CGSize(width: width, height: height + highlightNodeOffset), theme: presentationData.theme, transition: transition)

View File

@ -60,6 +60,7 @@ import HashtagSearchUI
import ActionSheetPeerItem
import TelegramCallsUI
import PeerInfoAvatarListNode
import PasswordSetupUI
protocol PeerInfoScreenItem: class {
var id: AnyHashable { get }
@ -715,7 +716,7 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
let _ = dismissServerProvidedSuggestion(account: context.account, suggestion: .validatePassword).start()
}))
items[.phone]!.append(PeerInfoScreenActionItem(id: 2, text: presentationData.strings.Settings_TryEnterPassword, action: {
interaction.openSettings(.phoneNumber)
interaction.openSettings(.rememberPassword)
}))
}
@ -2939,16 +2940,19 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
var currentVideoCallsAvailable: Bool?
if let previousCachedData = previousData?.cachedData as? CachedUserData, let cachedData = data.cachedData as? CachedUserData {
previousCallsPrivate = previousCachedData.callsPrivate ?? false
previousCallsPrivate = previousCachedData.callsPrivate
currentCallsPrivate = cachedData.callsPrivate
previousVideoCallsAvailable = previousCachedData.videoCallsAvailable ?? true
previousVideoCallsAvailable = previousCachedData.videoCallsAvailable
currentVideoCallsAvailable = cachedData.videoCallsAvailable
}
if let previousSuggestPhoneNumberConfirmation = previousData?.globalSettings?.suggestPhoneNumberConfirmation, previousSuggestPhoneNumberConfirmation != data.globalSettings?.suggestPhoneNumberConfirmation {
infoUpdated = true
}
if let previousSuggestPasswordConfirmation = previousData?.globalSettings?.suggestPasswordConfirmation, previousSuggestPasswordConfirmation != data.globalSettings?.suggestPasswordConfirmation {
infoUpdated = true
}
if previousCallsPrivate != currentCallsPrivate || previousVideoCallsAvailable != currentVideoCallsAvailable {
infoUpdated = true
}
@ -5454,7 +5458,16 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
}
case .rememberPassword:
break
let context = self.context
let controller = TwoFactorDataInputScreen(sharedContext: self.context.sharedContext, engine: .authorized(self.context.engine), mode: .rememberPassword, stateUpdated: { _ in
}, presentation: .modalInLargeLayout)
controller.twoStepAuthSettingsController = { configuration in
return twoStepVerificationUnlockSettingsController(context: context, mode: .access(intro: false, data: .single(TwoStepVerificationUnlockSettingsControllerData.access(configuration: TwoStepVerificationAccessConfiguration(configuration: configuration, password: nil)))))
}
controller.passwordRemembered = {
let _ = dismissServerProvidedSuggestion(account: context.account, suggestion: .validatePassword).start()
}
self.controller?.push(controller)
}
}
@ -6661,11 +6674,12 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen {
|> map { presentationData, notificationsAuthorizationStatus, notificationsWarningSuppressed, suggestions, accountTabBarAvatar, accountTabBarAvatarBadge -> (String, UIImage?, UIImage?, String?) in
let notificationsWarning = shouldDisplayNotificationsPermissionWarning(status: notificationsAuthorizationStatus, suppressed: notificationsWarningSuppressed)
let phoneNumberWarning = suggestions.contains(.validatePhoneNumber)
let passwordWarning = suggestions.contains(.validatePassword)
var otherAccountsBadge: String?
if accountTabBarAvatarBadge > 0 {
otherAccountsBadge = compactNumericCountString(Int(accountTabBarAvatarBadge), decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
}
return (presentationData.strings.Settings_Title, accountTabBarAvatar?.0 ?? icon, accountTabBarAvatar?.1 ?? icon, notificationsWarning || phoneNumberWarning ? "!" : otherAccountsBadge)
return (presentationData.strings.Settings_Title, accountTabBarAvatar?.0 ?? icon, accountTabBarAvatar?.1 ?? icon, notificationsWarning || phoneNumberWarning || passwordWarning ? "!" : otherAccountsBadge)
}
self.tabBarItemDisposable = (tabBarItem |> deliverOnMainQueue).start(next: { [weak self] title, image, selectedImage, badgeValue in