import Foundation import UIKit import AsyncDisplayKit import Display public enum ChatTitleActivityAnimationStyle { case none case crossfade case slide } public enum ChatTitleActivityInfoType { case online case lastSeenTime case generic } public enum ChatTitleActivityNodeState: Equatable { case none case info(NSAttributedString, ChatTitleActivityInfoType) case typingText(NSAttributedString, UIColor) case uploading(NSAttributedString, UIColor) case recordingVoice(NSAttributedString, UIColor) case recordingVideo(NSAttributedString, UIColor) case playingGame(NSAttributedString, UIColor) case choosingSticker(NSAttributedString, UIColor) case interactingWithEmoji(NSAttributedString, UIColor) func contentNode() -> ChatTitleActivityContentNode? { switch self { case .none: return nil case let .info(text, _): return ChatTitleActivityContentNode(text: text) case let .typingText(text, color): return ChatTypingActivityContentNode(text: text, color: color) case let .uploading(text, color): return ChatUploadingActivityContentNode(text: text, color: color) case let .recordingVoice(text, color): return ChatRecordingVoiceActivityContentNode(text: text, color: color) case let .recordingVideo(text, color): return ChatRecordingVideoActivityContentNode(text: text, color: color) case let .playingGame(text, color): return ChatPlayingActivityContentNode(text: text, color: color) case let .choosingSticker(text, color): return ChatChoosingStickerActivityContentNode(text: text, color: color) case let .interactingWithEmoji(text, _): return ChatTitleActivityContentNode(text: text) } } public var string: String? { if case let .info(text, _) = self { return text.string } return nil } } public class ChatTitleActivityNode: ASDisplayNode { public private(set) var state: ChatTitleActivityNodeState = .none private var contentNode: ChatTitleActivityContentNode? private var nextContentNode: ChatTitleActivityContentNode? override public init() { super.init() } public func makeCopy() -> ASDisplayNode { let node = ASDisplayNode() if let contentNode = self.contentNode { node.addSubnode(contentNode.makeCopy()) } node.frame = self.frame return node } public func transitionToState(_ state: ChatTitleActivityNodeState, animation: ChatTitleActivityAnimationStyle = .crossfade, completion: @escaping () -> Void = {}) -> Bool { if self.state != state { let currentState = self.state self.state = state let contentNode = state.contentNode() if contentNode !== self.contentNode { self.transitionToContentNode(contentNode, state: state, fromState: currentState, animation: animation, completion: completion) } return true } else { completion() return false } } private func transitionToContentNode(_ node: ChatTitleActivityContentNode?, state: ChatTitleActivityNodeState, fromState: ChatTitleActivityNodeState, animation: ChatTitleActivityAnimationStyle = .crossfade, completion: @escaping () -> Void) { if let previousContentNode = self.contentNode { if case .none = animation { previousContentNode.removeFromSupernode() self.contentNode = node if let contentNode = self.contentNode { self.addSubnode(contentNode) } } else { var animation = animation if case let .info(_, fromType) = fromState, case let .info(_, toType) = state, fromType == toType { animation = .none } if case .typingText = fromState, case .typingText = state { animation = .none } self.contentNode = node if let contentNode = self.contentNode { self.addSubnode(contentNode) if self.isNodeLoaded { contentNode.animateIn(from: fromState, style: animation) } } previousContentNode.animateOut(to: state, style: animation) { previousContentNode.removeFromSupernode() } } } else { self.contentNode = node if let contentNode = self.contentNode { self.addSubnode(contentNode) } } } public func updateLayout(_ constrainedSize: CGSize, offset: CGFloat = 0.0, alignment: NSTextAlignment) -> CGSize { return CGSize(width: 0.0, height: self.contentNode?.updateLayout(constrainedSize, offset: offset, alignment: alignment).height ?? 0.0) } }