Two step verification setup improvements

This commit is contained in:
Ali 2019-11-05 22:20:14 +04:00
parent 90ea94d308
commit 791569951f
4 changed files with 403 additions and 316 deletions

View File

@ -6,14 +6,8 @@ import RLottieBinding
import AppBundle
import GZip
enum ManagedAnimationTrackState {
case intro
case loop
case outro
}
private final class ManagedAnimationState {
var item: ManagedAnimationItem
let item: ManagedAnimationItem
private let instance: LottieInstance
@ -21,33 +15,40 @@ private final class ManagedAnimationState {
let fps: Double
var startTime: Double?
var trackState: ManagedAnimationTrackState?
var trackingFrameState: (Int, Int)?
var frameIndex: Int?
private let renderContext: DrawingContext
init?(item: ManagedAnimationItem) {
guard let path = getAppBundle().path(forResource: item.name, ofType: "tgs") else {
return nil
}
guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else {
return nil
}
guard let unpackedData = TGGUnzipData(data, 5 * 1024 * 1024) else {
return nil
}
guard let instance = LottieInstance(data: unpackedData, cacheKey: item.name) else {
return nil
init?(displaySize: CGSize, item: ManagedAnimationItem, current: ManagedAnimationState?) {
let resolvedInstance: LottieInstance
let renderContext: DrawingContext
if let current = current {
resolvedInstance = current.instance
renderContext = current.renderContext
} else {
guard let path = getAppBundle().path(forResource: item.name, ofType: "tgs") else {
return nil
}
guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else {
return nil
}
guard let unpackedData = TGGUnzipData(data, 5 * 1024 * 1024) else {
return nil
}
guard let instance = LottieInstance(data: unpackedData, cacheKey: item.name) else {
return nil
}
resolvedInstance = instance
renderContext = DrawingContext(size: displaySize, scale: UIScreenScale, premultiplied: true, clear: true)
}
self.item = item
self.instance = instance
self.instance = resolvedInstance
self.renderContext = renderContext
self.frameCount = Int(instance.frameCount)
self.fps = Double(instance.frameRate)
self.renderContext = DrawingContext(size: instance.dimensions, scale: UIScreenScale, premultiplied: true, clear: true)
self.frameCount = Int(self.instance.frameCount)
self.fps = Double(self.instance.frameRate)
}
func draw() -> UIImage? {
@ -56,34 +57,23 @@ private final class ManagedAnimationState {
}
}
enum ManagedAnimationActionAtEnd {
case pause
case advance
case loop
}
struct ManagedAnimationTrack: Equatable {
let frameRange: Range<Int>
struct ManagedAnimationFrameRange: Equatable {
var startFrame: Int
var endFrame: Int
}
struct ManagedAnimationItem: Equatable {
let name: String
var intro: ManagedAnimationTrack?
var loop: ManagedAnimationTrack?
var outro: ManagedAnimationTrack?
var frames: ManagedAnimationFrameRange
}
final class ManagedAnimationNode: ASDisplayNode {
class ManagedAnimationNode: ASDisplayNode {
let intrinsicSize: CGSize
private let imageNode: ASImageNode
private let displayLink: CADisplayLink
private var items: [ManagedAnimationState] = []
var currentItemName: String? {
return self.items.first?.item.name
}
private var state: ManagedAnimationState?
init(size: CGSize) {
self.intrinsicSize = size
@ -121,136 +111,174 @@ final class ManagedAnimationNode: ASDisplayNode {
}
private func updateAnimation() {
guard let item = self.items.first else {
guard let state = self.state else {
return
}
let timestamp = CACurrentMediaTime()
var startTime: Double
let maybeTrackState: ManagedAnimationTrackState?
if let current = item.startTime {
if let current = state.startTime {
startTime = current
} else {
startTime = timestamp
item.startTime = startTime
}
if let current = item.trackState {
maybeTrackState = current
} else if let _ = item.item.intro {
maybeTrackState = .intro
} else if let _ = item.item.loop {
maybeTrackState = .loop
} else if let _ = item.item.outro {
maybeTrackState = .outro
} else {
maybeTrackState = nil
}
if item.trackState != maybeTrackState {
item.trackState = maybeTrackState
item.startTime = timestamp
startTime = timestamp
state.startTime = startTime
}
guard let trackState = maybeTrackState else {
self.items.removeFirst()
return
}
let fps = state.fps
let frameRange = state.item.frames
var fps = item.fps
let duration: Double = 0.3
var t = (timestamp - startTime) / duration
t = max(0.0, t)
t = min(1.0, t)
let frameOffset = Int(Double(frameRange.startFrame) * (1.0 - t) + Double(frameRange.startFrame) * t)
let lowerBound = min(frameRange.startFrame, state.frameCount - 1)
let upperBound = min(frameRange.endFrame, state.frameCount - 1)
let frameIndex = max(lowerBound, min(upperBound, frameOffset))
let track: ManagedAnimationTrack
switch trackState {
case .intro:
track = item.item.intro!
case .loop:
track = item.item.loop!
if self.items.count > 1 {
//fps *= 2.0
}
case .outro:
track = item.item.outro!
}
let frameIndex: Int
if let (startFrame, endFrame) = item.trackingFrameState {
let duration: Double = 0.3
var t = (timestamp - startTime) / duration
t = max(0.0, t)
t = min(1.0, t)
let frameOffset = Int(Double(startFrame) * (1.0 - t) + Double(endFrame) * t)
let lowerBound = min(track.frameRange.lowerBound, item.frameCount - 1)
let upperBound = min(track.frameRange.upperBound, item.frameCount)
frameIndex = max(lowerBound, min(upperBound, frameOffset))
} else {
let frameOffset = Int((timestamp - startTime) * fps)
let lowerBound = min(track.frameRange.lowerBound, item.frameCount - 1)
let upperBound = min(track.frameRange.upperBound, item.frameCount)
if frameOffset >= upperBound - lowerBound {
switch trackState {
case .intro:
if let _ = item.item.loop {
item.trackState = .loop
item.startTime = timestamp
return
} else if let _ = item.item.outro {
item.trackState = .outro
item.startTime = timestamp
return
} else {
self.items.removeFirst()
return
}
case .loop:
if self.items.count > 1 {
if let _ = item.item.outro {
item.trackState = .outro
item.startTime = timestamp
} else {
self.items.removeFirst()
}
return
} else {
item.startTime = timestamp
frameIndex = lowerBound
}
case .outro:
self.items.removeFirst()
return
}
} else {
frameIndex = lowerBound + frameOffset % (upperBound - lowerBound)
}
}
if item.frameIndex != frameIndex {
item.frameIndex = frameIndex
if let image = item.draw() {
if state.frameIndex != frameIndex {
state.frameIndex = frameIndex
if let image = state.draw() {
self.imageNode.image = image
}
}
}
func switchTo(_ item: ManagedAnimationItem, noOutro: Bool = false) {
if let state = ManagedAnimationState(item: item) {
if let last = self.items.last {
if last.item.name == item.name {
return
}
}
if let first = self.items.first {
if noOutro {
first.item.outro = nil
}
}
self.items.append(state)
self.updateAnimation()
func trackTo(item: ManagedAnimationItem, frameIndex: Int) {
if let state = self.state, state.item.name == item.name {
self.state = ManagedAnimationState(displaySize: self.intrinsicSize, item: item, current: state)
} else {
self.state = ManagedAnimationState(displaySize: self.intrinsicSize, item: item, current: nil)
}
self.updateAnimation()
}
}
enum ManagedMonkeyAnimationState: Equatable {
case idle
case eyesClosed
case peeking
case tracking(CGFloat)
}
/*private let animationIdle = ManagedAnimationItem(name: "TwoFactorSetupMonkeyIdle",
intro: nil,
loop: ManagedAnimationTrack(frameRange: 0 ..< 1),
outro: nil
)
private let animationIdle = ManagedAnimationItem(name: "TwoFactorSetupMonkeyIdle",
intro: nil,
loop: ManagedAnimationTrack(frameRange: 0 ..< 1),
outro: nil
)
private let animationTracking = ManagedAnimationItem(name: "TwoFactorSetupMonkeyTracking",
intro: nil,
loop: ManagedAnimationTrack(frameRange: 0 ..< Int.max),
outro: nil
)
private let animationHide = ManagedAnimationItem(name: "TwoFactorSetupMonkeyClose",
intro: ManagedAnimationTrack(frameRange: 0 ..< 41),
loop: ManagedAnimationTrack(frameRange: 40 ..< 41),
outro: ManagedAnimationTrack(frameRange: 60 ..< 99)
)
private let animationHideNoOutro = ManagedAnimationItem(name: "TwoFactorSetupMonkeyClose",
intro: ManagedAnimationTrack(frameRange: 0 ..< 41),
loop: ManagedAnimationTrack(frameRange: 40 ..< 41),
outro: nil
)
private let animationHideNoIntro = ManagedAnimationItem(name: "TwoFactorSetupMonkeyClose",
intro: nil,
loop: ManagedAnimationTrack(frameRange: 40 ..< 41),
outro: ManagedAnimationTrack(frameRange: 60 ..< 99)
)
private let animationHideOutro = ManagedAnimationItem(name: "TwoFactorSetupMonkeyClose",
intro: nil,
loop: nil,
outro: ManagedAnimationTrack(frameRange: 60 ..< 99)
)
private let animationPeek = ManagedAnimationItem(name: "TwoFactorSetupMonkeyPeek",
intro: ManagedAnimationTrack(frameRange: 0 ..< 14),
loop: ManagedAnimationTrack(frameRange: 13 ..< 14),
outro: ManagedAnimationTrack(frameRange: 14 ..< 34)
)
private let animationMail = ManagedAnimationItem(name: "TwoFactorSetupMail",
intro: ManagedAnimationTrack(frameRange: 0 ..< Int.max),
loop: ManagedAnimationTrack(frameRange: Int.max - 1 ..< Int.max),
outro: nil
)
private let animationHint = ManagedAnimationItem(name: "TwoFactorSetupHint",
intro: ManagedAnimationTrack(frameRange: 0 ..< Int.max),
loop: ManagedAnimationTrack(frameRange: Int.max - 1 ..< Int.max),
outro: nil
)*/
final class ManagedMonkeyAnimationNode: ManagedAnimationNode {
private var state: ManagedMonkeyAnimationState = .idle
init() {
super.init(size: CGSize(width: 136.0, height: 136.0))
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyIdle", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 0)), frameIndex: 0)
}
func trackTo(frameIndex: Int) {
if let first = self.items.first {
first.startTime = CACurrentMediaTime()
first.trackingFrameState = (first.frameIndex ?? 0, frameIndex)
self.updateAnimation()
func setState(_ state: ManagedMonkeyAnimationState) {
let previousState = self.state
self.state = state
switch previousState {
case .idle:
switch state {
case .idle:
break
case .eyesClosed:
break
case .peeking:
break
case let .tracking(value):
break
}
case .eyesClosed:
switch state {
case .idle:
break
case .eyesClosed:
break
case .peeking:
break
case let .tracking(value):
break
}
case .peeking:
switch state {
case .idle:
break
case .eyesClosed:
break
case .peeking:
break
case let .tracking(value):
break
}
case let .tracking(previousValue):
switch state {
case .idle:
break
case .eyesClosed:
break
case .peeking:
break
case let .tracking(value):
break
}
}
}
}

View File

@ -10,76 +10,28 @@ import AccountContext
import TelegramPresentationData
import PresentationDataUtils
import TelegramCore
import AnimatedStickerNode
public enum TwoFactorDataInputMode {
case password
case emailAddress(password: String)
case emailConfirmation(password: String?, emailPattern: String)
case emailAddress(password: String, hint: String)
case updateEmailAddress(password: String)
case emailConfirmation(passwordAndHint: (String, String)?, emailPattern: String, codeLength: Int?)
case passwordHint(password: String)
}
private let animationIdle = ManagedAnimationItem(name: "TwoFactorSetupMonkeyIdle",
intro: nil,
loop: ManagedAnimationTrack(frameRange: 0 ..< 1),
outro: nil
)
private let animationTracking = ManagedAnimationItem(name: "TwoFactorSetupMonkeyTracking",
intro: nil,
loop: ManagedAnimationTrack(frameRange: 0 ..< Int.max),
outro: nil
)
private let animationHide = ManagedAnimationItem(name: "TwoFactorSetupMonkeyClose",
intro: ManagedAnimationTrack(frameRange: 0 ..< 41),
loop: ManagedAnimationTrack(frameRange: 40 ..< 41),
outro: ManagedAnimationTrack(frameRange: 60 ..< 99)
)
private let animationHideNoOutro = ManagedAnimationItem(name: "TwoFactorSetupMonkeyClose",
intro: ManagedAnimationTrack(frameRange: 0 ..< 41),
loop: ManagedAnimationTrack(frameRange: 40 ..< 41),
outro: nil
)
private let animationHideNoIntro = ManagedAnimationItem(name: "TwoFactorSetupMonkeyClose",
intro: nil,
loop: ManagedAnimationTrack(frameRange: 40 ..< 41),
outro: ManagedAnimationTrack(frameRange: 60 ..< 99)
)
private let animationHideOutro = ManagedAnimationItem(name: "TwoFactorSetupMonkeyClose",
intro: nil,
loop: nil,
outro: ManagedAnimationTrack(frameRange: 60 ..< 99)
)
private let animationPeek = ManagedAnimationItem(name: "TwoFactorSetupMonkeyPeek",
intro: ManagedAnimationTrack(frameRange: 0 ..< 14),
loop: ManagedAnimationTrack(frameRange: 13 ..< 14),
outro: ManagedAnimationTrack(frameRange: 14 ..< 34)
)
private let animationMail = ManagedAnimationItem(name: "TwoFactorSetupMail",
intro: ManagedAnimationTrack(frameRange: 0 ..< Int.max),
loop: ManagedAnimationTrack(frameRange: Int.max - 1 ..< Int.max),
outro: nil
)
private let animationHint = ManagedAnimationItem(name: "TwoFactorSetupHint",
intro: ManagedAnimationTrack(frameRange: 0 ..< Int.max),
loop: ManagedAnimationTrack(frameRange: Int.max - 1 ..< Int.max),
outro: nil
)
public final class TwoFactorDataInputScreen: ViewController {
private let context: AccountContext
private var presentationData: PresentationData
private let mode: TwoFactorDataInputMode
private let stateUpdated: (SetupTwoStepVerificationStateUpdate) -> Void
public init(context: AccountContext, mode: TwoFactorDataInputMode) {
public init(context: AccountContext, mode: TwoFactorDataInputMode, stateUpdated: @escaping (SetupTwoStepVerificationStateUpdate) -> Void) {
self.context = context
self.mode = mode
self.stateUpdated = stateUpdated
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
@ -131,14 +83,82 @@ public final class TwoFactorDataInputScreen: ViewController {
if controller is TwoFactorAuthSplashScreen {
return false
}
if controller is TwoFactorDataInputScreen {
if controller is TwoFactorDataInputScreen && controller !== strongSelf {
return false
}
return true
}
controllers.append(TwoFactorDataInputScreen(context: strongSelf.context, mode: .passwordHint(password: values[0])))
controllers.append(TwoFactorDataInputScreen(context: strongSelf.context, mode: .passwordHint(password: values[0]), stateUpdated: strongSelf.stateUpdated))
navigationController.setViewControllers(controllers, animated: true)
case let .emailAddress(password):
case let .emailAddress(password, hint):
guard let text = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !text.isEmpty else {
return
}
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
strongSelf.present(statusController, in: .window(.root))
let _ = (updateTwoStepVerificationPassword(network: strongSelf.context.account.network, currentPassword: "", updatedPassword: .password(password: password, hint: hint, email: text))
|> deliverOnMainQueue).start(next: { [weak statusController] result in
statusController?.dismiss()
guard let strongSelf = self else {
return
}
switch result {
case .none:
break
case let .password(_, pendingEmail):
if let pendingEmail = pendingEmail {
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(TwoFactorDataInputScreen(context: strongSelf.context, mode: .emailConfirmation(passwordAndHint: (password, hint), emailPattern: text, codeLength: pendingEmail.codeLength.flatMap(Int.init)), stateUpdated: strongSelf.stateUpdated))
navigationController.setViewControllers(controllers, animated: true)
} else {
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(context: strongSelf.context, mode: .done))
navigationController.setViewControllers(controllers, animated: true)
}
}
}, error: { [weak statusController] error in
statusController?.dismiss()
guard let strongSelf = self else {
return
}
let presentationData = strongSelf.presentationData
let alertText: String
switch error {
case .generic:
alertText = presentationData.strings.Login_UnknownError
case .invalidEmail:
alertText = presentationData.strings.TwoStepAuth_EmailInvalid
}
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: alertText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
})
case let .updateEmailAddress(password):
guard let text = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !text.isEmpty else {
return
}
@ -156,7 +176,7 @@ public final class TwoFactorDataInputScreen: ViewController {
switch result {
case .none:
break
case let .password(password, pendingEmail):
case let .password(_, pendingEmail):
if let pendingEmail = pendingEmail {
guard let navigationController = strongSelf.navigationController as? NavigationController else {
return
@ -170,7 +190,7 @@ public final class TwoFactorDataInputScreen: ViewController {
}
return true
}
controllers.append(TwoFactorDataInputScreen(context: strongSelf.context, mode: .emailConfirmation(password: password, emailPattern: text)))
controllers.append(TwoFactorDataInputScreen(context: strongSelf.context, mode: .emailConfirmation(passwordAndHint: (password, ""), emailPattern: text, codeLength: pendingEmail.codeLength.flatMap(Int.init)), stateUpdated: strongSelf.stateUpdated))
navigationController.setViewControllers(controllers, animated: true)
} else {
guard let navigationController = strongSelf.navigationController as? NavigationController else {
@ -263,38 +283,71 @@ public final class TwoFactorDataInputScreen: ViewController {
return
}
strongSelf.setPassword(password: password, hint: value)
strongSelf.push(TwoFactorDataInputScreen(context: strongSelf.context, mode: .emailAddress(password: password, hint: value), stateUpdated: strongSelf.stateUpdated))
}
}, skipAction: { [weak self] in
guard let strongSelf = self else {
return
}
switch strongSelf.mode {
case .emailAddress:
case let .emailAddress(password, hint):
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: strongSelf.presentationData.strings.TwoFactorSetup_Email_SkipConfirmationTitle, text: strongSelf.presentationData.strings.TwoFactorSetup_Email_SkipConfirmationText, actions: [
TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.TwoFactorSetup_Email_SkipConfirmationSkip, action: {
guard let strongSelf = self else {
return
}
guard let navigationController = strongSelf.navigationController as? NavigationController else {
return
}
var controllers = navigationController.viewControllers.filter { controller in
if controller is TwoFactorAuthSplashScreen {
return false
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
strongSelf.present(statusController, in: .window(.root))
let _ = (updateTwoStepVerificationPassword(network: strongSelf.context.account.network, currentPassword: "", updatedPassword: .password(password: password, hint: hint, email: nil))
|> deliverOnMainQueue).start(next: { [weak statusController] result in
statusController?.dismiss()
guard let strongSelf = self else {
return
}
if controller is TwoFactorDataInputScreen {
return false
switch result {
case .none:
break
case .password:
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(context: strongSelf.context, mode: .done))
navigationController.setViewControllers(controllers, animated: true)
}
return true
}
controllers.append(TwoFactorAuthSplashScreen(context: strongSelf.context, mode: .done))
navigationController.setViewControllers(controllers, animated: true)
}, error: { [weak statusController] error in
statusController?.dismiss()
guard let strongSelf = self else {
return
}
let presentationData = strongSelf.presentationData
let alertText: String
switch error {
case .generic:
alertText = presentationData.strings.Login_UnknownError
case .invalidEmail:
alertText = presentationData.strings.TwoStepAuth_EmailInvalid
}
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: alertText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
})
}),
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})
]), in: .window(.root))
case let .passwordHint(password):
strongSelf.setPassword(password: password, hint: "")
strongSelf.push(TwoFactorDataInputScreen(context: strongSelf.context, mode: .emailAddress(password: password, hint: ""), stateUpdated: strongSelf.stateUpdated))
default:
break
}
@ -303,8 +356,8 @@ public final class TwoFactorDataInputScreen: ViewController {
return
}
switch strongSelf.mode {
case let .emailConfirmation(password, _):
if let password = password {
case let .emailConfirmation(passwordAndHint, _, _):
if let (password, hint) = passwordAndHint {
guard let navigationController = strongSelf.navigationController as? NavigationController else {
return
}
@ -317,7 +370,7 @@ public final class TwoFactorDataInputScreen: ViewController {
}
return true
}
controllers.append(TwoFactorDataInputScreen(context: strongSelf.context, mode: .emailAddress(password: password)))
controllers.append(TwoFactorDataInputScreen(context: strongSelf.context, mode: .emailAddress(password: password, hint: hint), stateUpdated: strongSelf.stateUpdated))
navigationController.setViewControllers(controllers, animated: true)
} else {
}
@ -356,41 +409,6 @@ public final class TwoFactorDataInputScreen: ViewController {
self.displayNodeDidLoad()
}
private func setPassword(password: String, hint: String) {
let statusController = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: nil))
self.present(statusController, in: .window(.root))
let _ = (updateTwoStepVerificationPassword(network: self.context.account.network, currentPassword: nil, updatedPassword: .password(password: password, hint: hint, email: nil))
|> deliverOnMainQueue).start(next: { [weak self, weak statusController] _ in
statusController?.dismiss()
guard let strongSelf = self else {
return
}
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(TwoFactorDataInputScreen(context: strongSelf.context, mode: .emailAddress(password: password)))
navigationController.setViewControllers(controllers, animated: true)
}, error: { [weak self, weak statusController] _ in
statusController?.dismiss()
guard let strongSelf = self else {
return
}
})
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
@ -612,7 +630,8 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
private let navigationBackgroundNode: ASDisplayNode
private let navigationSeparatorNode: ASDisplayNode
private let scrollNode: ASScrollNode
private let animationNode: ManagedAnimationNode
private var animatedStickerNode: AnimatedStickerNode?
private var monkeyNode: ManagedMonkeyAnimationNode?
private let titleNode: ImmediateTextNode
private let textNode: ImmediateTextNode
private let skipActionTitleNode: ImmediateTextNode
@ -647,7 +666,24 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
self.scrollNode = ASScrollNode()
self.scrollNode.canCancelAllTouchesInViews = true
self.animationNode = ManagedAnimationNode(size: CGSize(width: 136.0, height: 136.0))
switch mode {
case .password, .emailAddress, .updateEmailAddress:
self.monkeyNode = ManagedMonkeyAnimationNode()
case .emailConfirmation:
if let path = getAppBundle().path(forResource: "TwoFactorSetupMail", ofType: "tgs") {
let animatedStickerNode = AnimatedStickerNode()
animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 272, height: 272, playbackMode: .once, mode: .direct)
animatedStickerNode.visibility = true
self.animatedStickerNode = animatedStickerNode
}
case .passwordHint:
if let path = getAppBundle().path(forResource: "TwoFactorSetupHint", ofType: "tgs") {
let animatedStickerNode = AnimatedStickerNode()
animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 272, height: 272, playbackMode: .once, mode: .direct)
animatedStickerNode.visibility = true
self.animatedStickerNode = animatedStickerNode
}
}
let title: String
let text: NSAttributedString
@ -664,8 +700,6 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
switch mode {
case .password:
self.animationNode.switchTo(animationIdle)
title = presentationData.strings.TwoFactorSetup_Password_Title
text = NSAttributedString(string: "", font: Font.regular(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
buttonText = presentationData.strings.TwoFactorSetup_Password_Action
@ -692,9 +726,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
toggleTextHidden?(node)
})
]
case .emailAddress:
self.animationNode.switchTo(animationTracking)
case .emailAddress, .updateEmailAddress:
title = presentationData.strings.TwoFactorSetup_Email_Title
text = NSAttributedString(string: presentationData.strings.TwoFactorSetup_Email_Text, font: Font.regular(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
buttonText = presentationData.strings.TwoFactorSetup_Email_Action
@ -712,9 +744,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
toggleTextHidden?(node)
}),
]
case let .emailConfirmation(_, emailPattern):
self.animationNode.switchTo(animationMail)
case let .emailConfirmation(_, emailPattern, _):
title = presentationData.strings.TwoFactorSetup_EmailVerification_Title
let (rawText, ranges) = presentationData.strings.TwoFactorSetup_EmailVerification_Text(emailPattern)
@ -742,8 +772,6 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
}),
]
case .passwordHint:
self.animationNode.switchTo(animationHint)
title = presentationData.strings.TwoFactorSetup_Hint_Title
text = NSAttributedString(string: presentationData.strings.TwoFactorSetup_Hint_Text, font: Font.regular(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
@ -812,7 +840,8 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
self.addSubnode(self.scrollNode)
self.scrollNode.addSubnode(self.animationNode)
self.animatedStickerNode.flatMap(self.scrollNode.addSubnode)
self.monkeyNode.flatMap(self.scrollNode.addSubnode)
self.scrollNode.addSubnode(self.titleNode)
self.scrollNode.addSubnode(self.textNode)
self.scrollNode.addSubnode(self.skipActionTitleNode)
@ -880,7 +909,13 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
guard let strongSelf = self else {
return
}
if let index = strongSelf.inputNodes.index(where: { $0 === node }) {
if index == strongSelf.inputNodes.count - 1 {
strongSelf.action()
} else if strongSelf.buttonNode.isUserInteractionEnabled {
strongSelf.inputNodes[index + 1].focus()
}
}
}
focused = { [weak self] node in
DispatchQueue.main.async {
@ -895,7 +930,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
return
}
let hasText = strongSelf.inputNodes.contains(where: { !$0.text.isEmpty })
switch strongSelf.mode {
/*switch strongSelf.mode {
case .password:
if !hasText {
if strongSelf.animationNode.currentItemName == animationPeek.name {
@ -942,25 +977,30 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
}
default:
break
}
}*/
}
updated = { [weak self] _ in
guard let strongSelf = self else {
return
}
switch strongSelf.mode {
case .emailAddress:
case .emailAddress, .updateEmailAddress:
let hasText = strongSelf.inputNodes.contains(where: { !$0.text.isEmpty })
strongSelf.buttonNode.isHidden = !hasText
strongSelf.skipActionTitleNode.isHidden = hasText
strongSelf.skipActionButtonNode.isHidden = hasText
case .emailConfirmation:
let hasText = strongSelf.inputNodes.contains(where: { !$0.text.isEmpty })
case let .emailConfirmation(_, _, codeLength):
let text = strongSelf.inputNodes[0].text
let hasText = !text.isEmpty
strongSelf.buttonNode.isHidden = !hasText
strongSelf.changeEmailActionTitleNode.isHidden = hasText
strongSelf.changeEmailActionButtonNode.isHidden = hasText
strongSelf.resendCodeActionTitleNode.isHidden = hasText
strongSelf.resendCodeActionButtonNode.isHidden = hasText
if let codeLength = codeLength, text.count == codeLength {
action()
}
case .passwordHint:
let hasText = strongSelf.inputNodes.contains(where: { !$0.text.isEmpty })
strongSelf.buttonNode.isHidden = !hasText
@ -1041,7 +1081,14 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: contentAreaSize))
let iconSize: CGSize = self.animationNode.intrinsicSize
let iconSize: CGSize
if let animatedStickerNode = self.animatedStickerNode {
iconSize = CGSize(width: 136.0, height: 136.0)
} else if let monkeyNode = self.monkeyNode {
iconSize = monkeyNode.intrinsicSize
} else {
iconSize = CGSize(width: 100.0, height: 100.0)
}
let titleSize = self.titleNode.updateLayout(CGSize(width: contentAreaSize.width - sideInset * 2.0, height: contentAreaSize.height))
let textSize = self.textNode.updateLayout(CGSize(width: contentAreaSize.width - sideInset * 2.0, height: contentAreaSize.height))
@ -1070,7 +1117,11 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
let contentVerticalOrigin = max(layout.statusBarHeight ?? 0.0, floor((areaHeight - calculatedContentHeight) / 2.0))
let iconFrame = CGRect(origin: CGPoint(x: floor((contentAreaSize.width - iconSize.width) / 2.0), y: contentVerticalOrigin), size: iconSize)
transition.updateFrame(node: self.animationNode, frame: iconFrame)
if let animatedStickerNode = self.animatedStickerNode {
transition.updateFrame(node: animatedStickerNode, frame: iconFrame)
} else if let monkeyNode = self.monkeyNode {
transition.updateFrame(node: monkeyNode, frame: iconFrame)
}
let titleFrame = CGRect(origin: CGPoint(x: floor((contentAreaSize.width - titleSize.width) / 2.0), y: iconFrame.maxY + iconSpacing), size: titleSize)
transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)
let textFrame: CGRect

View File

@ -54,7 +54,8 @@ public final class TwoFactorAuthSplashScreen: ViewController {
}
switch strongSelf.mode {
case .intro:
strongSelf.push(TwoFactorDataInputScreen(context: strongSelf.context, mode: .password))
strongSelf.push(TwoFactorDataInputScreen(context: strongSelf.context, mode: .password, stateUpdated: { _ in
}))
case .done:
guard let navigationController = strongSelf.navigationController as? NavigationController else {
return

View File

@ -268,6 +268,7 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
var replaceControllerImpl: ((ViewController, Bool) -> Void)?
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
var dismissImpl: (() -> Void)?
let actionsDisposable = DisposableSet()
@ -629,7 +630,8 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
state.checking = false
return state
}
dataPromise.set(.single(TwoStepVerificationUnlockSettingsControllerData.access(configuration: .notSet(pendingEmail: nil))))
//dataPromise.set(.single(TwoStepVerificationUnlockSettingsControllerData.access(configuration: .notSet(pendingEmail: nil))))
dismissImpl?()
}))
}
})]), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
@ -642,33 +644,35 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
|> take(1)
|> deliverOnMainQueue).start(next: { data in
switch data {
case .access:
break
case let .manage(password, emailSet, _, hasSecureValues):
let controller = SetupTwoStepVerificationController(context: context, initialState: .addEmail(hadRecoveryEmail: emailSet, hasSecureValues: hasSecureValues, password: password), stateUpdated: { update, shouldDismiss, controller in
switch update {
case .noPassword:
assertionFailure()
break
case let .awaitingEmailConfirmation(password, pattern, codeLength):
let data: TwoStepVerificationUnlockSettingsControllerData = .manage(password: password, emailSet: emailSet, pendingEmail: TwoStepVerificationPendingEmail(pattern: pattern, codeLength: codeLength), hasSecureValues: hasSecureValues)
case .access:
break
case let .manage(password, emailSet, _, hasSecureValues):
//let controller = TwoFactorDataInputScreen(context: context, mode: .updateEmailAddress(password: password))
let controller = SetupTwoStepVerificationController(context: context, initialState: .addEmail(hadRecoveryEmail: emailSet, hasSecureValues: hasSecureValues, password: password), stateUpdated: { update, shouldDismiss, controller in
switch update {
case .noPassword:
assertionFailure()
break
case let .awaitingEmailConfirmation(password, pattern, codeLength):
let data: TwoStepVerificationUnlockSettingsControllerData = .manage(password: password, emailSet: emailSet, pendingEmail: TwoStepVerificationPendingEmail(pattern: pattern, codeLength: codeLength), hasSecureValues: hasSecureValues)
dataPromise.set(.single(data))
case let .passwordSet(password, hasRecoveryEmail, hasSecureValues):
if let password = password {
let data: TwoStepVerificationUnlockSettingsControllerData = .manage(password: password, emailSet: hasRecoveryEmail, pendingEmail: nil, hasSecureValues: hasSecureValues)
dataPromise.set(.single(data))
case let .passwordSet(password, hasRecoveryEmail, hasSecureValues):
if let password = password {
let data: TwoStepVerificationUnlockSettingsControllerData = .manage(password: password, emailSet: hasRecoveryEmail, pendingEmail: nil, hasSecureValues: hasSecureValues)
dataPromise.set(.single(data))
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(emailSet ? presentationData.strings.TwoStepAuth_EmailChangeSuccess : presentationData.strings.TwoStepAuth_EmailAddSuccess, false)), nil)
} else {
dataPromise.set(.single(.access(configuration: nil))
|> then(twoStepVerificationConfiguration(account: context.account) |> map { TwoStepVerificationUnlockSettingsControllerData.access(configuration: TwoStepVerificationAccessConfiguration(configuration: $0, password: password)) }))
}
}
if shouldDismiss {
controller.dismiss()
}
})
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(emailSet ? presentationData.strings.TwoStepAuth_EmailChangeSuccess : presentationData.strings.TwoStepAuth_EmailAddSuccess, false)), nil)
} else {
dataPromise.set(.single(.access(configuration: nil))
|> then(twoStepVerificationConfiguration(account: context.account) |> map { TwoStepVerificationUnlockSettingsControllerData.access(configuration: TwoStepVerificationAccessConfiguration(configuration: $0, password: password)) }))
}
}
if shouldDismiss {
controller.dismiss()
}
})
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
}))
}, openResetPendingEmail: {
@ -822,6 +826,9 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
controller.present(c, in: .window(.root), with: p)
}
}
dismissImpl = { [weak controller] in
controller?.dismiss()
}
initialFocusImpl = { [weak controller] in
guard let controller = controller, controller.didAppearOnce else {
return