mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 01:10:09 +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.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.";
|
"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.contentGridNode)
|
||||||
self.contentContainerNode.addSubnode(self.createActionSeparatorNode)
|
self.contentContainerNode.addSubnode(self.createActionSeparatorNode)
|
||||||
self.contentContainerNode.addSubnode(self.createActionButtonNode)
|
self.contentContainerNode.addSubnode(self.createActionButtonNode)
|
||||||
self.contentContainerNode.addSubnode(self.addToExistingActionSeparatorNode)
|
// self.contentContainerNode.addSubnode(self.addToExistingActionSeparatorNode)
|
||||||
self.contentContainerNode.addSubnode(self.addToExistingActionButtonNode)
|
// self.contentContainerNode.addSubnode(self.addToExistingActionButtonNode)
|
||||||
self.wrappingScrollNode.addSubnode(self.contentTitleNode)
|
self.wrappingScrollNode.addSubnode(self.contentTitleNode)
|
||||||
self.wrappingScrollNode.addSubnode(self.contentSeparatorNode)
|
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 PresentationDataUtils
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import AnimatedStickerNode
|
import AnimatedStickerNode
|
||||||
|
import ActivityIndicator
|
||||||
|
|
||||||
public enum TwoFactorDataInputMode {
|
public enum TwoFactorDataInputMode {
|
||||||
public struct Recovery {
|
public struct Recovery {
|
||||||
@ -40,6 +41,7 @@ public enum TwoFactorDataInputMode {
|
|||||||
case updateEmailAddress(password: String)
|
case updateEmailAddress(password: String)
|
||||||
case emailConfirmation(passwordAndHint: (String, String)?, emailPattern: String, codeLength: Int?)
|
case emailConfirmation(passwordAndHint: (String, String)?, emailPattern: String, codeLength: Int?)
|
||||||
case passwordHint(recovery: Recovery?, password: String)
|
case passwordHint(recovery: Recovery?, password: String)
|
||||||
|
case rememberPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class TwoFactorDataInputScreen: ViewController {
|
public final class TwoFactorDataInputScreen: ViewController {
|
||||||
@ -49,9 +51,16 @@ public final class TwoFactorDataInputScreen: ViewController {
|
|||||||
private let mode: TwoFactorDataInputMode
|
private let mode: TwoFactorDataInputMode
|
||||||
private let stateUpdated: (SetupTwoStepVerificationStateUpdate) -> Void
|
private let stateUpdated: (SetupTwoStepVerificationStateUpdate) -> Void
|
||||||
private let actionDisposable = MetaDisposable()
|
private let actionDisposable = MetaDisposable()
|
||||||
|
|
||||||
|
private let forgotDataDisposable = MetaDisposable()
|
||||||
|
private let forgotDataPromise = Promise<TwoStepVerificationConfiguration?>(nil)
|
||||||
|
private var didSetupForgotData = false
|
||||||
|
|
||||||
public var passwordRecoveryFailed: (() -> Void)?
|
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) {
|
public init(sharedContext: SharedAccountContext, engine: SomeTelegramEngine, mode: TwoFactorDataInputMode, stateUpdated: @escaping (SetupTwoStepVerificationStateUpdate) -> Void, presentation: ViewControllerNavigationPresentation = .modalInLargeLayout) {
|
||||||
self.sharedContext = sharedContext
|
self.sharedContext = sharedContext
|
||||||
self.engine = engine
|
self.engine = engine
|
||||||
@ -79,12 +88,21 @@ public final class TwoFactorDataInputScreen: ViewController {
|
|||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.actionDisposable.dispose()
|
self.actionDisposable.dispose()
|
||||||
|
self.forgotDataDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func backPressed() {
|
@objc private func backPressed() {
|
||||||
self.dismiss()
|
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() {
|
override public func loadDisplayNode() {
|
||||||
self.displayNode = TwoFactorDataInputScreenNode(presentationData: self.presentationData, mode: self.mode, action: { [weak self] in
|
self.displayNode = TwoFactorDataInputScreenNode(presentationData: self.presentationData, mode: self.mode, action: { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
@ -394,6 +412,63 @@ public final class TwoFactorDataInputScreen: ViewController {
|
|||||||
} else {
|
} else {
|
||||||
strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailAddress(password: password, hint: value), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation))
|
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
|
}, skipAction: { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
@ -472,6 +547,156 @@ public final class TwoFactorDataInputScreen: ViewController {
|
|||||||
}),
|
}),
|
||||||
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})
|
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})
|
||||||
]), in: .window(.root))
|
]), 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:
|
default:
|
||||||
break
|
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))
|
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()
|
statusController?.dismiss()
|
||||||
})
|
})
|
||||||
case let .authorized(engine):
|
case let .authorized(engine):
|
||||||
@ -762,10 +987,17 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega
|
|||||||
private let hideButtonNode: HighlightableButtonNode
|
private let hideButtonNode: HighlightableButtonNode
|
||||||
private let clearButtonNode: HighlightableButtonNode
|
private let clearButtonNode: HighlightableButtonNode
|
||||||
|
|
||||||
|
private var activityIndicator: ActivityIndicator?
|
||||||
|
|
||||||
fileprivate var ignoreTextChanged: Bool = false
|
fileprivate var ignoreTextChanged: Bool = false
|
||||||
|
|
||||||
var isFocused: Bool {
|
var isFocused: Bool {
|
||||||
return self.inputNode.textField.isFirstResponder
|
get {
|
||||||
|
return self.inputNode.textField.isFirstResponder
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
let _ = self.inputNode.textField.becomeFirstResponder()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var text: String {
|
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) {
|
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.theme = theme
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
@ -874,6 +1143,9 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega
|
|||||||
}
|
}
|
||||||
|
|
||||||
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||||
|
if self.isLoading {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -881,7 +1153,9 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega
|
|||||||
if !self.ignoreTextChanged {
|
if !self.ignoreTextChanged {
|
||||||
switch self.mode {
|
switch self.mode {
|
||||||
case .password:
|
case .password:
|
||||||
break
|
if self.isFailed {
|
||||||
|
self.isFailed = false
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
self.clearButtonNode.isHidden = self.text.isEmpty
|
self.clearButtonNode.isHidden = self.text.isEmpty
|
||||||
}
|
}
|
||||||
@ -899,6 +1173,8 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.validLayout = size
|
||||||
|
|
||||||
let leftInset: CGFloat = 16.0
|
let leftInset: CGFloat = 16.0
|
||||||
let rightInset: CGFloat = 38.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.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.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)))
|
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() {
|
func focus() {
|
||||||
@ -913,7 +1194,7 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateTextHidden(_ value: Bool) {
|
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 ?? ""
|
let text = self.inputNode.textField.text ?? ""
|
||||||
self.inputNode.textField.isSecureTextEntry = value
|
self.inputNode.textField.isSecureTextEntry = value
|
||||||
if value {
|
if value {
|
||||||
@ -954,12 +1235,18 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
|
|||||||
private let inputNodes: [TwoFactorDataInputTextNode]
|
private let inputNodes: [TwoFactorDataInputTextNode]
|
||||||
private let buttonNode: SolidRoundedButtonNode
|
private let buttonNode: SolidRoundedButtonNode
|
||||||
|
|
||||||
private var navigationHeight: CGFloat?
|
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||||
|
|
||||||
var inputText: [String] {
|
var inputText: [String] {
|
||||||
return self.inputNodes.map { $0.text }
|
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) {
|
init(presentationData: PresentationData, mode: TwoFactorDataInputMode, action: @escaping () -> Void, skipAction: @escaping () -> Void, changeEmailAction: @escaping () -> Void, resendCodeAction: @escaping () -> Void) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
@ -994,6 +1281,13 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
|
|||||||
animatedStickerNode.visibility = true
|
animatedStickerNode.visibility = true
|
||||||
self.animatedStickerNode = animatedStickerNode
|
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
|
let title: String
|
||||||
@ -1156,6 +1450,24 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
|
|||||||
toggleTextHidden?(node)
|
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()
|
self.titleNode = ImmediateTextNode()
|
||||||
@ -1200,6 +1512,10 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
|
|||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
if case .rememberPassword = mode {
|
||||||
|
self.buttonNode.alpha = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||||
|
|
||||||
self.addSubnode(self.scrollNode)
|
self.addSubnode(self.scrollNode)
|
||||||
@ -1374,6 +1690,30 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
|
|||||||
strongSelf.skipActionButtonNode.isHidden = hasText
|
strongSelf.skipActionButtonNode.isHidden = hasText
|
||||||
case .password:
|
case .password:
|
||||||
break
|
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()
|
updateAnimations()
|
||||||
}
|
}
|
||||||
@ -1382,7 +1722,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch strongSelf.mode {
|
switch strongSelf.mode {
|
||||||
case .password, .passwordRecovery:
|
case .password, .passwordRecovery, .rememberPassword:
|
||||||
textHidden = !textHidden
|
textHidden = !textHidden
|
||||||
for node in strongSelf.inputNodes {
|
for node in strongSelf.inputNodes {
|
||||||
node.updateTextHidden(textHidden)
|
node.updateTextHidden(textHidden)
|
||||||
@ -1395,6 +1735,10 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
|
|||||||
self.inputNodes.first.flatMap { updated?($0) }
|
self.inputNodes.first.flatMap { updated?($0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func focus() {
|
||||||
|
self.inputNodes.first?.isFocused = true
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func skipActionPressed() {
|
@objc private func skipActionPressed() {
|
||||||
self.skipAction()
|
self.skipAction()
|
||||||
}
|
}
|
||||||
@ -1426,8 +1770,42 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
|
|||||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
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) {
|
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
self.navigationHeight = navigationHeight
|
self.validLayout = (layout, navigationHeight)
|
||||||
|
|
||||||
let contentAreaSize = layout.size
|
let contentAreaSize = layout.size
|
||||||
let availableAreaSize = CGSize(width: layout.size.width, height: layout.size.height - layout.insets(options: [.input]).bottom)
|
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)
|
let _ = self.buttonNode.updateLayout(width: buttonFrame.width, transition: transition)
|
||||||
|
|
||||||
var skipButtonFrame = buttonFrame
|
var skipButtonFrame = buttonFrame
|
||||||
if !self.buttonNode.isHidden {
|
if case .rememberPassword = self.mode {
|
||||||
|
} else if !self.buttonNode.isHidden {
|
||||||
skipButtonFrame.origin.y += skipButtonFrame.height
|
skipButtonFrame.origin.y += skipButtonFrame.height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,7 @@ public enum TwoFactorAuthSplashMode {
|
|||||||
case intro
|
case intro
|
||||||
case done
|
case done
|
||||||
case recoveryDone(recoveredAccountData: RecoveredAccountData?, syncContacts: Bool, isPasswordSet: Bool)
|
case recoveryDone(recoveredAccountData: RecoveredAccountData?, syncContacts: Bool, isPasswordSet: Bool)
|
||||||
|
case remember
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class TwoFactorAuthSplashScreen: ViewController {
|
public final class TwoFactorAuthSplashScreen: ViewController {
|
||||||
@ -42,7 +43,18 @@ public final class TwoFactorAuthSplashScreen: ViewController {
|
|||||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||||
self.navigationBar?.intrinsicCanTransitionInline = false
|
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) {
|
required init(coder aDecoder: NSCoder) {
|
||||||
@ -61,7 +73,7 @@ public final class TwoFactorAuthSplashScreen: ViewController {
|
|||||||
case .intro:
|
case .intro:
|
||||||
strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .password, stateUpdated: { _ in
|
strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .password, stateUpdated: { _ in
|
||||||
}, presentation: strongSelf.navigationPresentation))
|
}, presentation: strongSelf.navigationPresentation))
|
||||||
case .done:
|
case .done, .remember:
|
||||||
guard let navigationController = strongSelf.navigationController as? NavigationController else {
|
guard let navigationController = strongSelf.navigationController as? NavigationController else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -169,6 +181,16 @@ private final class TwoFactorAuthSplashScreenNode: ViewControllerTracingNode {
|
|||||||
self.animationSize = CGSize(width: 124.0, height: 124.0)
|
self.animationSize = CGSize(width: 124.0, height: 124.0)
|
||||||
self.animationNode.visibility = true
|
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()
|
self.titleNode = ImmediateTextNode()
|
||||||
|
|||||||
@ -253,21 +253,21 @@ private func twoStepVerificationUnlockSettingsControllerEntries(presentationData
|
|||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
enum TwoStepVerificationUnlockSettingsControllerMode {
|
public enum TwoStepVerificationUnlockSettingsControllerMode {
|
||||||
case access(intro: Bool, data: Signal<TwoStepVerificationUnlockSettingsControllerData, NoError>?)
|
case access(intro: Bool, data: Signal<TwoStepVerificationUnlockSettingsControllerData, NoError>?)
|
||||||
case manage(password: String, email: String, pendingEmail: TwoStepVerificationPendingEmail?, hasSecureValues: Bool)
|
case manage(password: String, email: String, pendingEmail: TwoStepVerificationPendingEmail?, hasSecureValues: Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TwoStepVerificationPendingEmailState: Equatable {
|
public struct TwoStepVerificationPendingEmailState: Equatable {
|
||||||
let password: String?
|
let password: String?
|
||||||
let email: TwoStepVerificationPendingEmail
|
let email: TwoStepVerificationPendingEmail
|
||||||
}
|
}
|
||||||
|
|
||||||
enum TwoStepVerificationAccessConfiguration: Equatable {
|
public enum TwoStepVerificationAccessConfiguration: Equatable {
|
||||||
case notSet(pendingEmail: TwoStepVerificationPendingEmailState?)
|
case notSet(pendingEmail: TwoStepVerificationPendingEmailState?)
|
||||||
case set(hint: String, hasRecoveryEmail: Bool, hasSecureValues: Bool, pendingResetTimestamp: Int32?)
|
case set(hint: String, hasRecoveryEmail: Bool, hasSecureValues: Bool, pendingResetTimestamp: Int32?)
|
||||||
|
|
||||||
init(configuration: TwoStepVerificationConfiguration, password: String?) {
|
public init(configuration: TwoStepVerificationConfiguration, password: String?) {
|
||||||
switch configuration {
|
switch configuration {
|
||||||
case let .notSet(pendingEmail):
|
case let .notSet(pendingEmail):
|
||||||
self = .notSet(pendingEmail: pendingEmail.flatMap({ TwoStepVerificationPendingEmailState(password: password, email: $0) }))
|
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 access(configuration: TwoStepVerificationAccessConfiguration?)
|
||||||
case manage(password: String, emailSet: Bool, pendingEmail: TwoStepVerificationPendingEmail?, hasSecureValues: Bool)
|
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 initialState = TwoStepVerificationUnlockSettingsControllerState()
|
||||||
|
|
||||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
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()
|
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
|
let highlightNodeOffset: CGFloat = topItem == nil ? 0.0 : UIScreenPixel
|
||||||
self.selectionNode.update(size: CGSize(width: width, height: height + highlightNodeOffset), theme: presentationData.theme, transition: transition)
|
self.selectionNode.update(size: CGSize(width: width, height: height + highlightNodeOffset), theme: presentationData.theme, transition: transition)
|
||||||
|
|||||||
@ -60,6 +60,7 @@ import HashtagSearchUI
|
|||||||
import ActionSheetPeerItem
|
import ActionSheetPeerItem
|
||||||
import TelegramCallsUI
|
import TelegramCallsUI
|
||||||
import PeerInfoAvatarListNode
|
import PeerInfoAvatarListNode
|
||||||
|
import PasswordSetupUI
|
||||||
|
|
||||||
protocol PeerInfoScreenItem: class {
|
protocol PeerInfoScreenItem: class {
|
||||||
var id: AnyHashable { get }
|
var id: AnyHashable { get }
|
||||||
@ -715,7 +716,7 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
|
|||||||
let _ = dismissServerProvidedSuggestion(account: context.account, suggestion: .validatePassword).start()
|
let _ = dismissServerProvidedSuggestion(account: context.account, suggestion: .validatePassword).start()
|
||||||
}))
|
}))
|
||||||
items[.phone]!.append(PeerInfoScreenActionItem(id: 2, text: presentationData.strings.Settings_TryEnterPassword, action: {
|
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?
|
var currentVideoCallsAvailable: Bool?
|
||||||
|
|
||||||
if let previousCachedData = previousData?.cachedData as? CachedUserData, let cachedData = data.cachedData as? CachedUserData {
|
if let previousCachedData = previousData?.cachedData as? CachedUserData, let cachedData = data.cachedData as? CachedUserData {
|
||||||
previousCallsPrivate = previousCachedData.callsPrivate ?? false
|
previousCallsPrivate = previousCachedData.callsPrivate
|
||||||
currentCallsPrivate = cachedData.callsPrivate
|
currentCallsPrivate = cachedData.callsPrivate
|
||||||
|
|
||||||
previousVideoCallsAvailable = previousCachedData.videoCallsAvailable ?? true
|
previousVideoCallsAvailable = previousCachedData.videoCallsAvailable
|
||||||
currentVideoCallsAvailable = cachedData.videoCallsAvailable
|
currentVideoCallsAvailable = cachedData.videoCallsAvailable
|
||||||
}
|
}
|
||||||
|
|
||||||
if let previousSuggestPhoneNumberConfirmation = previousData?.globalSettings?.suggestPhoneNumberConfirmation, previousSuggestPhoneNumberConfirmation != data.globalSettings?.suggestPhoneNumberConfirmation {
|
if let previousSuggestPhoneNumberConfirmation = previousData?.globalSettings?.suggestPhoneNumberConfirmation, previousSuggestPhoneNumberConfirmation != data.globalSettings?.suggestPhoneNumberConfirmation {
|
||||||
infoUpdated = true
|
infoUpdated = true
|
||||||
}
|
}
|
||||||
|
if let previousSuggestPasswordConfirmation = previousData?.globalSettings?.suggestPasswordConfirmation, previousSuggestPasswordConfirmation != data.globalSettings?.suggestPasswordConfirmation {
|
||||||
|
infoUpdated = true
|
||||||
|
}
|
||||||
if previousCallsPrivate != currentCallsPrivate || previousVideoCallsAvailable != currentVideoCallsAvailable {
|
if previousCallsPrivate != currentCallsPrivate || previousVideoCallsAvailable != currentVideoCallsAvailable {
|
||||||
infoUpdated = true
|
infoUpdated = true
|
||||||
}
|
}
|
||||||
@ -5454,7 +5458,16 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .rememberPassword:
|
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
|
|> map { presentationData, notificationsAuthorizationStatus, notificationsWarningSuppressed, suggestions, accountTabBarAvatar, accountTabBarAvatarBadge -> (String, UIImage?, UIImage?, String?) in
|
||||||
let notificationsWarning = shouldDisplayNotificationsPermissionWarning(status: notificationsAuthorizationStatus, suppressed: notificationsWarningSuppressed)
|
let notificationsWarning = shouldDisplayNotificationsPermissionWarning(status: notificationsAuthorizationStatus, suppressed: notificationsWarningSuppressed)
|
||||||
let phoneNumberWarning = suggestions.contains(.validatePhoneNumber)
|
let phoneNumberWarning = suggestions.contains(.validatePhoneNumber)
|
||||||
|
let passwordWarning = suggestions.contains(.validatePassword)
|
||||||
var otherAccountsBadge: String?
|
var otherAccountsBadge: String?
|
||||||
if accountTabBarAvatarBadge > 0 {
|
if accountTabBarAvatarBadge > 0 {
|
||||||
otherAccountsBadge = compactNumericCountString(Int(accountTabBarAvatarBadge), decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
|
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
|
self.tabBarItemDisposable = (tabBarItem |> deliverOnMainQueue).start(next: { [weak self] title, image, selectedImage, badgeValue in
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user