Add monkey idle animations

This commit is contained in:
Ali 2019-11-15 16:48:03 +04:00
parent 8ae455eb5e
commit 72fd141183
4 changed files with 99 additions and 90 deletions

View File

@ -5,6 +5,7 @@ import AsyncDisplayKit
import RLottieBinding
import AppBundle
import GZip
import SwiftSignalKit
private final class ManagedAnimationState {
let item: ManagedAnimationItem
@ -65,6 +66,7 @@ struct ManagedAnimationFrameRange: Equatable {
struct ManagedAnimationItem: Equatable {
let name: String
var frames: ManagedAnimationFrameRange
var duration: Double
}
class ManagedAnimationNode: ASDisplayNode {
@ -75,6 +77,7 @@ class ManagedAnimationNode: ASDisplayNode {
fileprivate var state: ManagedAnimationState?
fileprivate var trackStack: [ManagedAnimationItem] = []
fileprivate var didTryAdvancingState = false
init(size: CGSize) {
self.intrinsicSize = size
@ -111,7 +114,7 @@ class ManagedAnimationNode: ASDisplayNode {
}
}
private func advanceState() {
func advanceState() {
guard !self.trackStack.isEmpty else {
return
}
@ -123,6 +126,8 @@ class ManagedAnimationNode: ASDisplayNode {
} else {
self.state = ManagedAnimationState(displaySize: self.intrinsicSize, item: item, current: nil)
}
self.didTryAdvancingState = false
}
fileprivate func updateAnimation() {
@ -138,10 +143,11 @@ class ManagedAnimationNode: ASDisplayNode {
let fps = state.fps
let frameRange = state.item.frames
let duration: Double = 0.3
let duration: Double = state.item.duration
var t = state.relativeTime / duration
t = max(0.0, t)
t = min(1.0, t)
//print("\(t) \(state.item.name)")
let frameOffset = Int(Double(frameRange.startFrame) * (1.0 - t) + Double(frameRange.endFrame) * t)
let lowerBound: Int = 0
let upperBound = state.frameCount - 1
@ -155,172 +161,175 @@ class ManagedAnimationNode: ASDisplayNode {
}
var animationAdvancement: Double = 1.0 / 60.0
animationAdvancement *= Double(self.trackStack.count + 1)
animationAdvancement *= Double(min(2, self.trackStack.count + 1))
state.relativeTime += animationAdvancement
if state.relativeTime >= duration {
if state.relativeTime >= duration && !self.didTryAdvancingState {
self.didTryAdvancingState = true
self.advanceState()
}
}
func trackTo(item: ManagedAnimationItem) {
self.trackStack.append(item)
self.didTryAdvancingState = false
self.updateAnimation()
}
}
enum ManagedMonkeyAnimationIdle: CaseIterable {
case blink
case ear
case still
}
enum ManagedMonkeyAnimationState: Equatable {
case idle
case idle(ManagedMonkeyAnimationIdle)
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 monkeyState: ManagedMonkeyAnimationState = .idle
private var monkeyState: ManagedMonkeyAnimationState = .idle(.blink)
private var timer: SwiftSignalKit.Timer?
init() {
super.init(size: CGSize(width: 136.0, height: 136.0))
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyIdle", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 0)))
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyIdle", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 0), duration: 0.3))
}
deinit {
self.timer?.invalidate()
}
private func startIdleTimer() {
self.timer?.invalidate()
let timer = SwiftSignalKit.Timer(timeout: Double.random(in: 1.0 ..< 1.5), repeat: false, completion: { [weak self] in
guard let strongSelf = self else {
return
}
switch strongSelf.monkeyState {
case .idle:
if let idle = ManagedMonkeyAnimationIdle.allCases.randomElement() {
strongSelf.setState(.idle(idle))
}
default:
break
}
}, queue: .mainQueue())
self.timer = timer
timer.start()
}
override func advanceState() {
super.advanceState()
self.timer?.invalidate()
self.timer = nil
if self.trackStack.isEmpty, case .idle = self.monkeyState {
self.startIdleTimer()
}
}
private func enqueueIdle(_ idle: ManagedMonkeyAnimationIdle) {
switch idle {
case .still:
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyIdle", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 0), duration: 0.3))
case .blink:
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyIdle1", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 30), duration: 0.3))
case .ear:
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyIdle2", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 30), duration: 0.3))
//self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyIdle", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 179), duration: 3.0))
}
}
func setState(_ monkeyState: ManagedMonkeyAnimationState) {
let previousState = self.monkeyState
self.monkeyState = monkeyState
self.timer?.invalidate()
self.timer = nil
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))
let item = ManagedAnimationItem(name: "TwoFactorSetupMonkeyTracking", frames: ManagedAnimationFrameRange(startFrame: state.frameIndex ?? 0, endFrame: frameIndex), duration: 0.3)
self.state = ManagedAnimationState(displaySize: self.intrinsicSize, item: item, current: state)
self.didTryAdvancingState = false
self.updateAnimation()
} else {
self.trackStack = self.trackStack.filter {
$0.name != "TwoFactorSetupMonkeyTracking"
}
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyTracking", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: frameIndex)))
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyTracking", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: frameIndex), duration: 0.3))
}
}
func enqueueClearTracking() {
if let state = self.state, state.item.name == "TwoFactorSetupMonkeyTracking" {
let item = ManagedAnimationItem(name: "TwoFactorSetupMonkeyTracking", frames: ManagedAnimationFrameRange(startFrame: state.frameIndex ?? 0, endFrame: 0))
let item = ManagedAnimationItem(name: "TwoFactorSetupMonkeyTracking", frames: ManagedAnimationFrameRange(startFrame: state.frameIndex ?? 0, endFrame: 0), duration: 0.3)
self.state = ManagedAnimationState(displaySize: self.intrinsicSize, item: item, current: state)
self.didTryAdvancingState = false
self.updateAnimation()
}
}
switch previousState {
case .idle:
case let .idle(previousIdle):
switch monkeyState {
case .idle:
break
case let .idle(idle):
self.enqueueIdle(idle)
case .eyesClosed:
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyClose", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 41)))
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyClose", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 41), duration: 0.3))
case .peeking:
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyCloseAndPeek", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 41)))
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyCloseAndPeek", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 41), duration: 0.3))
case let .tracking(value):
enqueueTracking(value)
}
case .eyesClosed:
switch monkeyState {
case .idle:
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyClose", frames: ManagedAnimationFrameRange(startFrame: 41, endFrame: 0)))
case let .idle(idle):
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyClose", frames: ManagedAnimationFrameRange(startFrame: 41, endFrame: 0), duration: 0.3))
self.enqueueIdle(idle)
case .eyesClosed:
break
case .peeking:
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyPeek", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 14)))
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyPeek", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 14), duration: 0.3))
case let .tracking(value):
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyClose", frames: ManagedAnimationFrameRange(startFrame: 41, endFrame: 0)))
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyClose", frames: ManagedAnimationFrameRange(startFrame: 41, endFrame: 0), duration: 0.3))
enqueueTracking(value)
}
case .peeking:
switch monkeyState {
case .idle:
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyCloseAndPeek", frames: ManagedAnimationFrameRange(startFrame: 41, endFrame: 0)))
case let .idle(idle):
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyCloseAndPeek", frames: ManagedAnimationFrameRange(startFrame: 41, endFrame: 0), duration: 0.3))
self.enqueueIdle(idle)
case .eyesClosed:
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyPeek", frames: ManagedAnimationFrameRange(startFrame: 14, endFrame: 0)))
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyPeek", frames: ManagedAnimationFrameRange(startFrame: 14, endFrame: 0), duration: 0.3))
case .peeking:
break
case let .tracking(value):
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyCloseAndPeek", frames: ManagedAnimationFrameRange(startFrame: 41, endFrame: 0)))
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyCloseAndPeek", frames: ManagedAnimationFrameRange(startFrame: 41, endFrame: 0), duration: 0.3))
enqueueTracking(value)
}
case let .tracking(currentValue):
switch monkeyState {
case .idle:
case let .idle(idle):
enqueueClearTracking()
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyIdle", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 0)))
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyIdle", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 0), duration: 0.3))
self.enqueueIdle(idle)
case .eyesClosed:
enqueueClearTracking()
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyClose", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 41)))
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyClose", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 41), duration: 0.3))
case .peeking:
enqueueClearTracking()
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyCloseAndPeek", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 41)))
self.trackTo(item: ManagedAnimationItem(name: "TwoFactorSetupMonkeyCloseAndPeek", frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 41), duration: 0.3))
case let .tracking(value):
if abs(currentValue - value) > CGFloat.ulpOfOne {
enqueueTracking(value)

View File

@ -967,14 +967,14 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
} else if strongSelf.inputNodes[0].isFocused {
let hasText = !strongSelf.inputNodes[0].text.isEmpty
if !hasText {
strongSelf.monkeyNode?.setState(.idle)
strongSelf.monkeyNode?.setState(.idle(.still))
} else if textHidden {
strongSelf.monkeyNode?.setState(.eyesClosed)
} else {
strongSelf.monkeyNode?.setState(.peeking)
}
} else {
strongSelf.monkeyNode?.setState(.idle)
strongSelf.monkeyNode?.setState(.idle(.still))
}
case .emailAddress:
if strongSelf.inputNodes[0].isFocused {
@ -990,7 +990,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
trackingOffset = max(0.0, min(1.0, trackingOffset))
strongSelf.monkeyNode?.setState(.tracking(trackingOffset))
} else {
strongSelf.monkeyNode?.setState(.idle)
strongSelf.monkeyNode?.setState(.idle(.still))
}
default:
break