[WIP] Stories

This commit is contained in:
Ali
2023-04-28 20:20:46 +04:00
parent cb1f40de1a
commit 7dd76ef329
27 changed files with 1062 additions and 190 deletions

View File

@@ -0,0 +1,173 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import AppBundle
import ObjCRuntimeUtils
private let innerCircleDiameter: CGFloat = 110.0
private let outerCircleDiameter = innerCircleDiameter + 50.0
private let outerCircleMinScale = innerCircleDiameter / outerCircleDiameter
private let innerCircleImage = generateFilledCircleImage(diameter: innerCircleDiameter, color: UIColor(rgb: 0x007aff))
private let outerCircleImage = generateFilledCircleImage(diameter: outerCircleDiameter, color: UIColor(rgb: 0x007aff, alpha: 0.2))
private let micIcon = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconMicrophone"), color: .white)!
private final class ChatTextInputAudioRecordingOverlayDisplayLinkTarget: NSObject {
private let f: () -> Void
init(_ f: @escaping () -> Void) {
self.f = f
super.init()
}
@objc func displayLinkEvent() {
self.f()
}
}
final class ChatTextInputAudioRecordingOverlay {
private weak var anchorView: UIView?
private let containerNode: ASDisplayNode
private let circleContainerNode: ASDisplayNode
private let innerCircleNode: ASImageNode
private let outerCircleNode: ASImageNode
private let iconNode: ASImageNode
var animationStartTime: Double?
var displayLink: CADisplayLink?
var currentLevel: CGFloat = 0.0
var inputLevel: CGFloat = 0.0
var animatedIn = false
var dismissFactor: CGFloat = 1.0 {
didSet {
let scale = max(0.3, min(self.dismissFactor, 1.0))
self.circleContainerNode.transform = CATransform3DMakeScale(scale, scale, 1.0)
}
}
init(anchorView: UIView) {
self.anchorView = anchorView
self.containerNode = ASDisplayNode()
self.containerNode.isLayerBacked = true
self.circleContainerNode = ASDisplayNode()
self.circleContainerNode.isLayerBacked = true
self.outerCircleNode = ASImageNode()
self.outerCircleNode.displayWithoutProcessing = true
self.outerCircleNode.displaysAsynchronously = false
self.outerCircleNode.isLayerBacked = true
self.outerCircleNode.image = outerCircleImage
self.outerCircleNode.frame = CGRect(origin: CGPoint(x: -outerCircleDiameter / 2.0, y: -outerCircleDiameter / 2.0), size: CGSize(width: outerCircleDiameter, height: outerCircleDiameter))
self.innerCircleNode = ASImageNode()
self.innerCircleNode.displayWithoutProcessing = true
self.innerCircleNode.displaysAsynchronously = false
self.innerCircleNode.isLayerBacked = true
self.innerCircleNode.image = innerCircleImage
self.innerCircleNode.frame = CGRect(origin: CGPoint(x: -innerCircleDiameter / 2.0, y: -innerCircleDiameter / 2.0), size: CGSize(width: innerCircleDiameter, height: innerCircleDiameter))
self.iconNode = ASImageNode()
self.iconNode.displayWithoutProcessing = true
self.iconNode.displaysAsynchronously = false
self.iconNode.isLayerBacked = true
self.iconNode.image = micIcon
self.iconNode.frame = CGRect(origin: CGPoint(x: -micIcon.size.width / 2.0, y: -micIcon.size.height / 2.0), size: micIcon.size)
self.circleContainerNode.addSubnode(self.outerCircleNode)
self.circleContainerNode.addSubnode(self.innerCircleNode)
self.containerNode.addSubnode(self.circleContainerNode)
self.containerNode.addSubnode(self.iconNode)
}
deinit {
self.displayLink?.invalidate()
}
func present(in window: UIWindow) {
if let anchorView = self.anchorView, let anchorSuperview = anchorView.superview {
if let displayLink = self.displayLink {
displayLink.invalidate()
}
self.displayLink = CADisplayLink(target: ChatTextInputAudioRecordingOverlayDisplayLinkTarget({ [weak self] in
self?.displayLinkEvent()
}), selector: #selector(ChatTextInputAudioRecordingOverlayDisplayLinkTarget.displayLinkEvent))
let convertedCenter = anchorSuperview.convert(anchorView.center, to: window)
self.containerNode.position = CGPoint(x: convertedCenter.x, y: convertedCenter.y)
window.addSubnode(self.containerNode)
self.innerCircleNode.layer.animateSpring(from: 0.2 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
self.outerCircleNode.layer.transform = CATransform3DMakeScale(outerCircleMinScale, outerCircleMinScale, 1.0)
self.outerCircleNode.layer.animateSpring(from: 0.2 as NSNumber, to: outerCircleMinScale as NSNumber, keyPath: "transform.scale", duration: 0.5)
self.innerCircleNode.layer.animateAlpha(from: 0.2, to: 1.0, duration: 0.15)
self.outerCircleNode.layer.animateAlpha(from: 0.2, to: 1.0, duration: 0.15)
self.iconNode.layer.animateAlpha(from: 0.2, to: 1.0, duration: 0.15)
self.animatedIn = true
self.animationStartTime = CACurrentMediaTime()
self.displayLink?.add(to: RunLoop.main, forMode: .common)
self.displayLink?.isPaused = false
}
}
func dismiss() {
self.displayLink?.invalidate()
self.displayLink = nil
var innerCompleted = false
var outerCompleted = false
var iconCompleted = false
var containerNodeRef: ASDisplayNode? = self.containerNode
let completion: () -> Void = {
if let containerNode = containerNodeRef, innerCompleted, outerCompleted, iconCompleted {
containerNode.removeFromSupernode()
containerNodeRef = nil
}
}
self.innerCircleNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false)
self.innerCircleNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.18, removeOnCompletion: false, completion: { _ in
innerCompleted = true
completion()
})
var currentScaleValue: CGFloat = outerCircleMinScale
if let currentScale = self.outerCircleNode.layer.floatValue(forKeyPath: "transform.scale") {
currentScaleValue = CGFloat(currentScale.floatValue)
}
self.outerCircleNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false)
self.outerCircleNode.layer.animateScale(from: currentScaleValue, to: 0.2, duration: 0.18, removeOnCompletion: false, completion: { _ in
outerCompleted = true
completion()
})
self.iconNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { _ in
iconCompleted = true
completion()
})
}
private func displayLinkEvent() {
let t = CACurrentMediaTime()
if let animationStartTime = self.animationStartTime {
if t > animationStartTime + 0.5 {
self.currentLevel = self.currentLevel * 0.8 + self.inputLevel * 0.2
let scale = outerCircleMinScale + self.currentLevel * (1.0 - outerCircleMinScale)
self.outerCircleNode.transform = CATransform3DMakeScale(scale, scale, 1.0)
}
}
}
func addImmediateMicLevel(_ level: CGFloat) {
self.inputLevel = level
}
}

