diff --git a/submodules/PasswordSetupUI/Sources/ManagedAnimationNode.swift b/submodules/PasswordSetupUI/Sources/ManagedAnimationNode.swift index d7eaace4bd..e68f9dcf5d 100644 --- a/submodules/PasswordSetupUI/Sources/ManagedAnimationNode.swift +++ b/submodules/PasswordSetupUI/Sources/ManagedAnimationNode.swift @@ -14,7 +14,7 @@ private final class ManagedAnimationState { let frameCount: Int let fps: Double - var startTime: Double? + var relativeTime: Double = 0.0 var frameIndex: Int? private let renderContext: DrawingContext @@ -73,7 +73,8 @@ class ManagedAnimationNode: ASDisplayNode { private let imageNode: ASImageNode private let displayLink: CADisplayLink - private var state: ManagedAnimationState? + fileprivate var state: ManagedAnimationState? + fileprivate var trackStack: [ManagedAnimationItem] = [] init(size: CGSize) { self.intrinsicSize = size @@ -110,30 +111,40 @@ class ManagedAnimationNode: ASDisplayNode { } } - private func updateAnimation() { + private func advanceState() { + guard !self.trackStack.isEmpty else { + return + } + + let item = self.trackStack.removeFirst() + + 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) + } + } + + fileprivate func updateAnimation() { + if self.state == nil { + self.advanceState() + } + guard let state = self.state else { return } let timestamp = CACurrentMediaTime() - var startTime: Double - if let current = state.startTime { - startTime = current - } else { - startTime = timestamp - state.startTime = startTime - } - let fps = state.fps let frameRange = state.item.frames let duration: Double = 0.3 - var t = (timestamp - startTime) / duration + var t = state.relativeTime / 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 frameOffset = Int(Double(frameRange.startFrame) * (1.0 - t) + Double(frameRange.endFrame) * t) + let lowerBound: Int = 0 + let upperBound = state.frameCount - 1 let frameIndex = max(lowerBound, min(upperBound, frameOffset)) if state.frameIndex != frameIndex { @@ -142,14 +153,19 @@ class ManagedAnimationNode: ASDisplayNode { self.imageNode.image = image } } + + var animationAdvancement: Double = 1.0 / 60.0 + animationAdvancement *= Double(self.trackStack.count + 1) + + state.relativeTime += animationAdvancement + + if state.relativeTime >= duration { + self.advanceState() + } } - 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) - } + func trackTo(item: ManagedAnimationItem) { + self.trackStack.append(item) self.updateAnimation() } } @@ -222,62 +238,93 @@ enum ManagedMonkeyAnimationState: Equatable { )*/ final class ManagedMonkeyAnimationNode: ManagedAnimationNode { - private var state: ManagedMonkeyAnimationState = .idle + private var monkeyState: 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) + self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyIdle", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 0))) } - func setState(_ state: ManagedMonkeyAnimationState) { - let previousState = self.state - self.state = state + func setState(_ monkeyState: ManagedMonkeyAnimationState) { + let previousState = self.monkeyState + self.monkeyState = monkeyState + + func enqueueTracking(_ value: CGFloat) { + let lowerBound = 18 + let upperBound = 160 + let frameIndex = lowerBound + Int(value * CGFloat(upperBound - lowerBound)) + if let state = self.state, state.item.name == "TwoFactorSetupMonkeyTracking" { + let item = ManagedAnimationItem(name: "TwoFactorSetupMonkeyTracking", frames: ManagedAnimationFrameRange(startFrame: state.frameIndex ?? 0, endFrame: frameIndex)) + self.state = ManagedAnimationState(displaySize: self.intrinsicSize, item: item, current: state) + self.updateAnimation() + } else { + self.trackStack = self.trackStack.filter { + $0.name != "TwoFactorSetupMonkeyTracking" + } + self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyTracking", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: frameIndex))) + } + } + + func enqueueClearTracking() { + if let state = self.state, state.item.name == "TwoFactorSetupMonkeyTracking" { + let item = ManagedAnimationItem(name: "TwoFactorSetupMonkeyTracking", frames: ManagedAnimationFrameRange(startFrame: state.frameIndex ?? 0, endFrame: 0)) + self.state = ManagedAnimationState(displaySize: self.intrinsicSize, item: item, current: state) + self.updateAnimation() + } + } switch previousState { case .idle: - switch state { + switch monkeyState { case .idle: break case .eyesClosed: - break + self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyClose", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 41))) case .peeking: - break + self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyCloseAndPeek", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 41))) case let .tracking(value): - break + enqueueTracking(value) } case .eyesClosed: - switch state { + switch monkeyState { case .idle: - break + self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyClose", frames: ManagedAnimationFrameRange(startFrame: 41, endFrame: 0))) case .eyesClosed: break case .peeking: - break + self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyPeek", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 14))) case let .tracking(value): - break + self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyClose", frames: ManagedAnimationFrameRange(startFrame: 41, endFrame: 0))) + enqueueTracking(value) } case .peeking: - switch state { + switch monkeyState { case .idle: - break + self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyCloseAndPeek", frames: ManagedAnimationFrameRange(startFrame: 41, endFrame: 0))) case .eyesClosed: - break + self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyPeek", frames: ManagedAnimationFrameRange(startFrame: 14, endFrame: 0))) case .peeking: break case let .tracking(value): - break + self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyCloseAndPeek", frames: ManagedAnimationFrameRange(startFrame: 41, endFrame: 0))) + enqueueTracking(value) } - case let .tracking(previousValue): - switch state { + case let .tracking(currentValue): + switch monkeyState { case .idle: - break + enqueueClearTracking() + self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyIdle", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 0))) case .eyesClosed: - break + enqueueClearTracking() + self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyClose", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 41))) case .peeking: - break + enqueueClearTracking() + self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyCloseAndPeek", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 41))) case let .tracking(value): - break + if abs(currentValue - value) > CGFloat.ulpOfOne { + enqueueTracking(value) + } } } } diff --git a/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift b/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift index 98b8278fa4..4746fb4135 100644 --- a/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift +++ b/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift @@ -20,8 +20,6 @@ public enum TwoFactorDataInputMode { case passwordHint(password: String) } - - public final class TwoFactorDataInputScreen: ViewController { private let context: AccountContext private var presentationData: PresentationData @@ -471,7 +469,7 @@ private func generateTextHiddenImage(color: UIColor, on: Bool) -> UIImage? { private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelegate { private let theme: PresentationTheme let mode: TwoFactorDataInputTextNodeType - private let focused: (TwoFactorDataInputTextNode) -> Void + private let focusUpdated: (TwoFactorDataInputTextNode, Bool) -> Void private let next: (TwoFactorDataInputTextNode) -> Void private let updated: (TwoFactorDataInputTextNode) -> Void private let toggleTextHidden: (TwoFactorDataInputTextNode) -> Void @@ -481,6 +479,12 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega private let hideButtonNode: HighlightableButtonNode private let clearButtonNode: HighlightableButtonNode + fileprivate var ignoreTextChanged: Bool = false + + var isFocused: Bool { + return self.inputNode.textField.isFirstResponder + } + var text: String { get { return self.inputNode.textField.text ?? "" @@ -490,10 +494,10 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega } } - init(theme: PresentationTheme, mode: TwoFactorDataInputTextNodeType, placeholder: String, focused: @escaping (TwoFactorDataInputTextNode) -> 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.mode = mode - self.focused = focused + self.focusUpdated = focusUpdated self.next = next self.updated = updated self.toggleTextHidden = toggleTextHidden @@ -564,11 +568,21 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega func textFieldDidBeginEditing(_ textField: UITextField) { let text = self.text - let isEmpty = text.isEmpty - self.focused(self) + + if self.inputNode.textField.isSecureTextEntry { + let previousIgnoreTextChanged = self.ignoreTextChanged + self.ignoreTextChanged = true + self.inputNode.textField.text = "" + self.inputNode.textField.insertText(text + " ") + self.inputNode.textField.deleteBackward() + self.ignoreTextChanged = previousIgnoreTextChanged + } + + self.focusUpdated(self, true) } func textFieldDidEndEditing(_ textField: UITextField) { + self.focusUpdated(self, false) } func textFieldShouldReturn(_ textField: UITextField) -> Bool { @@ -581,13 +595,15 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega } @objc private func textFieldChanged(_ textField: UITextField) { - switch self.mode { - case .password: - break - default: - self.clearButtonNode.isHidden = self.text.isEmpty + if !self.ignoreTextChanged { + switch self.mode { + case .password: + break + default: + self.clearButtonNode.isHidden = self.text.isEmpty + } + self.updated(self) } - self.updated(self) } @objc private func hidePressed() { @@ -615,7 +631,19 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega func updateTextHidden(_ value: Bool) { self.hideButtonNode.setImage(generateTextHiddenImage(color: self.theme.actionSheet.inputClearButtonColor, on: !value), for: []) + let text = self.inputNode.textField.text ?? "" self.inputNode.textField.isSecureTextEntry = value + if value { + if self.inputNode.textField.isFirstResponder { + let previousIgnoreTextChanged = self.ignoreTextChanged + self.ignoreTextChanged = true + self.inputNode.textField.text = "" + self.inputNode.textField.becomeFirstResponder() + self.inputNode.textField.insertText(text + " ") + self.inputNode.textField.deleteBackward() + self.ignoreTextChanged = previousIgnoreTextChanged + } + } } } @@ -694,7 +722,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS var inputNodes: [TwoFactorDataInputTextNode] = [] var next: ((TwoFactorDataInputTextNode) -> Void)? - var focused: ((TwoFactorDataInputTextNode) -> Void)? + var focusUpdated: ((TwoFactorDataInputTextNode, Bool) -> Void)? var updated: ((TwoFactorDataInputTextNode) -> Void)? var toggleTextHidden: ((TwoFactorDataInputTextNode) -> Void)? @@ -707,8 +735,8 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS changeEmailActionText = "" resendCodeActionText = "" inputNodes = [ - TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(confirmation: false), placeholder: presentationData.strings.TwoFactorSetup_Password_PlaceholderPassword, focused: { node in - focused?(node) + TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(confirmation: false), placeholder: presentationData.strings.TwoFactorSetup_Password_PlaceholderPassword, focusUpdated: { node, focused in + focusUpdated?(node, focused) }, next: { node in next?(node) }, updated: { node in @@ -716,8 +744,8 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS }, toggleTextHidden: { node in toggleTextHidden?(node) }), - TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(confirmation: true), placeholder: presentationData.strings.TwoFactorSetup_Password_PlaceholderConfirmPassword, focused: { node in - focused?(node) + TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(confirmation: true), placeholder: presentationData.strings.TwoFactorSetup_Password_PlaceholderConfirmPassword, focusUpdated: { node, focused in + focusUpdated?(node, focused) }, next: { node in next?(node) }, updated: { node in @@ -734,8 +762,8 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS changeEmailActionText = "" resendCodeActionText = "" inputNodes = [ - TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .email, placeholder: presentationData.strings.TwoFactorSetup_Email_Placeholder, focused: { node in - focused?(node) + TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .email, placeholder: presentationData.strings.TwoFactorSetup_Email_Placeholder, focusUpdated: { node, focused in + focusUpdated?(node, focused) }, next: { node in next?(node) }, updated: { node in @@ -761,8 +789,8 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS changeEmailActionText = presentationData.strings.TwoFactorSetup_EmailVerification_ChangeAction resendCodeActionText = presentationData.strings.TwoFactorSetup_EmailVerification_ResendAction inputNodes = [ - TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .code, placeholder: presentationData.strings.TwoFactorSetup_EmailVerification_Placeholder, focused: { node in - focused?(node) + TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .code, placeholder: presentationData.strings.TwoFactorSetup_EmailVerification_Placeholder, focusUpdated: { node, focused in + focusUpdated?(node, focused) }, next: { node in next?(node) }, updated: { node in @@ -781,8 +809,8 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS changeEmailActionText = "" resendCodeActionText = "" inputNodes = [ - TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .hint, placeholder: presentationData.strings.TwoFactorSetup_Hint_Placeholder, focused: { node in - focused?(node) + TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .hint, placeholder: presentationData.strings.TwoFactorSetup_Hint_Placeholder, focusUpdated: { node, focused in + focusUpdated?(node, focused) }, next: { node in next?(node) }, updated: { node in @@ -917,67 +945,64 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS } } } - focused = { [weak self] node in - DispatchQueue.main.async { - guard let strongSelf = self else { - return - } - } - } var textHidden = true let updateAnimations: () -> Void = { [weak self] in guard let strongSelf = self else { 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 { - strongSelf.animationNode.switchTo(animationHideOutro) - strongSelf.animationNode.switchTo(animationIdle) + if strongSelf.inputNodes[1].isFocused { + let textLength = strongSelf.inputNodes[1].text.count + let maxWidth = strongSelf.inputNodes[1].bounds.width + + let textNode = ImmediateTextNode() + textNode.attributedText = NSAttributedString(string: strongSelf.inputNodes[1].text, font: Font.regular(17.0), textColor: .black) + let textSize = textNode.updateLayout(CGSize(width: 1000.0, height: 100.0)) + + let maxTextLength = 20 + var trackingOffset = textSize.width / maxWidth + trackingOffset = max(0.0, min(1.0, trackingOffset)) + strongSelf.monkeyNode?.setState(.tracking(trackingOffset)) + } else if strongSelf.inputNodes[0].isFocused { + let hasText = !strongSelf.inputNodes[0].text.isEmpty + if !hasText { + strongSelf.monkeyNode?.setState(.idle) + } else if textHidden { + strongSelf.monkeyNode?.setState(.eyesClosed) } else { - strongSelf.animationNode.switchTo(animationIdle) - } - } else if textHidden { - if strongSelf.animationNode.currentItemName == animationPeek.name { - strongSelf.animationNode.switchTo(animationHideNoIntro) - } else { - strongSelf.animationNode.switchTo(animationHide) + strongSelf.monkeyNode?.setState(.peeking) } } else { - if strongSelf.animationNode.currentItemName != animationPeek.name { - if strongSelf.animationNode.currentItemName == animationHide.name { - strongSelf.animationNode.switchTo(animationPeek, noOutro: true) - } else if strongSelf.animationNode.currentItemName == animationIdle.name { - strongSelf.animationNode.switchTo(animationHideNoOutro) - strongSelf.animationNode.switchTo(animationPeek) - } else { - strongSelf.animationNode.switchTo(animationPeek, noOutro: strongSelf.animationNode.currentItemName == animationHide.name) - } - } + strongSelf.monkeyNode?.setState(.idle) } case .emailAddress: - let textLength = strongSelf.inputNodes[0].text.count - let maxWidth = strongSelf.inputNodes[0].bounds.width - if textLength == 0 || maxWidth.isZero { - strongSelf.animationNode.trackTo(frameIndex: 0) - } else { + if strongSelf.inputNodes[0].isFocused { + let textLength = strongSelf.inputNodes[0].text.count + let maxWidth = strongSelf.inputNodes[0].bounds.width + let textNode = ImmediateTextNode() textNode.attributedText = NSAttributedString(string: strongSelf.inputNodes[0].text, font: Font.regular(17.0), textColor: .black) let textSize = textNode.updateLayout(CGSize(width: 1000.0, height: 100.0)) let maxTextLength = 20 - let lowerBound = 14 - let upperBound = 160 var trackingOffset = textSize.width / maxWidth trackingOffset = max(0.0, min(1.0, trackingOffset)) - let frameIndex = lowerBound + Int(trackingOffset * CGFloat(upperBound - lowerBound)) - strongSelf.animationNode.trackTo(frameIndex: frameIndex) + strongSelf.monkeyNode?.setState(.tracking(trackingOffset)) + } else { + strongSelf.monkeyNode?.setState(.idle) } default: break - }*/ + } + } + focusUpdated = { [weak self] node, _ in + DispatchQueue.main.async { + guard let strongSelf = self else { + return + } + updateAnimations() + } } updated = { [weak self] _ in guard let strongSelf = self else { @@ -1068,7 +1093,13 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS let sideInset: CGFloat = 32.0 let buttonSideInset: CGFloat = 48.0 - let iconSpacing: CGFloat = 2.0 + let iconSpacing: CGFloat + switch self.mode { + case .passwordHint, .emailConfirmation: + iconSpacing = 6.0 + default: + iconSpacing = 2.0 + } let titleSpacing: CGFloat = 19.0 let titleInputSpacing: CGFloat = 26.0 let textSpacing: CGFloat = 30.0 @@ -1118,6 +1149,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS let iconFrame = CGRect(origin: CGPoint(x: floor((contentAreaSize.width - iconSize.width) / 2.0), y: contentVerticalOrigin), size: iconSize) if let animatedStickerNode = self.animatedStickerNode { + animatedStickerNode.updateLayout(size: iconFrame.size) transition.updateFrame(node: animatedStickerNode, frame: iconFrame) } else if let monkeyNode = self.monkeyNode { transition.updateFrame(node: monkeyNode, frame: iconFrame) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift index 013c7b52b0..438bc08cdd 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift @@ -641,14 +641,14 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting case .set: break case let .notSet(pendingEmail): - break //intro = pendingEmail == nil - /*if pendingEmail == nil { + if pendingEmail == nil { let controller = TwoFactorAuthSplashScreen(context: context, mode: .intro) pushControllerImpl?(controller, true) + return } else { - }*/ + } } } if intro {