mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
Password remembering improvements
This commit is contained in:
parent
b28b67eef2
commit
3519a216c5
@ -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";
|
||||
|
@ -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)
|
||||
|
||||
|
BIN
submodules/PasswordSetupUI/Resources/TwoFactorSetupRemember.tgs
Normal file
BIN
submodules/PasswordSetupUI/Resources/TwoFactorSetupRemember.tgs
Normal file
Binary file not shown.
Binary file not shown.
@ -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 {
|
||||
@ -49,9 +51,16 @@ public final class TwoFactorDataInputScreen: ViewController {
|
||||
private let mode: TwoFactorDataInputMode
|
||||
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,10 +987,17 @@ 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 {
|
||||
return self.inputNode.textField.isFirstResponder
|
||||
get {
|
||||
return self.inputNode.textField.isFirstResponder
|
||||
}
|
||||
set {
|
||||
let _ = self.inputNode.textField.becomeFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
var text: String {
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||
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()
|
||||
|
@ -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)
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -120,7 +120,11 @@ private final class PeerInfoScreenActionItemNode: PeerInfoScreenItemNode {
|
||||
self.iconNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.textNode, frame: textFrame)
|
||||
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)
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user