View File

@@ -0,0 +1,540 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import TelegramCore
import SwiftSignalKit
import TelegramPresentationData
import LegacyComponents
import AccountContext
import ChatInterfaceState
import AudioBlob
import ChatPresentationInterfaceState
import ComponentFlow
import LottieAnimationComponent
import LottieComponent
import LegacyInstantVideoController
private let offsetThreshold: CGFloat = 10.0
private let dismissOffsetThreshold: CGFloat = 70.0
private func findTargetView(_ view: UIView, point: CGPoint) -> UIView? {
if view.bounds.contains(point) && view.tag == 0x01f2bca {
return view
}
for subview in view.subviews {
let frame = subview.frame
if let result = findTargetView(subview, point: point.offsetBy(dx: -frame.minX, dy: -frame.minY)) {
return result
}
}
return nil
}
private final class ChatTextInputMediaRecordingButtonPresenterContainer: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let result = findTargetView(self, point: point) {
return result
}
for subview in self.subviews {
if let result = subview.hitTest(point.offsetBy(dx: -subview.frame.minX, dy: -subview.frame.minY), with: event) {
return result
}
}
return super.hitTest(point, with: event)
}
}
private final class ChatTextInputMediaRecordingButtonPresenterController: ViewController {
private var controllerNode: ChatTextInputMediaRecordingButtonPresenterControllerNode {
return self.displayNode as! ChatTextInputMediaRecordingButtonPresenterControllerNode
}
var containerView: UIView? {
didSet {
if self.isNodeLoaded {
self.controllerNode.containerView = self.containerView
}
}
}
override func loadDisplayNode() {
self.displayNode = ChatTextInputMediaRecordingButtonPresenterControllerNode()
if let containerView = self.containerView {
self.controllerNode.containerView = containerView
}
}
}
private final class ChatTextInputMediaRecordingButtonPresenterControllerNode: ViewControllerTracingNode {
var containerView: UIView? {
didSet {
if self.containerView !== oldValue {
if self.isNodeLoaded, let containerView = oldValue, containerView.superview === self.view {
containerView.removeFromSuperview()
}
if self.isNodeLoaded, let containerView = self.containerView {
self.view.addSubview(containerView)
}
}
}
}
override func didLoad() {
super.didLoad()
if let containerView = self.containerView {
self.view.addSubview(containerView)
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let containerView = self.containerView {
if let result = containerView.hitTest(point, with: event), result !== containerView {
return result
}
}
return nil
}
}
private final class ChatTextInputMediaRecordingButtonPresenter : NSObject, TGModernConversationInputMicButtonPresentation {
private let statusBarHost: StatusBarHost?
private let presentController: (ViewController) -> Void
let container: ChatTextInputMediaRecordingButtonPresenterContainer
private var presentationController: ChatTextInputMediaRecordingButtonPresenterController?
private var timer: SwiftSignalKit.Timer?
fileprivate weak var button: ChatTextInputMediaRecordingButton?
init(statusBarHost: StatusBarHost?, presentController: @escaping (ViewController) -> Void) {
self.statusBarHost = statusBarHost
self.presentController = presentController
self.container = ChatTextInputMediaRecordingButtonPresenterContainer()
}
deinit {
self.container.removeFromSuperview()
if let presentationController = self.presentationController {
presentationController.presentingViewController?.dismiss(animated: false, completion: {})
self.presentationController = nil
}
self.timer?.invalidate()
}
func view() -> UIView! {
return self.container
}
func setUserInteractionEnabled(_ enabled: Bool) {
self.container.isUserInteractionEnabled = enabled
}
func present() {
let windowIsVisible: (UIWindow) -> Bool = { window in
return !window.frame.height.isZero
}
if let statusBarHost = self.statusBarHost, let keyboardWindow = statusBarHost.keyboardWindow, let keyboardView = statusBarHost.keyboardView, !keyboardView.frame.height.isZero, isViewVisibleInHierarchy(keyboardView) {
keyboardWindow.addSubview(self.container)
self.timer = SwiftSignalKit.Timer(timeout: 0.05, repeat: true, completion: { [weak self] in
if let keyboardWindow = LegacyComponentsGlobals.provider().applicationKeyboardWindow(), windowIsVisible(keyboardWindow) {
} else {
self?.present()
}
}, queue: Queue.mainQueue())
self.timer?.start()
} else {
var presentNow = false
if self.presentationController == nil {
let presentationController = ChatTextInputMediaRecordingButtonPresenterController(navigationBarPresentationData: nil)
presentationController.statusBar.statusBarStyle = .Ignore
self.presentationController = presentationController
presentNow = true
}
self.presentationController?.containerView = self.container
if let presentationController = self.presentationController, presentNow {
self.presentController(presentationController)
}
if let timer = self.timer {
self.button?.reset()
timer.invalidate()
}
}
}
func dismiss() {
self.timer?.invalidate()
self.container.removeFromSuperview()
if let presentationController = self.presentationController {
presentationController.presentingViewController?.dismiss(animated: false, completion: {})
self.presentationController = nil
}
}
}
public final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButton, TGModernConversationInputMicButtonDelegate {
private let context: AccountContext
private var theme: PresentationTheme
private let strings: PresentationStrings
public var mode: ChatTextInputMediaRecordingButtonMode = .audio
public var statusBarHost: StatusBarHost?
public let presentController: (ViewController) -> Void
public var recordingDisabled: () -> Void = { }
public var beginRecording: () -> Void = { }
public var endRecording: (Bool) -> Void = { _ in }
public var stopRecording: () -> Void = { }
public var offsetRecordingControls: () -> Void = { }
public var switchMode: () -> Void = { }
public var updateLocked: (Bool) -> Void = { _ in }
public var updateCancelTranslation: () -> Void = { }
private var modeTimeoutTimer: SwiftSignalKit.Timer?
private let animationView: ComponentView<Empty>
private var recordingOverlay: ChatTextInputAudioRecordingOverlay?
private var startTouchLocation: CGPoint?
fileprivate var controlsOffset: CGFloat = 0.0
public private(set) var cancelTranslation: CGFloat = 0.0
private var micLevelDisposable: MetaDisposable?
private weak var currentPresenter: UIView?
public var contentContainer: (UIView, CGRect)? {
if let _ = self.currentPresenter {
return (self.micDecoration, self.micDecoration.bounds)
} else {
return nil
}
}
public var audioRecorder: ManagedAudioRecorder? {
didSet {
if self.audioRecorder !== oldValue {
if self.micLevelDisposable == nil {
micLevelDisposable = MetaDisposable()
}
if let audioRecorder = self.audioRecorder {
self.micLevelDisposable?.set(audioRecorder.micLevel.start(next: { [weak self] level in
Queue.mainQueue().async {
//self?.recordingOverlay?.addImmediateMicLevel(CGFloat(level))
self?.addMicLevel(CGFloat(level))
}
}))
} else if self.videoRecordingStatus == nil {
self.micLevelDisposable?.set(nil)
}
self.hasRecorder = self.audioRecorder != nil || self.videoRecordingStatus != nil
}
}
}
public var videoRecordingStatus: InstantVideoControllerRecordingStatus? {
didSet {
if self.videoRecordingStatus !== oldValue {
if self.micLevelDisposable == nil {
micLevelDisposable = MetaDisposable()
}
if let videoRecordingStatus = self.videoRecordingStatus {
self.micLevelDisposable?.set(videoRecordingStatus.micLevel.start(next: { [weak self] level in
Queue.mainQueue().async {
//self?.recordingOverlay?.addImmediateMicLevel(CGFloat(level))
self?.addMicLevel(CGFloat(level))
}
}))
} else if self.audioRecorder == nil {
self.micLevelDisposable?.set(nil)
}
self.hasRecorder = self.audioRecorder != nil || self.videoRecordingStatus != nil
}
}
}
private var hasRecorder: Bool = false {
didSet {
if self.hasRecorder != oldValue {
if self.hasRecorder {
self.animateIn()
} else {
self.animateOut(false)
}
}
}
}
private var micDecorationValue: VoiceBlobView?
private var micDecoration: (UIView & TGModernConversationInputMicButtonDecoration) {
if let micDecorationValue = self.micDecorationValue {
return micDecorationValue
} else {
let blobView = VoiceBlobView(
frame: CGRect(origin: CGPoint(), size: CGSize(width: 220.0, height: 220.0)),
maxLevel: 4,
smallBlobRange: (0.45, 0.55),
mediumBlobRange: (0.52, 0.87),
bigBlobRange: (0.57, 1.00)
)
blobView.setColor(self.theme.chat.inputPanel.actionControlFillColor)
self.micDecorationValue = blobView
return blobView
}
}
private var micLockValue: (UIView & TGModernConversationInputMicButtonLock)?
private var micLock: UIView & TGModernConversationInputMicButtonLock {
if let current = self.micLockValue {
return current
} else {
let lockView = LockView(frame: CGRect(origin: CGPoint(), size: CGSize(width: 40.0, height: 60.0)), theme: self.theme, strings: self.strings)
lockView.addTarget(self, action: #selector(handleStopTap), for: .touchUpInside)
self.micLockValue = lockView
return lockView
}
}
public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, presentController: @escaping (ViewController) -> Void) {
self.context = context
self.theme = theme
self.strings = strings
self.animationView = ComponentView<Empty>()
self.presentController = presentController
super.init(frame: CGRect())
self.disablesInteractiveTransitionGestureRecognizer = true
self.pallete = legacyInputMicPalette(from: theme)
self.disablesInteractiveTransitionGestureRecognizer = true
self.updateMode(mode: self.mode, animated: false, force: true)
self.delegate = self
self.isExclusiveTouch = false;
self.centerOffset = CGPoint(x: 0.0, y: -1.0 + UIScreenPixel)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
if let micLevelDisposable = self.micLevelDisposable {
micLevelDisposable.dispose()
}
if let recordingOverlay = self.recordingOverlay {
recordingOverlay.dismiss()
}
}
public func updateMode(mode: ChatTextInputMediaRecordingButtonMode, animated: Bool) {
self.updateMode(mode: mode, animated: animated, force: false)
}
private func updateMode(mode: ChatTextInputMediaRecordingButtonMode, animated: Bool, force: Bool) {
let previousMode = self.mode
if mode != self.mode || force {
self.mode = mode
self.updateAnimation(previousMode: previousMode)
}
}
private func updateAnimation(previousMode: ChatTextInputMediaRecordingButtonMode) {
let image: UIImage?
switch self.mode {
case .audio:
self.icon = PresentationResourcesChat.chatInputPanelVoiceActiveButtonImage(self.theme)
image = PresentationResourcesChat.chatInputPanelVoiceButtonImage(self.theme)
case .video:
self.icon = PresentationResourcesChat.chatInputPanelVideoActiveButtonImage(self.theme)
image = PresentationResourcesChat.chatInputPanelVoiceButtonImage(self.theme)
}
let size = self.bounds.size
let iconSize: CGSize
if let image = image {
iconSize = image.size
} else {
iconSize = size
}
let animationFrame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)
let animationName: String
switch self.mode {
case .audio:
animationName = "anim_videoToMic"
case .video:
animationName = "anim_micToVideo"
}
let _ = animationView.update(
transition: .immediate,
component: AnyComponent(LottieComponent(
content: LottieComponent.AppBundleContent(name: animationName),
color: self.theme.chat.inputPanel.panelControlColor.blitOver(self.theme.chat.inputPanel.inputBackgroundColor, alpha: 1.0)
)),
environment: {},
containerSize: animationFrame.size
)
if let view = animationView.view as? LottieComponent.View {
view.isUserInteractionEnabled = false
if view.superview == nil {
self.insertSubview(view, at: 0)
}
view.frame = animationFrame
if previousMode != mode {
view.playOnce()
}
}
}
public func updateTheme(theme: PresentationTheme) {
self.theme = theme
self.updateAnimation(previousMode: self.mode)
self.pallete = legacyInputMicPalette(from: theme)
self.micDecorationValue?.setColor(self.theme.chat.inputPanel.actionControlFillColor)
(self.micLockValue as? LockView)?.updateTheme(theme)
}
public func cancelRecording() {
self.isEnabled = false
self.isEnabled = true
}
public func micButtonInteractionBegan() {
if self.fadeDisabled {
self.recordingDisabled()
} else {
//print("\(CFAbsoluteTimeGetCurrent()) began")
self.modeTimeoutTimer?.invalidate()
let modeTimeoutTimer = SwiftSignalKit.Timer(timeout: 0.19, repeat: false, completion: { [weak self] in
if let strongSelf = self {
strongSelf.modeTimeoutTimer = nil
strongSelf.beginRecording()
}
}, queue: Queue.mainQueue())
self.modeTimeoutTimer = modeTimeoutTimer
modeTimeoutTimer.start()
}
}
public func micButtonInteractionCancelled(_ velocity: CGPoint) {
//print("\(CFAbsoluteTimeGetCurrent()) cancelled")
self.modeTimeoutTimer?.invalidate()
self.endRecording(false)
}
public func micButtonInteractionCompleted(_ velocity: CGPoint) {
//print("\(CFAbsoluteTimeGetCurrent()) completed")
if let modeTimeoutTimer = self.modeTimeoutTimer {
//print("\(CFAbsoluteTimeGetCurrent()) switch")
modeTimeoutTimer.invalidate()
self.modeTimeoutTimer = nil
self.switchMode()
}
self.endRecording(true)
}
public func micButtonInteractionUpdate(_ offset: CGPoint) {
self.controlsOffset = offset.x
self.offsetRecordingControls()
}
public func micButtonInteractionUpdateCancelTranslation(_ translation: CGFloat) {
self.cancelTranslation = translation
self.updateCancelTranslation()
}
public func micButtonInteractionLocked() {
self.updateLocked(true)
}
public func micButtonInteractionRequestedLockedAction() {
}
public func micButtonInteractionStopped() {
self.stopRecording()
}
public func micButtonShouldLock() -> Bool {
return true
}
public func micButtonPresenter() -> TGModernConversationInputMicButtonPresentation! {
let presenter = ChatTextInputMediaRecordingButtonPresenter(statusBarHost: self.statusBarHost, presentController: self.presentController)
presenter.button = self
self.currentPresenter = presenter.view()
return presenter
}
public func micButtonDecoration() -> (UIView & TGModernConversationInputMicButtonDecoration)! {
return micDecoration
}
public func micButtonLock() -> (UIView & TGModernConversationInputMicButtonLock)! {
return micLock
}
@objc private func handleStopTap() {
micButtonInteractionStopped()
}
override public func animateIn() {
super.animateIn()
if self.context.sharedContext.energyUsageSettings.fullTranslucency {
micDecoration.isHidden = false
micDecoration.startAnimating()
}
let transition = ContainedViewLayoutTransition.animated(duration: 0.15, curve: .easeInOut)
if let layer = self.animationView.view?.layer {
transition.updateAlpha(layer: layer, alpha: 0.0)
transition.updateTransformScale(layer: layer, scale: 0.3)
}
}
override public func animateOut(_ toSmallSize: Bool) {
super.animateOut(toSmallSize)
micDecoration.stopAnimating()
if toSmallSize {
micDecoration.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.03, delay: 0.15, removeOnCompletion: false)
} else {
micDecoration.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false)
let transition = ContainedViewLayoutTransition.animated(duration: 0.15, curve: .easeInOut)
if let layer = self.animationView.view?.layer {
transition.updateAlpha(layer: layer, alpha: 1.0)
transition.updateTransformScale(layer: layer, scale: 1.0)
}
}
}
private var previousSize = CGSize()
public func layoutItems() {
let size = self.bounds.size
if size != self.previousSize {
self.previousSize = size
if let view = self.animationView.view {
let iconSize = view.bounds.size
view.frame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)
}
}
}
}

View File

@@ -0,0 +1,98 @@
import UIKit
import LegacyComponents
import AppBundle
import Lottie
import TelegramPresentationData
final class LockView: UIButton, TGModernConversationInputMicButtonLock {
//private var colorCallbacks = [LOTValueDelegate]()
private let idleView: AnimationView = {
guard let url = getAppBundle().url(forResource: "LockWait", withExtension: "json"), let animation = Animation.filepath(url.path)
else { return AnimationView() }
let view = AnimationView(animation: animation, configuration: LottieConfiguration(renderingEngine: .mainThread, decodingStrategy: .codable))
view.loopMode = .autoReverse
view.backgroundColor = .clear
view.isOpaque = false
return view
}()
private let lockingView: AnimationView = {
guard let url = getAppBundle().url(forResource: "Lock", withExtension: "json"), let animation = Animation.filepath(url.path)
else { return AnimationView() }
let view = AnimationView(animation: animation, configuration: LottieConfiguration(renderingEngine: .mainThread, decodingStrategy: .codable))
view.backgroundColor = .clear
view.isOpaque = false
return view
}()
init(frame: CGRect, theme: PresentationTheme, strings: PresentationStrings) {
super.init(frame: frame)
accessibilityLabel = strings.VoiceOver_Recording_StopAndPreview
addSubview(idleView)
idleView.frame = bounds
addSubview(lockingView)
lockingView.frame = bounds
updateTheme(theme)
updateLockness(0)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func updateLockness(_ lockness: CGFloat) {
idleView.isHidden = lockness > 0
if lockness > 0 && idleView.isAnimationPlaying {
idleView.stop()
} else if lockness == 0 && !idleView.isAnimationPlaying {
idleView.play()
}
lockingView.isHidden = !idleView.isHidden
lockingView.currentProgress = lockness
}
func updateTheme(_ theme: PresentationTheme) {
//colorCallbacks.removeAll()
[
"Rectangle.Заливка 1": theme.chat.inputPanel.panelBackgroundColor,
"Rectangle.Rectangle.Обводка 1": theme.chat.inputPanel.panelControlAccentColor,
"Path.Path.Обводка 1": theme.chat.inputPanel.panelControlAccentColor,
"Path 4.Path 4.Обводка 1": theme.chat.inputPanel.panelControlAccentColor
].forEach { key, value in
idleView.setValueProvider(ColorValueProvider(value.lottieColorValue), keypath: AnimationKeypath(keypath: "\(key).Color"))
/*let colorCallback = LOTColorValueCallback(color: value.cgColor)
self.colorCallbacks.append(colorCallback)
idleView.setValueDelegate(colorCallback, for: LOTKeypath(string: "\(key).Color"))*/
}
[
"Path.Path.Обводка 1": theme.chat.inputPanel.panelControlAccentColor,
"Path.Path.Заливка 1": theme.chat.inputPanel.panelBackgroundColor,
"Rectangle.Rectangle.Обводка 1": theme.chat.inputPanel.panelControlAccentColor,
"Rectangle.Заливка 1": theme.chat.inputPanel.panelControlAccentColor,
"Path 4.Path 4.Обводка 1": theme.chat.inputPanel.panelControlAccentColor
].forEach { key, value in
lockingView.setValueProvider(ColorValueProvider(value.lottieColorValue), keypath: AnimationKeypath(keypath: "\(key).Color"))
/*let colorCallback = LOTColorValueCallback(color: value.cgColor)
self.colorCallbacks.append(colorCallback)
lockingView.setValueDelegate(colorCallback, for: LOTKeypath(string: "\(key).Color"))*/
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let superTest = super.hitTest(point, with: event)
if superTest === lockingView {
return self
}
return superTest
}
}