mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 01:10:09 +00:00
Reaction concept update
This commit is contained in:
parent
d9ac01d601
commit
d7a5983255
@ -19,6 +19,7 @@ swift_library(
|
|||||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||||
"//submodules/WebPBinding:WebPBinding",
|
"//submodules/WebPBinding:WebPBinding",
|
||||||
"//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode",
|
"//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode",
|
||||||
|
"//submodules/Components/ReactionImageComponent:ReactionImageComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -10,6 +10,27 @@ import TelegramPresentationData
|
|||||||
import UIKit
|
import UIKit
|
||||||
import WebPBinding
|
import WebPBinding
|
||||||
import AnimatedAvatarSetNode
|
import AnimatedAvatarSetNode
|
||||||
|
import ReactionImageComponent
|
||||||
|
|
||||||
|
public final class ReactionIconView: PortalSourceView {
|
||||||
|
fileprivate let imageView: UIImageView
|
||||||
|
|
||||||
|
override public init(frame: CGRect) {
|
||||||
|
self.imageView = UIImageView()
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.addSubview(self.imageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
|
transition.updateFrame(view: self.imageView, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
||||||
fileprivate final class ContainerButtonNode: HighlightTrackingButtonNode {
|
fileprivate final class ContainerButtonNode: HighlightTrackingButtonNode {
|
||||||
@ -272,7 +293,6 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|||||||
let spec: Spec
|
let spec: Spec
|
||||||
|
|
||||||
let backgroundColor: UInt32
|
let backgroundColor: UInt32
|
||||||
let clippingHeight: CGFloat
|
|
||||||
let sideInsets: CGFloat
|
let sideInsets: CGFloat
|
||||||
|
|
||||||
let imageFrame: CGRect
|
let imageFrame: CGRect
|
||||||
@ -281,46 +301,37 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|||||||
let counterFrame: CGRect?
|
let counterFrame: CGRect?
|
||||||
|
|
||||||
let backgroundLayout: ContainerButtonNode.Layout
|
let backgroundLayout: ContainerButtonNode.Layout
|
||||||
//let backgroundImage: UIImage
|
|
||||||
//let extractedBackgroundImage: UIImage
|
|
||||||
|
|
||||||
let size: CGSize
|
let size: CGSize
|
||||||
|
|
||||||
init(
|
init(
|
||||||
spec: Spec,
|
spec: Spec,
|
||||||
backgroundColor: UInt32,
|
backgroundColor: UInt32,
|
||||||
clippingHeight: CGFloat,
|
|
||||||
sideInsets: CGFloat,
|
sideInsets: CGFloat,
|
||||||
imageFrame: CGRect,
|
imageFrame: CGRect,
|
||||||
counterLayout: CounterLayout?,
|
counterLayout: CounterLayout?,
|
||||||
counterFrame: CGRect?,
|
counterFrame: CGRect?,
|
||||||
backgroundLayout: ContainerButtonNode.Layout,
|
backgroundLayout: ContainerButtonNode.Layout,
|
||||||
//backgroundImage: UIImage,
|
|
||||||
//extractedBackgroundImage: UIImage,
|
|
||||||
size: CGSize
|
size: CGSize
|
||||||
) {
|
) {
|
||||||
self.spec = spec
|
self.spec = spec
|
||||||
self.backgroundColor = backgroundColor
|
self.backgroundColor = backgroundColor
|
||||||
self.clippingHeight = clippingHeight
|
|
||||||
self.sideInsets = sideInsets
|
self.sideInsets = sideInsets
|
||||||
self.imageFrame = imageFrame
|
self.imageFrame = imageFrame
|
||||||
self.counterLayout = counterLayout
|
self.counterLayout = counterLayout
|
||||||
self.counterFrame = counterFrame
|
self.counterFrame = counterFrame
|
||||||
self.backgroundLayout = backgroundLayout
|
self.backgroundLayout = backgroundLayout
|
||||||
//self.backgroundImage = backgroundImage
|
|
||||||
//self.extractedBackgroundImage = extractedBackgroundImage
|
|
||||||
self.size = size
|
self.size = size
|
||||||
}
|
}
|
||||||
|
|
||||||
static func calculate(spec: Spec, currentLayout: Layout?) -> Layout {
|
static func calculate(spec: Spec, currentLayout: Layout?) -> Layout {
|
||||||
let clippingHeight: CGFloat = 22.0
|
|
||||||
let sideInsets: CGFloat = 8.0
|
let sideInsets: CGFloat = 8.0
|
||||||
let height: CGFloat = 30.0
|
let height: CGFloat = 30.0
|
||||||
let spacing: CGFloat = 4.0
|
let spacing: CGFloat = 4.0
|
||||||
|
|
||||||
let defaultImageSize = CGSize(width: 22.0, height: 22.0)
|
let defaultImageSize = CGSize(width: 26.0, height: 26.0)
|
||||||
let imageSize: CGSize
|
let imageSize: CGSize
|
||||||
if let file = spec.component.reaction.iconFile {
|
if let file = spec.component.reaction.centerAnimation {
|
||||||
imageSize = file.dimensions?.cgSize.aspectFitted(defaultImageSize) ?? defaultImageSize
|
imageSize = file.dimensions?.cgSize.aspectFitted(defaultImageSize) ?? defaultImageSize
|
||||||
} else {
|
} else {
|
||||||
imageSize = defaultImageSize
|
imageSize = defaultImageSize
|
||||||
@ -386,14 +397,11 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|||||||
return Layout(
|
return Layout(
|
||||||
spec: spec,
|
spec: spec,
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
clippingHeight: clippingHeight,
|
|
||||||
sideInsets: sideInsets,
|
sideInsets: sideInsets,
|
||||||
imageFrame: imageFrame,
|
imageFrame: imageFrame,
|
||||||
counterLayout: counterLayout,
|
counterLayout: counterLayout,
|
||||||
counterFrame: counterFrame,
|
counterFrame: counterFrame,
|
||||||
backgroundLayout: backgroundLayout,
|
backgroundLayout: backgroundLayout,
|
||||||
//backgroundImage: backgroundImage,
|
|
||||||
//extractedBackgroundImage: extractedBackgroundImage,
|
|
||||||
size: size
|
size: size
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -403,7 +411,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|||||||
|
|
||||||
public let containerNode: ContextExtractedContentContainingNode
|
public let containerNode: ContextExtractedContentContainingNode
|
||||||
private let buttonNode: ContainerButtonNode
|
private let buttonNode: ContainerButtonNode
|
||||||
public let iconView: UIImageView
|
public let iconView: ReactionIconView
|
||||||
private var avatarsView: AnimatedAvatarSetView?
|
private var avatarsView: AnimatedAvatarSetView?
|
||||||
|
|
||||||
private let iconImageDisposable = MetaDisposable()
|
private let iconImageDisposable = MetaDisposable()
|
||||||
@ -412,7 +420,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|||||||
self.containerNode = ContextExtractedContentContainingNode()
|
self.containerNode = ContextExtractedContentContainingNode()
|
||||||
self.buttonNode = ContainerButtonNode()
|
self.buttonNode = ContainerButtonNode()
|
||||||
|
|
||||||
self.iconView = UIImageView()
|
self.iconView = ReactionIconView()
|
||||||
self.iconView.isUserInteractionEnabled = false
|
self.iconView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
@ -480,18 +488,19 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|||||||
self.buttonNode.update(layout: layout.backgroundLayout)
|
self.buttonNode.update(layout: layout.backgroundLayout)
|
||||||
|
|
||||||
animation.animator.updateFrame(layer: self.iconView.layer, frame: layout.imageFrame, completion: nil)
|
animation.animator.updateFrame(layer: self.iconView.layer, frame: layout.imageFrame, completion: nil)
|
||||||
|
self.iconView.update(size: layout.imageFrame.size, transition: animation.transition)
|
||||||
|
|
||||||
if self.layout?.spec.component.reaction != layout.spec.component.reaction {
|
if self.layout?.spec.component.reaction != layout.spec.component.reaction {
|
||||||
if let file = layout.spec.component.reaction.iconFile {
|
if let file = layout.spec.component.reaction.centerAnimation {
|
||||||
self.iconImageDisposable.set((layout.spec.component.context.account.postbox.mediaBox.resourceData(file.resource)
|
self.iconImageDisposable.set((reactionStaticImage(context: layout.spec.component.context, animation: file, pixelSize: CGSize(width: 72.0, height: 72.0))
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] data in
|
|> deliverOnMainQueue).start(next: { [weak self] data in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.complete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
|
if data.isComplete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
|
||||||
if let image = WebP.convert(fromWebP: dataValue) {
|
if let image = UIImage(data: dataValue) {
|
||||||
strongSelf.iconView.image = image
|
strongSelf.iconView.imageView.image = image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@ -564,29 +573,21 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class ReactionButtonComponent: Component {
|
public final class ReactionButtonComponent: Equatable {
|
||||||
public struct ViewTag: Equatable {
|
|
||||||
public var value: String
|
|
||||||
|
|
||||||
public init(value: String) {
|
|
||||||
self.value = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct Reaction: Equatable {
|
public struct Reaction: Equatable {
|
||||||
public var value: String
|
public var value: String
|
||||||
public var iconFile: TelegramMediaFile?
|
public var centerAnimation: TelegramMediaFile?
|
||||||
|
|
||||||
public init(value: String, iconFile: TelegramMediaFile?) {
|
public init(value: String, centerAnimation: TelegramMediaFile?) {
|
||||||
self.value = value
|
self.value = value
|
||||||
self.iconFile = iconFile
|
self.centerAnimation = centerAnimation
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: Reaction, rhs: Reaction) -> Bool {
|
public static func ==(lhs: Reaction, rhs: Reaction) -> Bool {
|
||||||
if lhs.value != rhs.value {
|
if lhs.value != rhs.value {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.iconFile?.fileId != rhs.iconFile?.fileId {
|
if lhs.centerAnimation?.fileId != rhs.centerAnimation?.fileId {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -665,153 +666,6 @@ public final class ReactionButtonComponent: Component {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class View: UIButton, ComponentTaggedView {
|
|
||||||
public let iconView: UIImageView
|
|
||||||
private let textView: ComponentHostView<Empty>
|
|
||||||
private let measureTextView: ComponentHostView<Empty>
|
|
||||||
|
|
||||||
private var currentComponent: ReactionButtonComponent?
|
|
||||||
|
|
||||||
private let iconImageDisposable = MetaDisposable()
|
|
||||||
|
|
||||||
init() {
|
|
||||||
self.iconView = UIImageView()
|
|
||||||
self.iconView.isUserInteractionEnabled = false
|
|
||||||
|
|
||||||
self.textView = ComponentHostView<Empty>()
|
|
||||||
self.textView.isUserInteractionEnabled = false
|
|
||||||
|
|
||||||
self.measureTextView = ComponentHostView<Empty>()
|
|
||||||
self.measureTextView.isUserInteractionEnabled = false
|
|
||||||
|
|
||||||
super.init(frame: CGRect())
|
|
||||||
|
|
||||||
self.addSubview(self.iconView)
|
|
||||||
self.addSubview(self.textView)
|
|
||||||
|
|
||||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
|
||||||
preconditionFailure()
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
self.iconImageDisposable.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func pressed() {
|
|
||||||
guard let currentComponent = self.currentComponent else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
currentComponent.action(currentComponent.reaction.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func matches(tag: Any) -> Bool {
|
|
||||||
guard let tag = tag as? ViewTag else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
guard let currentComponent = self.currentComponent else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if currentComponent.reaction.value == tag.value {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(component: ReactionButtonComponent, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
|
||||||
let sideInsets: CGFloat = 8.0
|
|
||||||
let height: CGFloat = 30.0
|
|
||||||
let spacing: CGFloat = 4.0
|
|
||||||
|
|
||||||
let defaultImageSize = CGSize(width: 22.0, height: 22.0)
|
|
||||||
|
|
||||||
let imageSize: CGSize
|
|
||||||
if self.currentComponent?.reaction != component.reaction {
|
|
||||||
if let file = component.reaction.iconFile {
|
|
||||||
self.iconImageDisposable.set((component.context.account.postbox.mediaBox.resourceData(file.resource)
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] data in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.complete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
|
|
||||||
if let image = WebP.convert(fromWebP: dataValue) {
|
|
||||||
strongSelf.iconView.image = image
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
imageSize = file.dimensions?.cgSize.aspectFitted(defaultImageSize) ?? defaultImageSize
|
|
||||||
} else {
|
|
||||||
imageSize = defaultImageSize
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
imageSize = self.iconView.bounds.size
|
|
||||||
}
|
|
||||||
|
|
||||||
self.iconView.frame = CGRect(origin: CGPoint(x: sideInsets, y: floorToScreenPixels((height - imageSize.height) / 2.0)), size: imageSize)
|
|
||||||
|
|
||||||
let text = countString(Int64(component.count))
|
|
||||||
var measureText = ""
|
|
||||||
for _ in 0 ..< text.count {
|
|
||||||
measureText.append("0")
|
|
||||||
}
|
|
||||||
|
|
||||||
let minTextWidth = self.measureTextView.update(
|
|
||||||
transition: .immediate,
|
|
||||||
component: AnyComponent(Text(
|
|
||||||
text: measureText,
|
|
||||||
font: Font.regular(11.0),
|
|
||||||
color: .black
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
|
||||||
).width + 2.0
|
|
||||||
|
|
||||||
let actualTextSize: CGSize
|
|
||||||
if self.currentComponent?.count != component.count || self.currentComponent?.colors != component.colors || self.currentComponent?.isSelected != component.isSelected {
|
|
||||||
actualTextSize = self.textView.update(
|
|
||||||
transition: .immediate,
|
|
||||||
component: AnyComponent(Text(
|
|
||||||
text: text,
|
|
||||||
font: Font.medium(11.0),
|
|
||||||
color: UIColor(argb: component.isSelected ? component.colors.selectedForeground : component.colors.deselectedForeground)
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
actualTextSize = self.textView.bounds.size
|
|
||||||
}
|
|
||||||
let layoutTextSize = CGSize(width: max(actualTextSize.width, minTextWidth), height: actualTextSize.height)
|
|
||||||
|
|
||||||
if self.currentComponent?.colors != component.colors || self.currentComponent?.isSelected != component.isSelected {
|
|
||||||
if component.isSelected {
|
|
||||||
self.backgroundColor = UIColor(argb: component.colors.selectedBackground)
|
|
||||||
} else {
|
|
||||||
self.backgroundColor = UIColor(argb: component.colors.deselectedBackground)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.layer.cornerRadius = height / 2.0
|
|
||||||
|
|
||||||
self.textView.frame = CGRect(origin: CGPoint(x: sideInsets + imageSize.width + spacing, y: floorToScreenPixels((height - actualTextSize.height) / 2.0)), size: actualTextSize)
|
|
||||||
|
|
||||||
self.currentComponent = component
|
|
||||||
|
|
||||||
return CGSize(width: imageSize.width + spacing + layoutTextSize.width + sideInsets * 2.0, height: height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func makeView() -> View {
|
|
||||||
return View()
|
|
||||||
}
|
|
||||||
|
|
||||||
public func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
|
||||||
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class ReactionButtonsAsyncLayoutContainer {
|
public final class ReactionButtonsAsyncLayoutContainer {
|
||||||
|
|||||||
@ -17,6 +17,8 @@ swift_library(
|
|||||||
"//submodules/TelegramCore:TelegramCore",
|
"//submodules/TelegramCore:TelegramCore",
|
||||||
"//submodules/AccountContext:AccountContext",
|
"//submodules/AccountContext:AccountContext",
|
||||||
"//submodules/WebPBinding:WebPBinding",
|
"//submodules/WebPBinding:WebPBinding",
|
||||||
|
"//submodules/rlottie:RLottieBinding",
|
||||||
|
"//submodules/GZip:GZip",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -9,6 +9,65 @@ import AccountContext
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import UIKit
|
import UIKit
|
||||||
import WebPBinding
|
import WebPBinding
|
||||||
|
import RLottieBinding
|
||||||
|
import GZip
|
||||||
|
|
||||||
|
public func reactionStaticImage(context: AccountContext, animation: TelegramMediaFile, pixelSize: CGSize) -> Signal<EngineMediaResource.ResourceData, NoError> {
|
||||||
|
return context.engine.resources.custom(id: "\(animation.resource.id.stringRepresentation):reaction-static-v7", fetch: EngineMediaResource.Fetch {
|
||||||
|
return Signal { subscriber in
|
||||||
|
let fetchDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: MediaResourceReference.standalone(resource: animation.resource)).start()
|
||||||
|
let dataDisposable = context.account.postbox.mediaBox.resourceData(animation.resource).start(next: { data in
|
||||||
|
if !data.complete {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let data = try? Data(contentsOf: URL(fileURLWithPath: data.path)) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let unpackedData = TGGUnzipData(data, 5 * 1024 * 1024) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let instance = LottieInstance(data: unpackedData, fitzModifier: .none, cacheKey: "") else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let innerInsets = UIEdgeInsets(top: 2.4, left: 2.4, bottom: 2.4, right: 2.4)
|
||||||
|
let renderContext = DrawingContext(size: CGSize(width: floor(pixelSize.width * (1.0 + innerInsets.left + innerInsets.right)), height: floor(pixelSize.height * (1.0 + innerInsets.bottom + innerInsets.top))), scale: 1.0, clear: true)
|
||||||
|
|
||||||
|
instance.renderFrame(with: Int32(instance.frameCount - 1), into: renderContext.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(renderContext.size.width * renderContext.scale), height: Int32(renderContext.size.height * renderContext.scale), bytesPerRow: Int32(renderContext.bytesPerRow))
|
||||||
|
|
||||||
|
guard let image = renderContext.generateImage() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let clippingContext = DrawingContext(size: pixelSize, scale: 1.0, clear: true)
|
||||||
|
clippingContext.withContext { context in
|
||||||
|
UIGraphicsPushContext(context)
|
||||||
|
|
||||||
|
image.draw(at: CGPoint(x: -innerInsets.left * pixelSize.width, y: -innerInsets.top * pixelSize.height))
|
||||||
|
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let pngData = clippingContext.generateImage()?.pngData() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let tempFile = TempBox.shared.tempFile(fileName: "image.png")
|
||||||
|
guard let _ = try? pngData.write(to: URL(fileURLWithPath: tempFile.path)) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriber.putNext(.moveTempFile(file: tempFile))
|
||||||
|
subscriber.putCompletion()
|
||||||
|
})
|
||||||
|
|
||||||
|
return ActionDisposable {
|
||||||
|
fetchDisposable.dispose()
|
||||||
|
dataDisposable.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
public final class ReactionImageNode: ASImageNode {
|
public final class ReactionImageNode: ASImageNode {
|
||||||
private var disposable: Disposable?
|
private var disposable: Disposable?
|
||||||
|
|||||||
@ -1244,9 +1244,9 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
|
func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, completion: @escaping () -> Void) {
|
||||||
if let presentationNode = self.presentationNode {
|
if let presentationNode = self.presentationNode {
|
||||||
presentationNode.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, completion: completion)
|
presentationNode.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, animateTargetContainer: animateTargetContainer, completion: completion)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1264,7 +1264,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||||||
|
|
||||||
self.reactionContextNodeIsAnimatingOut = true
|
self.reactionContextNodeIsAnimatingOut = true
|
||||||
reactionContextNode.willAnimateOutToReaction(value: value)
|
reactionContextNode.willAnimateOutToReaction(value: value)
|
||||||
reactionContextNode.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, completion: { [weak self] in
|
reactionContextNode.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, animateTargetContainer: animateTargetContainer, completion: { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -2375,10 +2375,10 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
|||||||
self.dismissed?()
|
self.dismissed?()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func dismissWithReaction(value: String, targetView: UIView, hideNode: Bool, completion: (() -> Void)?) {
|
public func dismissWithReaction(value: String, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, completion: (() -> Void)?) {
|
||||||
if !self.wasDismissed {
|
if !self.wasDismissed {
|
||||||
self.wasDismissed = true
|
self.wasDismissed = true
|
||||||
self.controllerNode.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, completion: { [weak self] in
|
self.controllerNode.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, animateTargetContainer: animateTargetContainer, completion: { [weak self] in
|
||||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||||
completion?()
|
completion?()
|
||||||
})
|
})
|
||||||
|
|||||||
@ -675,7 +675,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
|
func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, completion: @escaping () -> Void) {
|
||||||
guard let reactionContextNode = self.reactionContextNode else {
|
guard let reactionContextNode = self.reactionContextNode else {
|
||||||
self.requestAnimateOut(.default, completion)
|
self.requestAnimateOut(.default, completion)
|
||||||
return
|
return
|
||||||
@ -697,7 +697,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
|||||||
intermediateCompletion()
|
intermediateCompletion()
|
||||||
})
|
})
|
||||||
|
|
||||||
reactionContextNode.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, completion: { [weak self] in
|
reactionContextNode.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, animateTargetContainer: animateTargetContainer, completion: { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,7 @@ protocol ContextControllerPresentationNode: ASDisplayNode {
|
|||||||
stateTransition: ContextControllerPresentationNodeStateTransition?
|
stateTransition: ContextControllerPresentationNodeStateTransition?
|
||||||
)
|
)
|
||||||
|
|
||||||
func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void)
|
func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, completion: @escaping () -> Void)
|
||||||
func cancelReactionAnimation()
|
func cancelReactionAnimation()
|
||||||
|
|
||||||
func highlightGestureMoved(location: CGPoint)
|
func highlightGestureMoved(location: CGPoint)
|
||||||
|
|||||||
@ -57,6 +57,9 @@ private final class ChildWindowHostView: UIView, WindowHost {
|
|||||||
|
|
||||||
func presentInGlobalOverlay(_ controller: ContainableController) {
|
func presentInGlobalOverlay(_ controller: ContainableController) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addGlobalPortalHostView(sourceView: PortalSourceView) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func childWindowHostView(parent: UIView) -> WindowHostView {
|
public func childWindowHostView(parent: UIView) -> WindowHostView {
|
||||||
|
|||||||
@ -53,6 +53,8 @@ final class GlobalOverlayPresentationContext {
|
|||||||
|
|
||||||
private(set) var controllers: [ContainableController] = []
|
private(set) var controllers: [ContainableController] = []
|
||||||
|
|
||||||
|
private var globalPortalViews: [GlobalPortalView] = []
|
||||||
|
|
||||||
private var presentationDisposables = DisposableSet()
|
private var presentationDisposables = DisposableSet()
|
||||||
private var layout: ContainerViewLayout?
|
private var layout: ContainerViewLayout?
|
||||||
|
|
||||||
@ -184,6 +186,34 @@ final class GlobalOverlayPresentationContext {
|
|||||||
transition.updateFrame(node: controller.displayNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
transition.updateFrame(node: controller.displayNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
controller.containerLayoutUpdated(layout, transition: transition)
|
controller.containerLayoutUpdated(layout, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for globalPortalView in self.globalPortalViews {
|
||||||
|
transition.updateFrame(view: globalPortalView.view, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func addGlobalPortalHostView(sourceView: PortalSourceView) {
|
||||||
|
guard let globalPortalView = GlobalPortalView(wasRemoved: { [weak self] globalPortalView in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let index = strongSelf.globalPortalViews.firstIndex(where: { $0 === globalPortalView }) {
|
||||||
|
strongSelf.globalPortalViews.remove(at: index)
|
||||||
|
}
|
||||||
|
globalPortalView.view.removeFromSuperview()
|
||||||
|
}) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
globalPortalView.view.isUserInteractionEnabled = false
|
||||||
|
self.globalPortalViews.append(globalPortalView)
|
||||||
|
|
||||||
|
sourceView.setGlobalPortal(view: globalPortalView)
|
||||||
|
|
||||||
|
if let presentationView = self.currentPresentationView(underStatusBar: true), let initialLayout = self.layout {
|
||||||
|
presentationView.addSubview(globalPortalView.view)
|
||||||
|
globalPortalView.view.frame = CGRect(origin: CGPoint(), size: initialLayout.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,6 +250,14 @@ final class GlobalOverlayPresentationContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !self.globalPortalViews.isEmpty, let view = self.currentPresentationView(underStatusBar: true) {
|
||||||
|
for globalPortalView in self.globalPortalViews {
|
||||||
|
view.addSubview(globalPortalView.view)
|
||||||
|
|
||||||
|
globalPortalView.view.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: layout.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,6 +267,10 @@ final class GlobalOverlayPresentationContext {
|
|||||||
controller.view.removeFromSuperview()
|
controller.view.removeFromSuperview()
|
||||||
controller.viewDidDisappear(false)
|
controller.viewDidDisappear(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for globalPortalView in self.globalPortalViews {
|
||||||
|
globalPortalView.view.removeFromSuperview()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
|||||||
19
submodules/Display/Source/GlobalPortalView.swift
Normal file
19
submodules/Display/Source/GlobalPortalView.swift
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import UIKit
|
||||||
|
|
||||||
|
final class GlobalPortalView: PortalView {
|
||||||
|
private let wasRemoved: (GlobalPortalView) -> Void
|
||||||
|
|
||||||
|
init?(wasRemoved: @escaping (GlobalPortalView) -> Void) {
|
||||||
|
self.wasRemoved = wasRemoved
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func triggerWasRemoved() {
|
||||||
|
self.wasRemoved(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import AudioToolbox
|
import AudioToolbox
|
||||||
|
import CoreHaptics
|
||||||
|
|
||||||
public enum ImpactHapticFeedbackStyle: Hashable {
|
public enum ImpactHapticFeedbackStyle: Hashable {
|
||||||
case light
|
case light
|
||||||
@ -193,3 +194,38 @@ public final class HapticFeedback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(iOS 13.0, *)
|
||||||
|
public final class ContinuousHaptic {
|
||||||
|
private let engine: CHHapticEngine
|
||||||
|
private let player: CHHapticPatternPlayer
|
||||||
|
|
||||||
|
public init(duration: Double) throws {
|
||||||
|
self.engine = try CHHapticEngine()
|
||||||
|
|
||||||
|
var events: [CHHapticEvent] = []
|
||||||
|
for i in 0 ... 10 {
|
||||||
|
let t = CGFloat(i) / 10.0
|
||||||
|
|
||||||
|
let intensity = CHHapticEventParameter(parameterID: .hapticIntensity, value: Float((1.0 - t) * 0.1 + t * 1.0))
|
||||||
|
let sharpness = CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.3)
|
||||||
|
let eventDuration: Double
|
||||||
|
if i == 10 {
|
||||||
|
eventDuration = 100.0
|
||||||
|
} else {
|
||||||
|
eventDuration = duration
|
||||||
|
}
|
||||||
|
let event = CHHapticEvent(eventType: .hapticContinuous, parameters: [intensity, sharpness], relativeTime: Double(i) / 10.0 * duration, duration: eventDuration)
|
||||||
|
events.append(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
let pattern = try CHHapticPattern(events: events, parameters: [])
|
||||||
|
self.player = try self.engine.makePlayer(with: pattern)
|
||||||
|
|
||||||
|
try self.engine.start()
|
||||||
|
try self.player.start(atTime: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.engine.stop(completionHandler: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -234,6 +234,7 @@ private final class NativeWindow: UIWindow, WindowHost {
|
|||||||
var updateToInterfaceOrientation: ((UIInterfaceOrientation) -> Void)?
|
var updateToInterfaceOrientation: ((UIInterfaceOrientation) -> Void)?
|
||||||
var presentController: ((ContainableController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)?
|
var presentController: ((ContainableController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)?
|
||||||
var presentControllerInGlobalOverlay: ((_ controller: ContainableController) -> Void)?
|
var presentControllerInGlobalOverlay: ((_ controller: ContainableController) -> Void)?
|
||||||
|
var addGlobalPortalHostViewImpl: ((PortalSourceView) -> Void)?
|
||||||
var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)?
|
var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)?
|
||||||
var presentNativeImpl: ((UIViewController) -> Void)?
|
var presentNativeImpl: ((UIViewController) -> Void)?
|
||||||
var invalidateDeferScreenEdgeGestureImpl: (() -> Void)?
|
var invalidateDeferScreenEdgeGestureImpl: (() -> Void)?
|
||||||
@ -321,6 +322,10 @@ private final class NativeWindow: UIWindow, WindowHost {
|
|||||||
self.presentControllerInGlobalOverlay?(controller)
|
self.presentControllerInGlobalOverlay?(controller)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addGlobalPortalHostView(sourceView: PortalSourceView) {
|
||||||
|
self.addGlobalPortalHostViewImpl?(sourceView)
|
||||||
|
}
|
||||||
|
|
||||||
func presentNative(_ controller: UIViewController) {
|
func presentNative(_ controller: UIViewController) {
|
||||||
self.presentNativeImpl?(controller)
|
self.presentNativeImpl?(controller)
|
||||||
}
|
}
|
||||||
@ -396,6 +401,10 @@ public func nativeWindowHostView() -> (UIWindow & WindowHost, WindowHostView) {
|
|||||||
hostView?.presentInGlobalOverlay?(controller)
|
hostView?.presentInGlobalOverlay?(controller)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.addGlobalPortalHostViewImpl = { [weak hostView] sourceView in
|
||||||
|
hostView?.addGlobalPortalHostViewImpl?(sourceView)
|
||||||
|
}
|
||||||
|
|
||||||
window.presentNativeImpl = { [weak hostView] controller in
|
window.presentNativeImpl = { [weak hostView] controller in
|
||||||
hostView?.presentNative?(controller)
|
hostView?.presentNative?(controller)
|
||||||
}
|
}
|
||||||
|
|||||||
81
submodules/Display/Source/PortalSourceView.swift
Normal file
81
submodules/Display/Source/PortalSourceView.swift
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import UIKit
|
||||||
|
|
||||||
|
open class PortalSourceView: UIView {
|
||||||
|
private final class PortalReference {
|
||||||
|
weak var portalView: PortalView?
|
||||||
|
|
||||||
|
init(portalView: PortalView) {
|
||||||
|
self.portalView = portalView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var portalReferences: [PortalReference] = []
|
||||||
|
private weak var globalPortalView: GlobalPortalView?
|
||||||
|
|
||||||
|
public final var needsGlobalPortal: Bool = false {
|
||||||
|
didSet {
|
||||||
|
if self.needsGlobalPortal != oldValue {
|
||||||
|
if self.needsGlobalPortal {
|
||||||
|
self.alpha = 0.0
|
||||||
|
|
||||||
|
if let windowHost = self.windowHost {
|
||||||
|
windowHost.addGlobalPortalHostView(sourceView: self)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.alpha = 1.0
|
||||||
|
|
||||||
|
if let globalPortalView = self.globalPortalView {
|
||||||
|
self.globalPortalView = nil
|
||||||
|
|
||||||
|
globalPortalView.triggerWasRemoved()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
if let globalPortalView = self.globalPortalView {
|
||||||
|
globalPortalView.triggerWasRemoved()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func addPortal(view: PortalView) {
|
||||||
|
self.portalReferences.append(PortalReference(portalView: view))
|
||||||
|
if self.window != nil {
|
||||||
|
view.reloadPortal(sourceView: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setGlobalPortal(view: GlobalPortalView?) {
|
||||||
|
if let globalPortalView = self.globalPortalView {
|
||||||
|
self.globalPortalView = nil
|
||||||
|
|
||||||
|
globalPortalView.triggerWasRemoved()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let view = view {
|
||||||
|
self.globalPortalView = view
|
||||||
|
|
||||||
|
if self.window != nil {
|
||||||
|
view.reloadPortal(sourceView: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override open func didMoveToWindow() {
|
||||||
|
super.didMoveToWindow()
|
||||||
|
|
||||||
|
if self.window != nil {
|
||||||
|
for portalReference in self.portalReferences {
|
||||||
|
if let portalView = portalReference.portalView {
|
||||||
|
portalView.reloadPortal(sourceView: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.needsGlobalPortal, self.globalPortalView == nil, let windowHost = self.windowHost {
|
||||||
|
windowHost.addGlobalPortalHostView(sourceView: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
submodules/Display/Source/PortalView.swift
Normal file
21
submodules/Display/Source/PortalView.swift
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import UIKit
|
||||||
|
import UIKitRuntimeUtils
|
||||||
|
|
||||||
|
public class PortalView {
|
||||||
|
public let view: UIView & UIKitPortalViewProtocol
|
||||||
|
|
||||||
|
public init?() {
|
||||||
|
guard let view = makePortalView() else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
self.view = view
|
||||||
|
}
|
||||||
|
|
||||||
|
func reloadPortal(sourceView: PortalSourceView) {
|
||||||
|
self.view.sourceView = sourceView
|
||||||
|
|
||||||
|
if let portalSuperview = self.view.superview, let index = portalSuperview.subviews.firstIndex(of: self.view) {
|
||||||
|
portalSuperview.insertSubview(self.view, at: index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -19,7 +19,7 @@ private func findCurrentResponder(_ view: UIView) -> UIResponder? {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func findWindow(_ view: UIView) -> WindowHost? {
|
func findWindow(_ view: UIView) -> WindowHost? {
|
||||||
if let view = view as? WindowHost {
|
if let view = view as? WindowHost {
|
||||||
return view
|
return view
|
||||||
} else if let superview = view.superview {
|
} else if let superview = view.superview {
|
||||||
@ -569,6 +569,10 @@ public enum TabBarItemContextActionType {
|
|||||||
self.window?.presentInGlobalOverlay(controller)
|
self.window?.presentInGlobalOverlay(controller)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func addGlobalPortalHostView(sourceView: PortalSourceView) {
|
||||||
|
self.window?.addGlobalPortalHostView(sourceView: sourceView)
|
||||||
|
}
|
||||||
|
|
||||||
open override func viewWillDisappear(_ animated: Bool) {
|
open override func viewWillDisappear(_ animated: Bool) {
|
||||||
self.activeInputViewCandidate = findCurrentResponder(self.view)
|
self.activeInputViewCandidate = findCurrentResponder(self.view)
|
||||||
|
|
||||||
|
|||||||
@ -163,6 +163,7 @@ public final class WindowHostView {
|
|||||||
|
|
||||||
var present: ((ContainableController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)?
|
var present: ((ContainableController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)?
|
||||||
var presentInGlobalOverlay: ((_ controller: ContainableController) -> Void)?
|
var presentInGlobalOverlay: ((_ controller: ContainableController) -> Void)?
|
||||||
|
var addGlobalPortalHostViewImpl: ((PortalSourceView) -> Void)?
|
||||||
var presentNative: ((UIViewController) -> Void)?
|
var presentNative: ((UIViewController) -> Void)?
|
||||||
var nativeController: (() -> UIViewController?)?
|
var nativeController: (() -> UIViewController?)?
|
||||||
var updateSize: ((CGSize, Double) -> Void)?
|
var updateSize: ((CGSize, Double) -> Void)?
|
||||||
@ -200,12 +201,25 @@ public protocol WindowHost {
|
|||||||
func forEachController(_ f: (ContainableController) -> Void)
|
func forEachController(_ f: (ContainableController) -> Void)
|
||||||
func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool, completion: @escaping () -> Void)
|
func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool, completion: @escaping () -> Void)
|
||||||
func presentInGlobalOverlay(_ controller: ContainableController)
|
func presentInGlobalOverlay(_ controller: ContainableController)
|
||||||
|
func addGlobalPortalHostView(sourceView: PortalSourceView)
|
||||||
func invalidateDeferScreenEdgeGestures()
|
func invalidateDeferScreenEdgeGestures()
|
||||||
func invalidatePrefersOnScreenNavigationHidden()
|
func invalidatePrefersOnScreenNavigationHidden()
|
||||||
func invalidateSupportedOrientations()
|
func invalidateSupportedOrientations()
|
||||||
func cancelInteractiveKeyboardGestures()
|
func cancelInteractiveKeyboardGestures()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public extension UIView {
|
||||||
|
var windowHost: WindowHost? {
|
||||||
|
if let window = self.window as? WindowHost {
|
||||||
|
return window
|
||||||
|
} else if let result = findWindow(self) {
|
||||||
|
return result
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func layoutMetricsForScreenSize(_ size: CGSize) -> LayoutMetrics {
|
private func layoutMetricsForScreenSize(_ size: CGSize) -> LayoutMetrics {
|
||||||
if size.width > 690.0 && size.height > 690.0 {
|
if size.width > 690.0 && size.height > 690.0 {
|
||||||
return LayoutMetrics(widthClass: .regular, heightClass: .regular)
|
return LayoutMetrics(widthClass: .regular, heightClass: .regular)
|
||||||
@ -379,6 +393,10 @@ public class Window1 {
|
|||||||
self?.presentInGlobalOverlay(controller)
|
self?.presentInGlobalOverlay(controller)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.hostView.addGlobalPortalHostViewImpl = { [weak self] sourceView in
|
||||||
|
self?.addGlobalPortalHostView(sourceView: sourceView)
|
||||||
|
}
|
||||||
|
|
||||||
self.hostView.presentNative = { [weak self] controller in
|
self.hostView.presentNative = { [weak self] controller in
|
||||||
self?.presentNative(controller)
|
self?.presentNative(controller)
|
||||||
}
|
}
|
||||||
@ -1105,6 +1123,10 @@ public class Window1 {
|
|||||||
self.overlayPresentationContext.present(controller)
|
self.overlayPresentationContext.present(controller)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func addGlobalPortalHostView(sourceView: PortalSourceView) {
|
||||||
|
self.overlayPresentationContext.addGlobalPortalHostView(sourceView: sourceView)
|
||||||
|
}
|
||||||
|
|
||||||
public func presentNative(_ controller: UIViewController) {
|
public func presentNative(_ controller: UIViewController) {
|
||||||
if let nativeController = self.hostView.nativeController?() {
|
if let nativeController = self.hostView.nativeController?() {
|
||||||
nativeController.present(controller, animated: true, completion: nil)
|
nativeController.present(controller, animated: true, completion: nil)
|
||||||
|
|||||||
@ -19,6 +19,7 @@ swift_library(
|
|||||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||||
"//submodules/StickerResources:StickerResources",
|
"//submodules/StickerResources:StickerResources",
|
||||||
"//submodules/AccountContext:AccountContext",
|
"//submodules/AccountContext:AccountContext",
|
||||||
|
"//submodules/Components/ReactionButtonListComponent:ReactionButtonListComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -163,11 +163,11 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func animateIn() {
|
func animateIn() {
|
||||||
let smallCircleDuration: Double = 0.35
|
let smallCircleDuration: Double = 0.4
|
||||||
let largeCircleDuration: Double = 0.35
|
let largeCircleDuration: Double = 0.4
|
||||||
let largeCircleDelay: Double = 0.13
|
let largeCircleDelay: Double = 0.0
|
||||||
let mainCircleDuration: Double = 0.25
|
let mainCircleDuration: Double = 0.3
|
||||||
let mainCircleDelay: Double = 0.16
|
let mainCircleDelay: Double = 0.0
|
||||||
|
|
||||||
self.smallCircleLayer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: smallCircleDuration, delay: 0.0)
|
self.smallCircleLayer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: smallCircleDuration, delay: 0.0)
|
||||||
|
|
||||||
@ -181,9 +181,9 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func animateInFromAnchorRect(size: CGSize, sourceBackgroundFrame: CGRect) {
|
func animateInFromAnchorRect(size: CGSize, sourceBackgroundFrame: CGRect) {
|
||||||
let springDuration: Double = 0.2
|
let springDuration: Double = 0.3
|
||||||
let springDamping: CGFloat = 104.0
|
let springDamping: CGFloat = 104.0
|
||||||
let springDelay: Double = 0.25
|
let springDelay: Double = 0.05
|
||||||
let shadowInset: CGFloat = 15.0
|
let shadowInset: CGFloat = 15.0
|
||||||
|
|
||||||
let contentBounds = self.backgroundNode.frame
|
let contentBounds = self.backgroundNode.frame
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import TelegramCore
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import AccountContext
|
import AccountContext
|
||||||
import TelegramAnimatedStickerNode
|
import TelegramAnimatedStickerNode
|
||||||
|
import ReactionButtonListComponent
|
||||||
|
|
||||||
public final class ReactionContextItem {
|
public final class ReactionContextItem {
|
||||||
public struct Reaction: Equatable {
|
public struct Reaction: Equatable {
|
||||||
@ -53,8 +54,11 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
private let previewingItemContainer: ASDisplayNode
|
private let previewingItemContainer: ASDisplayNode
|
||||||
private var visibleItemNodes: [Int: ReactionNode] = [:]
|
private var visibleItemNodes: [Int: ReactionNode] = [:]
|
||||||
|
|
||||||
|
private weak var currentLongPressItemNode: ReactionNode?
|
||||||
|
|
||||||
private var isExpanded: Bool = true
|
private var isExpanded: Bool = true
|
||||||
private var highlightedReaction: ReactionContextItem.Reaction?
|
private var highlightedReaction: ReactionContextItem.Reaction?
|
||||||
|
private var continuousHaptic: Any?
|
||||||
private var validLayout: (CGSize, UIEdgeInsets, CGRect)?
|
private var validLayout: (CGSize, UIEdgeInsets, CGRect)?
|
||||||
private var isLeftAligned: Bool = true
|
private var isLeftAligned: Bool = true
|
||||||
|
|
||||||
@ -82,6 +86,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.scrollNode.view.scrollsToTop = false
|
self.scrollNode.view.scrollsToTop = false
|
||||||
self.scrollNode.view.delaysContentTouches = false
|
self.scrollNode.view.delaysContentTouches = false
|
||||||
self.scrollNode.view.canCancelContentTouches = true
|
self.scrollNode.view.canCancelContentTouches = true
|
||||||
|
self.scrollNode.clipsToBounds = false
|
||||||
if #available(iOS 11.0, *) {
|
if #available(iOS 11.0, *) {
|
||||||
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
|
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
|
||||||
}
|
}
|
||||||
@ -132,6 +137,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||||
|
|
||||||
|
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.longPressGesture(_:)))
|
||||||
|
longPressGesture.minimumPressDuration = 0.2
|
||||||
|
self.view.addGestureRecognizer(longPressGesture)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateLayout(size: CGSize, insets: UIEdgeInsets, anchorRect: CGRect, transition: ContainedViewLayoutTransition) {
|
public func updateLayout(size: CGSize, insets: UIEdgeInsets, anchorRect: CGRect, transition: ContainedViewLayoutTransition) {
|
||||||
@ -142,7 +151,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.backgroundNode.updateIsIntersectingContent(isIntersectingContent: isIntersectingContent, transition: transition)
|
self.backgroundNode.updateIsIntersectingContent(isIntersectingContent: isIntersectingContent, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func calculateBackgroundFrame(containerSize: CGSize, insets: UIEdgeInsets, anchorRect: CGRect, contentSize: CGSize) -> (backgroundFrame: CGRect, isLeftAligned: Bool, cloudSourcePoint: CGFloat) {
|
private func calculateBackgroundFrame(containerSize: CGSize, insets: UIEdgeInsets, anchorRect: CGRect, contentSize: CGSize) -> (backgroundFrame: CGRect, visualBackgroundFrame: CGRect, isLeftAligned: Bool, cloudSourcePoint: CGFloat) {
|
||||||
var contentSize = contentSize
|
var contentSize = contentSize
|
||||||
contentSize.width = max(52.0, contentSize.width)
|
contentSize.width = max(52.0, contentSize.width)
|
||||||
contentSize.height = 52.0
|
contentSize.height = 52.0
|
||||||
@ -178,12 +187,14 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
cloudSourcePoint = max(rect.minX + rect.height / 2.0, anchorRect.minX)
|
cloudSourcePoint = max(rect.minX + rect.height / 2.0, anchorRect.minX)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var visualRect = rect
|
||||||
|
|
||||||
if self.highlightedReaction != nil {
|
if self.highlightedReaction != nil {
|
||||||
rect.origin.x -= 2.0
|
visualRect.origin.x -= 4.0
|
||||||
rect.size.width += 4.0
|
visualRect.size.width += 8.0
|
||||||
}
|
}
|
||||||
|
|
||||||
return (rect, isLeftAligned, cloudSourcePoint)
|
return (rect, visualRect, isLeftAligned, cloudSourcePoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
@ -265,7 +276,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
itemNode.updateLayout(size: itemFrame.size, isExpanded: false, isPreviewing: isPreviewing, transition: transition)
|
itemNode.updateLayout(size: itemFrame.size, isExpanded: false, isPreviewing: isPreviewing, transition: transition)
|
||||||
|
|
||||||
if animateIn {
|
if animateIn {
|
||||||
itemNode.animateIn()
|
itemNode.appear(animated: !self.context.sharedContext.currentPresentationData.with({ $0 }).reduceMotion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -305,41 +316,41 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
backgroundInsets.left += sideInset
|
backgroundInsets.left += sideInset
|
||||||
backgroundInsets.right += sideInset
|
backgroundInsets.right += sideInset
|
||||||
|
|
||||||
let (backgroundFrame, isLeftAligned, cloudSourcePoint) = self.calculateBackgroundFrame(containerSize: CGSize(width: size.width, height: size.height), insets: backgroundInsets, anchorRect: anchorRect, contentSize: CGSize(width: visibleContentWidth, height: contentHeight))
|
let (actualBackgroundFrame, visualBackgroundFrame, isLeftAligned, cloudSourcePoint) = self.calculateBackgroundFrame(containerSize: CGSize(width: size.width, height: size.height), insets: backgroundInsets, anchorRect: anchorRect, contentSize: CGSize(width: visibleContentWidth, height: contentHeight))
|
||||||
self.isLeftAligned = isLeftAligned
|
self.isLeftAligned = isLeftAligned
|
||||||
|
|
||||||
transition.updateFrame(node: self.contentContainer, frame: backgroundFrame, beginWithCurrentState: true)
|
transition.updateFrame(node: self.contentContainer, frame: visualBackgroundFrame, beginWithCurrentState: true)
|
||||||
transition.updateFrame(view: self.contentContainerMask, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size), beginWithCurrentState: true)
|
transition.updateFrame(view: self.contentContainerMask, frame: CGRect(origin: CGPoint(), size: visualBackgroundFrame.size), beginWithCurrentState: true)
|
||||||
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size), beginWithCurrentState: true)
|
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: actualBackgroundFrame.size), beginWithCurrentState: true)
|
||||||
transition.updateFrame(node: self.previewingItemContainer, frame: backgroundFrame, beginWithCurrentState: true)
|
transition.updateFrame(node: self.previewingItemContainer, frame: visualBackgroundFrame, beginWithCurrentState: true)
|
||||||
self.scrollNode.view.contentSize = CGSize(width: completeContentWidth, height: backgroundFrame.size.height)
|
self.scrollNode.view.contentSize = CGSize(width: completeContentWidth, height: visualBackgroundFrame.size.height)
|
||||||
|
|
||||||
self.updateScrolling(transition: transition)
|
self.updateScrolling(transition: transition)
|
||||||
|
|
||||||
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame, beginWithCurrentState: true)
|
transition.updateFrame(node: self.backgroundNode, frame: visualBackgroundFrame, beginWithCurrentState: true)
|
||||||
self.backgroundNode.update(
|
self.backgroundNode.update(
|
||||||
theme: self.theme,
|
theme: self.theme,
|
||||||
size: backgroundFrame.size,
|
size: visualBackgroundFrame.size,
|
||||||
cloudSourcePoint: cloudSourcePoint - backgroundFrame.minX,
|
cloudSourcePoint: cloudSourcePoint - visualBackgroundFrame.minX,
|
||||||
isLeftAligned: isLeftAligned,
|
isLeftAligned: isLeftAligned,
|
||||||
transition: transition
|
transition: transition
|
||||||
)
|
)
|
||||||
|
|
||||||
if let animateInFromAnchorRect = animateInFromAnchorRect {
|
if let animateInFromAnchorRect = animateInFromAnchorRect {
|
||||||
let springDuration: Double = 0.42
|
let springDuration: Double = 0.3
|
||||||
let springDamping: CGFloat = 104.0
|
let springDamping: CGFloat = 104.0
|
||||||
let springDelay: Double = 0.22
|
let springDelay: Double = 0.05
|
||||||
|
|
||||||
let sourceBackgroundFrame = self.calculateBackgroundFrame(containerSize: size, insets: backgroundInsets, anchorRect: animateInFromAnchorRect, contentSize: CGSize(width: backgroundFrame.height, height: contentHeight)).0
|
let sourceBackgroundFrame = self.calculateBackgroundFrame(containerSize: size, insets: backgroundInsets, anchorRect: animateInFromAnchorRect, contentSize: CGSize(width: visualBackgroundFrame.height, height: contentHeight)).0
|
||||||
|
|
||||||
self.backgroundNode.animateInFromAnchorRect(size: backgroundFrame.size, sourceBackgroundFrame: sourceBackgroundFrame.offsetBy(dx: -backgroundFrame.minX, dy: -backgroundFrame.minY))
|
self.backgroundNode.animateInFromAnchorRect(size: visualBackgroundFrame.size, sourceBackgroundFrame: sourceBackgroundFrame.offsetBy(dx: -visualBackgroundFrame.minX, dy: -visualBackgroundFrame.minY))
|
||||||
|
|
||||||
self.contentContainer.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: sourceBackgroundFrame.midX - backgroundFrame.midX, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true)
|
self.contentContainer.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: sourceBackgroundFrame.midX - visualBackgroundFrame.midX, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true)
|
||||||
self.contentContainer.layer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: sourceBackgroundFrame.size)), to: NSValue(cgRect: CGRect(origin: CGPoint(), size: backgroundFrame.size)), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping)
|
self.contentContainer.layer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: sourceBackgroundFrame.size)), to: NSValue(cgRect: CGRect(origin: CGPoint(), size: visualBackgroundFrame.size)), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping)
|
||||||
} else if let animateOutToAnchorRect = animateOutToAnchorRect {
|
} else if let animateOutToAnchorRect = animateOutToAnchorRect {
|
||||||
let targetBackgroundFrame = self.calculateBackgroundFrame(containerSize: size, insets: backgroundInsets, anchorRect: animateOutToAnchorRect, contentSize: CGSize(width: visibleContentWidth, height: contentHeight)).0
|
let targetBackgroundFrame = self.calculateBackgroundFrame(containerSize: size, insets: backgroundInsets, anchorRect: animateOutToAnchorRect, contentSize: CGSize(width: visibleContentWidth, height: contentHeight)).0
|
||||||
|
|
||||||
let offset = CGPoint(x: -(targetBackgroundFrame.minX - backgroundFrame.minX), y: -(targetBackgroundFrame.minY - backgroundFrame.minY))
|
let offset = CGPoint(x: -(targetBackgroundFrame.minX - visualBackgroundFrame.minX), y: -(targetBackgroundFrame.minY - visualBackgroundFrame.minY))
|
||||||
self.position = CGPoint(x: self.position.x - offset.x, y: self.position.y - offset.y)
|
self.position = CGPoint(x: self.position.x - offset.x, y: self.position.y - offset.y)
|
||||||
self.layer.animatePosition(from: offset, to: CGPoint(), duration: 0.2, removeOnCompletion: true, additive: true)
|
self.layer.animatePosition(from: offset, to: CGPoint(), duration: 0.2, removeOnCompletion: true, additive: true)
|
||||||
}
|
}
|
||||||
@ -353,20 +364,29 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//let mainCircleDuration: Double = 0.5
|
//let mainCircleDuration: Double = 0.5
|
||||||
let mainCircleDelay: Double = 0.1
|
let mainCircleDelay: Double = 0.01
|
||||||
|
|
||||||
self.backgroundNode.animateIn()
|
self.backgroundNode.animateIn()
|
||||||
|
|
||||||
self.didAnimateIn = true
|
self.didAnimateIn = true
|
||||||
|
|
||||||
for i in 0 ..< self.items.count {
|
if !self.context.sharedContext.currentPresentationData.with({ $0 }).reduceMotion {
|
||||||
guard let itemNode = self.visibleItemNodes[i] else {
|
for i in 0 ..< self.items.count {
|
||||||
continue
|
guard let itemNode = self.visibleItemNodes[i] else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let itemDelay = mainCircleDelay + Double(i) * 0.06
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + itemDelay, execute: { [weak itemNode] in
|
||||||
|
itemNode?.appear(animated: true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i in 0 ..< self.items.count {
|
||||||
|
guard let itemNode = self.visibleItemNodes[i] else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
itemNode.appear(animated: false)
|
||||||
}
|
}
|
||||||
let itemDelay = mainCircleDelay + 0.1 + Double(i) * 0.035
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + itemDelay, execute: { [weak itemNode] in
|
|
||||||
itemNode?.animateIn()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,6 +406,15 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func animateFromItemNodeToReaction(itemNode: ReactionNode, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
|
private func animateFromItemNodeToReaction(itemNode: ReactionNode, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
|
||||||
|
if "".isEmpty {
|
||||||
|
if hideNode {
|
||||||
|
targetView.alpha = 1.0
|
||||||
|
targetView.isHidden = false
|
||||||
|
}
|
||||||
|
completion()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
guard let targetSnapshotView = targetView.snapshotContentTree(unhide: true) else {
|
guard let targetSnapshotView = targetView.snapshotContentTree(unhide: true) else {
|
||||||
completion()
|
completion()
|
||||||
return
|
return
|
||||||
@ -419,6 +448,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
targetSnapshotView?.isHidden = true
|
targetSnapshotView?.isHidden = true
|
||||||
|
|
||||||
if hideNode {
|
if hideNode {
|
||||||
|
targetView.alpha = 1.0
|
||||||
targetView.isHidden = false
|
targetView.isHidden = false
|
||||||
targetSnapshotView?.isHidden = true
|
targetSnapshotView?.isHidden = true
|
||||||
targetScaleCompleted = true
|
targetScaleCompleted = true
|
||||||
@ -441,7 +471,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
|
public func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, completion: @escaping () -> Void) {
|
||||||
for (_, itemNode) in self.visibleItemNodes {
|
for (_, itemNode) in self.visibleItemNodes {
|
||||||
if itemNode.item.reaction.rawValue != value {
|
if itemNode.item.reaction.rawValue != value {
|
||||||
continue
|
continue
|
||||||
@ -450,53 +480,48 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.animationTargetView = targetView
|
self.animationTargetView = targetView
|
||||||
self.animationHideNode = hideNode
|
self.animationHideNode = hideNode
|
||||||
|
|
||||||
/*let standaloneReactionAnimation = StandaloneReactionAnimation()
|
|
||||||
self.standaloneReactionAnimation = standaloneReactionAnimation
|
|
||||||
standaloneReactionAnimation.frame = self.bounds
|
|
||||||
self.addSubnode(standaloneReactionAnimation)
|
|
||||||
standaloneReactionAnimation.animateReactionSelection(context: itemNode.context, theme: self.theme, reaction: itemNode.item, targetView: targetView, currentItemNode: itemNode, hideNode: hideNode, completion: completion)
|
|
||||||
|
|
||||||
return*/
|
|
||||||
|
|
||||||
if hideNode {
|
if hideNode {
|
||||||
targetView.isHidden = true
|
if let animateTargetContainer = animateTargetContainer {
|
||||||
|
animateTargetContainer.isHidden = true
|
||||||
|
targetView.isHidden = true
|
||||||
|
} else {
|
||||||
|
targetView.alpha = 0.0
|
||||||
|
targetView.layer.animateAlpha(from: targetView.alpha, to: 0.0, duration: 0.2, completion: { [weak targetView] completed in
|
||||||
|
if completed {
|
||||||
|
targetView?.isHidden = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let itemSize: CGFloat = 40.0
|
|
||||||
|
|
||||||
itemNode.isExtracted = true
|
itemNode.isExtracted = true
|
||||||
let selfSourceRect = itemNode.view.convert(itemNode.view.bounds, to: self.view)
|
let selfSourceRect = itemNode.view.convert(itemNode.view.bounds, to: self.view)
|
||||||
let selfTargetRect = self.view.convert(targetView.bounds, from: targetView)
|
let selfTargetRect = self.view.convert(targetView.bounds, from: targetView)
|
||||||
|
|
||||||
let expandedScale: CGFloat = 4.0
|
let expandedSize: CGSize
|
||||||
let expandedSize = CGSize(width: floor(itemSize * expandedScale), height: floor(itemSize * expandedScale))
|
if targetView.bounds.width < 20.0 {
|
||||||
|
expandedSize = CGSize(width: 21.0, height: 21.0)
|
||||||
|
} else {
|
||||||
|
expandedSize = CGSize(width: 32.0, height: 32.0)
|
||||||
|
}
|
||||||
|
|
||||||
var expandedFrame = CGRect(origin: CGPoint(x: floor(selfTargetRect.midX - expandedSize.width / 2.0), y: floor(selfTargetRect.midY - expandedSize.height / 2.0)), size: expandedSize)
|
var expandedFrame = CGRect(origin: CGPoint(x: floor(selfTargetRect.midX - expandedSize.width / 2.0), y: floor(selfTargetRect.midY - expandedSize.height / 2.0)), size: expandedSize)
|
||||||
if expandedFrame.minX < -floor(expandedFrame.width * 0.05) {
|
expandedFrame.origin.y += UIScreenPixel
|
||||||
expandedFrame.origin.x = -floor(expandedFrame.width * 0.05)
|
|
||||||
}
|
let effectFrame = expandedFrame.insetBy(dx: -60.0, dy: -60.0)
|
||||||
|
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .linear)
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .linear)
|
||||||
|
|
||||||
self.addSubnode(itemNode)
|
self.addSubnode(itemNode)
|
||||||
//itemNode.position = selfSourceRect.center
|
|
||||||
itemNode.position = expandedFrame.center
|
itemNode.position = expandedFrame.center
|
||||||
transition.updateBounds(node: itemNode, bounds: CGRect(origin: CGPoint(), size: expandedFrame.size))
|
transition.updateBounds(node: itemNode, bounds: CGRect(origin: CGPoint(), size: expandedFrame.size))
|
||||||
itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, isPreviewing: false, transition: transition)
|
itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, isPreviewing: false, transition: transition)
|
||||||
|
|
||||||
transition.animatePositionWithKeyframes(node: itemNode, keyframes: generateParabollicMotionKeyframes(from: selfSourceRect.center, to: expandedFrame.center, elevation: 30.0))
|
|
||||||
|
|
||||||
let additionalAnimationNode = AnimatedStickerNode()
|
let additionalAnimationNode = AnimatedStickerNode()
|
||||||
let incomingMessage: Bool = expandedFrame.midX < self.bounds.width / 2.0
|
|
||||||
let animationFrame = expandedFrame.insetBy(dx: -expandedFrame.width * 0.5, dy: -expandedFrame.height * 0.5)
|
|
||||||
.offsetBy(dx: incomingMessage ? (expandedFrame.width - 50.0) : (-expandedFrame.width + 50.0), dy: 0.0)
|
|
||||||
|
|
||||||
additionalAnimationNode.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: itemNode.item.applicationAnimation.resource), width: Int(animationFrame.width * 2.0), height: Int(animationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: itemNode.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(itemNode.item.applicationAnimation.resource.id)))
|
additionalAnimationNode.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: itemNode.item.applicationAnimation.resource), width: Int(effectFrame.width * 2.0), height: Int(effectFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: itemNode.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(itemNode.item.applicationAnimation.resource.id)))
|
||||||
additionalAnimationNode.frame = animationFrame
|
additionalAnimationNode.frame = effectFrame
|
||||||
if incomingMessage {
|
additionalAnimationNode.updateLayout(size: effectFrame.size)
|
||||||
additionalAnimationNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
|
||||||
}
|
|
||||||
additionalAnimationNode.updateLayout(size: animationFrame.size)
|
|
||||||
self.addSubnode(additionalAnimationNode)
|
self.addSubnode(additionalAnimationNode)
|
||||||
|
|
||||||
var mainAnimationCompleted = false
|
var mainAnimationCompleted = false
|
||||||
@ -512,8 +537,15 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
intermediateCompletion()
|
intermediateCompletion()
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1 * UIView.animationDurationFactor(), execute: {
|
transition.animatePositionWithKeyframes(node: itemNode, keyframes: generateParabollicMotionKeyframes(from: selfSourceRect.center, to: expandedFrame.center, elevation: 30.0))
|
||||||
|
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.15 * UIView.animationDurationFactor(), execute: {
|
||||||
additionalAnimationNode.visibility = true
|
additionalAnimationNode.visibility = true
|
||||||
|
if let animateTargetContainer = animateTargetContainer {
|
||||||
|
animateTargetContainer.isHidden = false
|
||||||
|
animateTargetContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
animateTargetContainer.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + min(5.0, 2.0 * UIView.animationDurationFactor()), execute: {
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + min(5.0, 2.0 * UIView.animationDurationFactor()), execute: {
|
||||||
@ -536,12 +568,47 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func longPressGesture(_ recognizer: UILongPressGestureRecognizer) {
|
||||||
|
switch recognizer.state {
|
||||||
|
case .began:
|
||||||
|
let point = recognizer.location(in: self.view)
|
||||||
|
if let itemNode = self.reactionItemNode(at: point) {
|
||||||
|
self.highlightedReaction = itemNode.item.reaction
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
self.continuousHaptic = try? ContinuousHaptic(duration: 2.5)
|
||||||
|
}
|
||||||
|
//itemNode.updateIsLongPressing(isLongPressing: true)
|
||||||
|
|
||||||
|
if self.hapticFeedback == nil {
|
||||||
|
self.hapticFeedback = HapticFeedback()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (size, insets, anchorRect) = self.validLayout {
|
||||||
|
self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .animated(duration: 2.5, curve: .linear), animateInFromAnchorRect: nil, animateOutToAnchorRect: nil, animateReactionHighlight: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .ended, .cancelled:
|
||||||
|
self.continuousHaptic = nil
|
||||||
|
if let itemNode = self.currentLongPressItemNode {
|
||||||
|
self.currentLongPressItemNode = nil
|
||||||
|
self.reactionSelected?(itemNode.item)
|
||||||
|
itemNode.updateIsLongPressing(isLongPressing: false)
|
||||||
|
}
|
||||||
|
self.highlightGestureFinished(performAction: true)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
if case .ended = recognizer.state {
|
switch recognizer.state {
|
||||||
|
case .ended:
|
||||||
let point = recognizer.location(in: self.view)
|
let point = recognizer.location(in: self.view)
|
||||||
if let reaction = self.reaction(at: point) {
|
if let reaction = self.reaction(at: point) {
|
||||||
self.reactionSelected?(reaction)
|
self.reactionSelected?(reaction)
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -584,7 +651,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
var closestItem: (index: Int, distance: CGFloat)?
|
var closestItem: (index: Int, distance: CGFloat)?
|
||||||
|
|
||||||
for (index, itemNode) in self.visibleItemNodes {
|
for (index, itemNode) in self.visibleItemNodes {
|
||||||
let intersectionItemFrame = CGRect(origin: CGPoint(x: itemNode.frame.midX - itemSize / 2.0, y: itemNode.frame.midY - 1.0), size: CGSize(width: itemSize, height: 2.0))
|
let intersectionItemFrame = CGRect(origin: CGPoint(x: itemNode.position.x - itemSize / 2.0, y: itemNode.position.y - 1.0), size: CGSize(width: itemSize, height: 2.0))
|
||||||
|
|
||||||
if !self.scrollNode.bounds.contains(intersectionItemFrame) {
|
if !self.scrollNode.bounds.contains(intersectionItemFrame) {
|
||||||
continue
|
continue
|
||||||
@ -605,7 +672,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
public func reaction(at point: CGPoint) -> ReactionContextItem? {
|
private func reactionItemNode(at point: CGPoint) -> ReactionNode? {
|
||||||
for i in 0 ..< 2 {
|
for i in 0 ..< 2 {
|
||||||
let touchInset: CGFloat = i == 0 ? 0.0 : 8.0
|
let touchInset: CGFloat = i == 0 ? 0.0 : 8.0
|
||||||
for (_, itemNode) in self.visibleItemNodes {
|
for (_, itemNode) in self.visibleItemNodes {
|
||||||
@ -614,13 +681,17 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
let itemPoint = self.view.convert(point, to: itemNode.view)
|
let itemPoint = self.view.convert(point, to: itemNode.view)
|
||||||
if itemNode.bounds.insetBy(dx: -touchInset, dy: -touchInset).contains(itemPoint) {
|
if itemNode.bounds.insetBy(dx: -touchInset, dy: -touchInset).contains(itemPoint) {
|
||||||
return itemNode.item
|
return itemNode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func reaction(at point: CGPoint) -> ReactionContextItem? {
|
||||||
|
return self.reactionItemNode(at: point)?.item
|
||||||
|
}
|
||||||
|
|
||||||
public func performReactionSelection(reaction: ReactionContextItem.Reaction) {
|
public func performReactionSelection(reaction: ReactionContextItem.Reaction) {
|
||||||
for (_, itemNode) in self.visibleItemNodes {
|
for (_, itemNode) in self.visibleItemNodes {
|
||||||
if itemNode.item.reaction == reaction {
|
if itemNode.item.reaction == reaction {
|
||||||
@ -634,6 +705,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.standaloneReactionAnimation?.cancel()
|
self.standaloneReactionAnimation?.cancel()
|
||||||
|
|
||||||
if let animationTargetView = self.animationTargetView, self.animationHideNode {
|
if let animationTargetView = self.animationTargetView, self.animationHideNode {
|
||||||
|
animationTargetView.alpha = 1.0
|
||||||
animationTargetView.isHidden = false
|
animationTargetView.isHidden = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -695,22 +767,25 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
itemNode.isExtracted = true
|
itemNode.isExtracted = true
|
||||||
let sourceItemSize: CGFloat = 40.0
|
|
||||||
let selfTargetRect = self.view.convert(targetView.bounds, from: targetView)
|
let selfTargetRect = self.view.convert(targetView.bounds, from: targetView)
|
||||||
|
|
||||||
let expandedScale: CGFloat = 3.0
|
let expandedSize: CGSize
|
||||||
let expandedSize = CGSize(width: floor(sourceItemSize * expandedScale), height: floor(sourceItemSize * expandedScale))
|
if targetView.bounds.width < 20.0 {
|
||||||
|
expandedSize = CGSize(width: 21.0, height: 21.0)
|
||||||
|
} else {
|
||||||
|
expandedSize = CGSize(width: 32.0, height: 32.0)
|
||||||
|
}
|
||||||
|
|
||||||
var expandedFrame = CGRect(origin: CGPoint(x: floor(selfTargetRect.midX - expandedSize.width / 2.0), y: floor(selfTargetRect.midY - expandedSize.height / 2.0)), size: expandedSize)
|
var expandedFrame = CGRect(origin: CGPoint(x: floor(selfTargetRect.midX - expandedSize.width / 2.0), y: floor(selfTargetRect.midY - expandedSize.height / 2.0)), size: expandedSize)
|
||||||
if expandedFrame.minX < -floor(expandedFrame.width * 0.05) {
|
expandedFrame.origin.y += UIScreenPixel
|
||||||
expandedFrame.origin.x = -floor(expandedFrame.width * 0.05)
|
|
||||||
}
|
let effectFrame = expandedFrame.insetBy(dx: -60.0, dy: -60.0)
|
||||||
|
|
||||||
sourceSnapshotView.frame = selfTargetRect
|
sourceSnapshotView.frame = selfTargetRect
|
||||||
self.view.addSubview(sourceSnapshotView)
|
self.view.addSubview(sourceSnapshotView)
|
||||||
sourceSnapshotView.alpha = 0.0
|
sourceSnapshotView.alpha = 0.0
|
||||||
sourceSnapshotView.layer.animateSpring(from: 1.0 as NSNumber, to: (expandedFrame.width / selfTargetRect.width) as NSNumber, keyPath: "transform.scale", duration: 0.4)
|
sourceSnapshotView.layer.animateSpring(from: 1.0 as NSNumber, to: (expandedFrame.width / selfTargetRect.width) as NSNumber, keyPath: "transform.scale", duration: 0.4)
|
||||||
sourceSnapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.08, completion: { [weak sourceSnapshotView] _ in
|
sourceSnapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.01, completion: { [weak sourceSnapshotView] _ in
|
||||||
sourceSnapshotView?.removeFromSuperview()
|
sourceSnapshotView?.removeFromSuperview()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -719,23 +794,18 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, isPreviewing: false, transition: .immediate)
|
itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, isPreviewing: false, transition: .immediate)
|
||||||
|
|
||||||
itemNode.layer.animateSpring(from: (selfTargetRect.width / expandedFrame.width) as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
|
itemNode.layer.animateSpring(from: (selfTargetRect.width / expandedFrame.width) as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
|
||||||
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.04)
|
|
||||||
|
if targetView.bounds.width < 20.0 {
|
||||||
|
itemNode.layer.animateScale(from: 0.01, to: 1.0, duration: 0.15)
|
||||||
|
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
|
}
|
||||||
|
|
||||||
let additionalAnimationNode = AnimatedStickerNode()
|
let additionalAnimationNode = AnimatedStickerNode()
|
||||||
let incomingMessage: Bool = expandedFrame.midX < self.bounds.width / 2.0
|
additionalAnimationNode.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: itemNode.item.applicationAnimation.resource), width: Int(effectFrame.width * 2.0), height: Int(effectFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: itemNode.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(itemNode.item.applicationAnimation.resource.id)))
|
||||||
let animationFrame = expandedFrame.insetBy(dx: -expandedFrame.width * 0.5, dy: -expandedFrame.height * 0.5)
|
additionalAnimationNode.frame = effectFrame
|
||||||
.offsetBy(dx: incomingMessage ? (expandedFrame.width - 50.0) : (-expandedFrame.width + 50.0), dy: 0.0)
|
additionalAnimationNode.updateLayout(size: effectFrame.size)
|
||||||
|
|
||||||
additionalAnimationNode.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: itemNode.item.applicationAnimation.resource), width: Int(animationFrame.width * 2.0), height: Int(animationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: itemNode.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(itemNode.item.applicationAnimation.resource.id)))
|
|
||||||
additionalAnimationNode.frame = animationFrame
|
|
||||||
if incomingMessage {
|
|
||||||
additionalAnimationNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
|
||||||
}
|
|
||||||
additionalAnimationNode.updateLayout(size: animationFrame.size)
|
|
||||||
self.addSubnode(additionalAnimationNode)
|
self.addSubnode(additionalAnimationNode)
|
||||||
|
|
||||||
additionalAnimationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18)
|
|
||||||
|
|
||||||
var mainAnimationCompleted = false
|
var mainAnimationCompleted = false
|
||||||
var additionalAnimationCompleted = false
|
var additionalAnimationCompleted = false
|
||||||
let intermediateCompletion: () -> Void = {
|
let intermediateCompletion: () -> Void = {
|
||||||
@ -779,6 +849,15 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func animateFromItemNodeToReaction(itemNode: ReactionNode, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
|
private func animateFromItemNodeToReaction(itemNode: ReactionNode, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
|
||||||
|
if "".isEmpty {
|
||||||
|
if hideNode {
|
||||||
|
targetView.alpha = 1.0
|
||||||
|
targetView.isHidden = false
|
||||||
|
}
|
||||||
|
completion()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
guard let targetSnapshotView = targetView.snapshotContentTree(unhide: true) else {
|
guard let targetSnapshotView = targetView.snapshotContentTree(unhide: true) else {
|
||||||
completion()
|
completion()
|
||||||
return
|
return
|
||||||
@ -812,6 +891,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
targetSnapshotView?.isHidden = true
|
targetSnapshotView?.isHidden = true
|
||||||
|
|
||||||
if hideNode {
|
if hideNode {
|
||||||
|
targetView.alpha = 1.0
|
||||||
targetView.isHidden = false
|
targetView.isHidden = false
|
||||||
targetSnapshotView?.isHidden = true
|
targetSnapshotView?.isHidden = true
|
||||||
targetScaleCompleted = true
|
targetScaleCompleted = true
|
||||||
@ -834,6 +914,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
self.isCancelled = true
|
self.isCancelled = true
|
||||||
|
|
||||||
if let targetView = self.targetView, self.hideNode {
|
if let targetView = self.targetView, self.hideNode {
|
||||||
|
targetView.alpha = 1.0
|
||||||
targetView.isHidden = false
|
targetView.isHidden = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,6 +55,9 @@ final class ReactionNode: ASDisplayNode {
|
|||||||
|
|
||||||
var didSetupStillAnimation: Bool = false
|
var didSetupStillAnimation: Bool = false
|
||||||
|
|
||||||
|
private var isLongPressing: Bool = false
|
||||||
|
private var longPressAnimator: DisplayLinkAnimator?
|
||||||
|
|
||||||
init(context: AccountContext, theme: PresentationTheme, item: ReactionContextItem) {
|
init(context: AccountContext, theme: PresentationTheme, item: ReactionContextItem) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.item = item
|
self.item = item
|
||||||
@ -94,8 +97,12 @@ final class ReactionNode: ASDisplayNode {
|
|||||||
self.fetchFullAnimationDisposable?.dispose()
|
self.fetchFullAnimationDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateIn() {
|
func appear(animated: Bool) {
|
||||||
self.animateInAnimationNode?.visibility = true
|
if animated {
|
||||||
|
self.animateInAnimationNode?.visibility = true
|
||||||
|
} else {
|
||||||
|
self.animateInAnimationNode?.completed(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateLayout(size: CGSize, isExpanded: Bool, isPreviewing: Bool, transition: ContainedViewLayoutTransition) {
|
func updateLayout(size: CGSize, isExpanded: Bool, isPreviewing: Bool, transition: ContainedViewLayoutTransition) {
|
||||||
@ -113,14 +120,18 @@ final class ReactionNode: ASDisplayNode {
|
|||||||
var animationFrame = CGRect(origin: CGPoint(x: floor((intrinsicSize.width - animationDisplaySize.width) / 2.0), y: floor((intrinsicSize.height - animationDisplaySize.height) / 2.0)), size: animationDisplaySize)
|
var animationFrame = CGRect(origin: CGPoint(x: floor((intrinsicSize.width - animationDisplaySize.width) / 2.0), y: floor((intrinsicSize.height - animationDisplaySize.height) / 2.0)), size: animationDisplaySize)
|
||||||
animationFrame.origin.y = floor(animationFrame.origin.y + animationFrame.height * offsetFactor)
|
animationFrame.origin.y = floor(animationFrame.origin.y + animationFrame.height * offsetFactor)
|
||||||
|
|
||||||
|
let expandedInset: CGFloat = floor(size.width / 32.0 * 60.0)
|
||||||
|
let expandedAnimationFrame = animationFrame.insetBy(dx: -expandedInset, dy: -expandedInset)
|
||||||
|
|
||||||
if isExpanded, self.animationNode == nil {
|
if isExpanded, self.animationNode == nil {
|
||||||
let animationNode = AnimatedStickerNode()
|
let animationNode = AnimatedStickerNode()
|
||||||
|
animationNode.automaticallyLoadFirstFrame = true
|
||||||
self.animationNode = animationNode
|
self.animationNode = animationNode
|
||||||
self.addSubnode(animationNode)
|
self.addSubnode(animationNode)
|
||||||
|
|
||||||
animationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.listAnimation.resource), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.listAnimation.resource.id)))
|
animationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.listAnimation.resource), width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.listAnimation.resource.id)))
|
||||||
animationNode.frame = animationFrame
|
animationNode.frame = expandedAnimationFrame
|
||||||
animationNode.updateLayout(size: animationFrame.size)
|
animationNode.updateLayout(size: expandedAnimationFrame.size)
|
||||||
|
|
||||||
if transition.isAnimated {
|
if transition.isAnimated {
|
||||||
if let stillAnimationNode = self.stillAnimationNode, !stillAnimationNode.frame.isEmpty {
|
if let stillAnimationNode = self.stillAnimationNode, !stillAnimationNode.frame.isEmpty {
|
||||||
@ -172,7 +183,9 @@ final class ReactionNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
self.staticAnimationNode.isHidden = true
|
self.staticAnimationNode.isHidden = true
|
||||||
}
|
}
|
||||||
animationNode.visibility = true
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.17, execute: {
|
||||||
|
animationNode.visibility = true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.validSize != size {
|
if self.validSize != size {
|
||||||
@ -262,4 +275,32 @@ final class ReactionNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateIsLongPressing(isLongPressing: Bool) {
|
||||||
|
if self.isLongPressing == isLongPressing {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.isLongPressing = isLongPressing
|
||||||
|
|
||||||
|
if isLongPressing {
|
||||||
|
if self.longPressAnimator == nil {
|
||||||
|
let longPressAnimator = DisplayLinkAnimator(duration: 2.0, from: 1.0, to: 2.0, update: { [weak self] value in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let transition: ContainedViewLayoutTransition = .immediate
|
||||||
|
transition.updateSublayerTransformScale(node: strongSelf, scale: value)
|
||||||
|
}, completion: {
|
||||||
|
})
|
||||||
|
self.longPressAnimator = longPressAnimator
|
||||||
|
}
|
||||||
|
} else if let longPressAnimator = self.longPressAnimator {
|
||||||
|
self.longPressAnimator = nil
|
||||||
|
|
||||||
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
|
||||||
|
transition.updateSublayerTransformScale(node: self, scale: 1.0)
|
||||||
|
|
||||||
|
longPressAnimator.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,87 +1 @@
|
|||||||
/*import Foundation
|
|
||||||
import AsyncDisplayKit
|
|
||||||
import Display
|
|
||||||
import Postbox
|
|
||||||
import TelegramCore
|
|
||||||
import TelegramPresentationData
|
|
||||||
|
|
||||||
public final class ReactionSelectionParentNode: ASDisplayNode {
|
|
||||||
private let account: Account
|
|
||||||
private let theme: PresentationTheme
|
|
||||||
|
|
||||||
private var currentNode: ReactionSelectionNode?
|
|
||||||
private var currentLocation: (CGPoint, CGFloat, CGPoint)?
|
|
||||||
|
|
||||||
private var validLayout: (size: CGSize, insets: UIEdgeInsets)?
|
|
||||||
|
|
||||||
public init(account: Account, theme: PresentationTheme) {
|
|
||||||
self.account = account
|
|
||||||
self.theme = theme
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
func displayReactions(_ reactions: [ReactionGestureItem], at point: CGPoint, touchPoint: CGPoint) {
|
|
||||||
if let currentNode = self.currentNode {
|
|
||||||
currentNode.removeFromSupernode()
|
|
||||||
self.currentNode = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let reactionNode = ReactionSelectionNode(account: self.account, theme: self.theme, reactions: reactions)
|
|
||||||
self.addSubnode(reactionNode)
|
|
||||||
self.currentNode = reactionNode
|
|
||||||
self.currentLocation = (point, point.x, touchPoint)
|
|
||||||
|
|
||||||
if let (size, insets) = self.validLayout {
|
|
||||||
self.update(size: size, insets: insets, isInitial: true)
|
|
||||||
|
|
||||||
reactionNode.animateIn()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func selectedReaction() -> ReactionGestureItem? {
|
|
||||||
if let currentNode = self.currentNode {
|
|
||||||
return currentNode.selectedReaction()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func dismissReactions(into targetNode: ASDisplayNode?, hideTarget: Bool) {
|
|
||||||
if let currentNode = self.currentNode {
|
|
||||||
currentNode.animateOut(into: targetNode, hideTarget: hideTarget, completion: { [weak currentNode] in
|
|
||||||
currentNode?.removeFromSupernode()
|
|
||||||
})
|
|
||||||
self.currentNode = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateReactionsAnchor(point: CGPoint, touchPoint: CGPoint) {
|
|
||||||
if let (currentPoint, _, _) = self.currentLocation {
|
|
||||||
self.currentLocation = (currentPoint, point.x, touchPoint)
|
|
||||||
|
|
||||||
if let (size, insets) = self.validLayout {
|
|
||||||
self.update(size: size, insets: insets, isInitial: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) {
|
|
||||||
self.validLayout = (size, insets)
|
|
||||||
|
|
||||||
self.update(size: size, insets: insets, isInitial: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func update(size: CGSize, insets: UIEdgeInsets, isInitial: Bool) {
|
|
||||||
if let currentNode = self.currentNode, let (point, offset, touchPoint) = self.currentLocation {
|
|
||||||
currentNode.updateLayout(constrainedSize: size, startingPoint: CGPoint(x: size.width - 32.0, y: point.y), offsetFromStart: offset, isInitial: isInitial, touchPoint: touchPoint)
|
|
||||||
currentNode.frame = CGRect(origin: CGPoint(), size: size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|||||||
@ -92,6 +92,7 @@ swift_library(
|
|||||||
"//submodules/DebugSettingsUI:DebugSettingsUI",
|
"//submodules/DebugSettingsUI:DebugSettingsUI",
|
||||||
"//submodules/WallpaperBackgroundNode:WallpaperBackgroundNode",
|
"//submodules/WallpaperBackgroundNode:WallpaperBackgroundNode",
|
||||||
"//submodules/WebPBinding:WebPBinding",
|
"//submodules/WebPBinding:WebPBinding",
|
||||||
|
"//submodules/Components/ReactionImageComponent:ReactionImageComponent",
|
||||||
"//submodules/Translate:Translate",
|
"//submodules/Translate:Translate",
|
||||||
"//submodules/QrCodeUI:QrCodeUI",
|
"//submodules/QrCodeUI:QrCodeUI",
|
||||||
],
|
],
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import ItemListUI
|
|||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import AccountContext
|
import AccountContext
|
||||||
import WebPBinding
|
import WebPBinding
|
||||||
|
import ReactionImageComponent
|
||||||
|
|
||||||
private final class QuickReactionSetupControllerArguments {
|
private final class QuickReactionSetupControllerArguments {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import ItemListPeerActionItem
|
|||||||
import UndoUI
|
import UndoUI
|
||||||
import ShareController
|
import ShareController
|
||||||
import WebPBinding
|
import WebPBinding
|
||||||
|
import ReactionImageComponent
|
||||||
|
|
||||||
private final class InstalledStickerPacksControllerArguments {
|
private final class InstalledStickerPacksControllerArguments {
|
||||||
let account: Account
|
let account: Account
|
||||||
|
|||||||
@ -792,7 +792,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[594408994] = { return Api.EmojiKeyword.parse_emojiKeywordDeleted($0) }
|
dict[594408994] = { return Api.EmojiKeyword.parse_emojiKeywordDeleted($0) }
|
||||||
dict[-290921362] = { return Api.upload.CdnFile.parse_cdnFileReuploadNeeded($0) }
|
dict[-290921362] = { return Api.upload.CdnFile.parse_cdnFileReuploadNeeded($0) }
|
||||||
dict[-1449145777] = { return Api.upload.CdnFile.parse_cdnFile($0) }
|
dict[-1449145777] = { return Api.upload.CdnFile.parse_cdnFile($0) }
|
||||||
dict[35486795] = { return Api.AvailableReaction.parse_availableReaction($0) }
|
dict[-1065882623] = { return Api.AvailableReaction.parse_availableReaction($0) }
|
||||||
dict[415997816] = { return Api.help.InviteText.parse_inviteText($0) }
|
dict[415997816] = { return Api.help.InviteText.parse_inviteText($0) }
|
||||||
dict[-1826077446] = { return Api.MessageUserReaction.parse_messageUserReaction($0) }
|
dict[-1826077446] = { return Api.MessageUserReaction.parse_messageUserReaction($0) }
|
||||||
dict[1984755728] = { return Api.BotInlineMessage.parse_botInlineMessageMediaAuto($0) }
|
dict[1984755728] = { return Api.BotInlineMessage.parse_botInlineMessageMediaAuto($0) }
|
||||||
|
|||||||
@ -20236,13 +20236,13 @@ public extension Api {
|
|||||||
|
|
||||||
}
|
}
|
||||||
public enum AvailableReaction: TypeConstructorDescription {
|
public enum AvailableReaction: TypeConstructorDescription {
|
||||||
case availableReaction(flags: Int32, reaction: String, title: String, staticIcon: Api.Document, appearAnimation: Api.Document, selectAnimation: Api.Document, activateAnimation: Api.Document, effectAnimation: Api.Document)
|
case availableReaction(flags: Int32, reaction: String, title: String, staticIcon: Api.Document, appearAnimation: Api.Document, selectAnimation: Api.Document, activateAnimation: Api.Document, effectAnimation: Api.Document, aroundAnimation: Api.Document?, centerIcon: Api.Document?)
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
case .availableReaction(let flags, let reaction, let title, let staticIcon, let appearAnimation, let selectAnimation, let activateAnimation, let effectAnimation):
|
case .availableReaction(let flags, let reaction, let title, let staticIcon, let appearAnimation, let selectAnimation, let activateAnimation, let effectAnimation, let aroundAnimation, let centerIcon):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(35486795)
|
buffer.appendInt32(-1065882623)
|
||||||
}
|
}
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeString(reaction, buffer: buffer, boxed: false)
|
serializeString(reaction, buffer: buffer, boxed: false)
|
||||||
@ -20252,14 +20252,16 @@ public extension Api {
|
|||||||
selectAnimation.serialize(buffer, true)
|
selectAnimation.serialize(buffer, true)
|
||||||
activateAnimation.serialize(buffer, true)
|
activateAnimation.serialize(buffer, true)
|
||||||
effectAnimation.serialize(buffer, true)
|
effectAnimation.serialize(buffer, true)
|
||||||
|
if Int(flags) & Int(1 << 1) != 0 {aroundAnimation!.serialize(buffer, true)}
|
||||||
|
if Int(flags) & Int(1 << 1) != 0 {centerIcon!.serialize(buffer, true)}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
switch self {
|
switch self {
|
||||||
case .availableReaction(let flags, let reaction, let title, let staticIcon, let appearAnimation, let selectAnimation, let activateAnimation, let effectAnimation):
|
case .availableReaction(let flags, let reaction, let title, let staticIcon, let appearAnimation, let selectAnimation, let activateAnimation, let effectAnimation, let aroundAnimation, let centerIcon):
|
||||||
return ("availableReaction", [("flags", flags), ("reaction", reaction), ("title", title), ("staticIcon", staticIcon), ("appearAnimation", appearAnimation), ("selectAnimation", selectAnimation), ("activateAnimation", activateAnimation), ("effectAnimation", effectAnimation)])
|
return ("availableReaction", [("flags", flags), ("reaction", reaction), ("title", title), ("staticIcon", staticIcon), ("appearAnimation", appearAnimation), ("selectAnimation", selectAnimation), ("activateAnimation", activateAnimation), ("effectAnimation", effectAnimation), ("aroundAnimation", aroundAnimation), ("centerIcon", centerIcon)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20290,6 +20292,14 @@ public extension Api {
|
|||||||
if let signature = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
_8 = Api.parse(reader, signature: signature) as? Api.Document
|
_8 = Api.parse(reader, signature: signature) as? Api.Document
|
||||||
}
|
}
|
||||||
|
var _9: Api.Document?
|
||||||
|
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
|
||||||
|
_9 = Api.parse(reader, signature: signature) as? Api.Document
|
||||||
|
} }
|
||||||
|
var _10: Api.Document?
|
||||||
|
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
|
||||||
|
_10 = Api.parse(reader, signature: signature) as? Api.Document
|
||||||
|
} }
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = _2 != nil
|
let _c2 = _2 != nil
|
||||||
let _c3 = _3 != nil
|
let _c3 = _3 != nil
|
||||||
@ -20298,8 +20308,10 @@ public extension Api {
|
|||||||
let _c6 = _6 != nil
|
let _c6 = _6 != nil
|
||||||
let _c7 = _7 != nil
|
let _c7 = _7 != nil
|
||||||
let _c8 = _8 != nil
|
let _c8 = _8 != nil
|
||||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
|
let _c9 = (Int(_1!) & Int(1 << 1) == 0) || _9 != nil
|
||||||
return Api.AvailableReaction.availableReaction(flags: _1!, reaction: _2!, title: _3!, staticIcon: _4!, appearAnimation: _5!, selectAnimation: _6!, activateAnimation: _7!, effectAnimation: _8!)
|
let _c10 = (Int(_1!) & Int(1 << 1) == 0) || _10 != nil
|
||||||
|
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 {
|
||||||
|
return Api.AvailableReaction.availableReaction(flags: _1!, reaction: _2!, title: _3!, staticIcon: _4!, appearAnimation: _5!, selectAnimation: _6!, activateAnimation: _7!, effectAnimation: _8!, aroundAnimation: _9, centerIcon: _10)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -14,6 +14,8 @@ public final class AvailableReactions: Equatable, Codable {
|
|||||||
case selectAnimation
|
case selectAnimation
|
||||||
case activateAnimation
|
case activateAnimation
|
||||||
case effectAnimation
|
case effectAnimation
|
||||||
|
case aroundAnimation
|
||||||
|
case centerAnimation
|
||||||
}
|
}
|
||||||
|
|
||||||
public let isEnabled: Bool
|
public let isEnabled: Bool
|
||||||
@ -24,6 +26,8 @@ public final class AvailableReactions: Equatable, Codable {
|
|||||||
public let selectAnimation: TelegramMediaFile
|
public let selectAnimation: TelegramMediaFile
|
||||||
public let activateAnimation: TelegramMediaFile
|
public let activateAnimation: TelegramMediaFile
|
||||||
public let effectAnimation: TelegramMediaFile
|
public let effectAnimation: TelegramMediaFile
|
||||||
|
public let aroundAnimation: TelegramMediaFile?
|
||||||
|
public let centerAnimation: TelegramMediaFile?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
isEnabled: Bool,
|
isEnabled: Bool,
|
||||||
@ -33,7 +37,9 @@ public final class AvailableReactions: Equatable, Codable {
|
|||||||
appearAnimation: TelegramMediaFile,
|
appearAnimation: TelegramMediaFile,
|
||||||
selectAnimation: TelegramMediaFile,
|
selectAnimation: TelegramMediaFile,
|
||||||
activateAnimation: TelegramMediaFile,
|
activateAnimation: TelegramMediaFile,
|
||||||
effectAnimation: TelegramMediaFile
|
effectAnimation: TelegramMediaFile,
|
||||||
|
aroundAnimation: TelegramMediaFile?,
|
||||||
|
centerAnimation: TelegramMediaFile?
|
||||||
) {
|
) {
|
||||||
self.isEnabled = isEnabled
|
self.isEnabled = isEnabled
|
||||||
self.value = value
|
self.value = value
|
||||||
@ -43,6 +49,8 @@ public final class AvailableReactions: Equatable, Codable {
|
|||||||
self.selectAnimation = selectAnimation
|
self.selectAnimation = selectAnimation
|
||||||
self.activateAnimation = activateAnimation
|
self.activateAnimation = activateAnimation
|
||||||
self.effectAnimation = effectAnimation
|
self.effectAnimation = effectAnimation
|
||||||
|
self.aroundAnimation = aroundAnimation
|
||||||
|
self.centerAnimation = centerAnimation
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: Reaction, rhs: Reaction) -> Bool {
|
public static func ==(lhs: Reaction, rhs: Reaction) -> Bool {
|
||||||
@ -70,6 +78,12 @@ public final class AvailableReactions: Equatable, Codable {
|
|||||||
if lhs.effectAnimation != rhs.effectAnimation {
|
if lhs.effectAnimation != rhs.effectAnimation {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.aroundAnimation != rhs.aroundAnimation {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.centerAnimation != rhs.centerAnimation {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,6 +109,18 @@ public final class AvailableReactions: Equatable, Codable {
|
|||||||
|
|
||||||
let effectAnimationData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .effectAnimation)
|
let effectAnimationData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .effectAnimation)
|
||||||
self.effectAnimation = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: effectAnimationData.data)))
|
self.effectAnimation = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: effectAnimationData.data)))
|
||||||
|
|
||||||
|
if let aroundAnimationData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: .aroundAnimation) {
|
||||||
|
self.aroundAnimation = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: aroundAnimationData.data)))
|
||||||
|
} else {
|
||||||
|
self.aroundAnimation = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if let centerAnimationData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: .centerAnimation) {
|
||||||
|
self.centerAnimation = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: centerAnimationData.data)))
|
||||||
|
} else {
|
||||||
|
self.centerAnimation = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
@ -110,6 +136,12 @@ public final class AvailableReactions: Equatable, Codable {
|
|||||||
try container.encode(PostboxEncoder().encodeObjectToRawData(self.selectAnimation), forKey: .selectAnimation)
|
try container.encode(PostboxEncoder().encodeObjectToRawData(self.selectAnimation), forKey: .selectAnimation)
|
||||||
try container.encode(PostboxEncoder().encodeObjectToRawData(self.activateAnimation), forKey: .activateAnimation)
|
try container.encode(PostboxEncoder().encodeObjectToRawData(self.activateAnimation), forKey: .activateAnimation)
|
||||||
try container.encode(PostboxEncoder().encodeObjectToRawData(self.effectAnimation), forKey: .effectAnimation)
|
try container.encode(PostboxEncoder().encodeObjectToRawData(self.effectAnimation), forKey: .effectAnimation)
|
||||||
|
if let aroundAnimation = self.aroundAnimation {
|
||||||
|
try container.encode(PostboxEncoder().encodeObjectToRawData(aroundAnimation), forKey: .aroundAnimation)
|
||||||
|
}
|
||||||
|
if let centerAnimation = self.centerAnimation {
|
||||||
|
try container.encode(PostboxEncoder().encodeObjectToRawData(centerAnimation), forKey: .centerAnimation)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +189,7 @@ public final class AvailableReactions: Equatable, Codable {
|
|||||||
private extension AvailableReactions.Reaction {
|
private extension AvailableReactions.Reaction {
|
||||||
convenience init?(apiReaction: Api.AvailableReaction) {
|
convenience init?(apiReaction: Api.AvailableReaction) {
|
||||||
switch apiReaction {
|
switch apiReaction {
|
||||||
case let .availableReaction(flags, reaction, title, staticIcon, appearAnimation, selectAnimation, activateAnimation, effectAnimation):
|
case let .availableReaction(flags, reaction, title, staticIcon, appearAnimation, selectAnimation, activateAnimation, effectAnimation, aroundAnimation, centerIcon):
|
||||||
guard let staticIconFile = telegramMediaFileFromApiDocument(staticIcon) else {
|
guard let staticIconFile = telegramMediaFileFromApiDocument(staticIcon) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -173,6 +205,8 @@ private extension AvailableReactions.Reaction {
|
|||||||
guard let effectAnimationFile = telegramMediaFileFromApiDocument(effectAnimation) else {
|
guard let effectAnimationFile = telegramMediaFileFromApiDocument(effectAnimation) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
let aroundAnimationFile = aroundAnimation.flatMap(telegramMediaFileFromApiDocument)
|
||||||
|
let centerAnimationFile = centerIcon.flatMap(telegramMediaFileFromApiDocument)
|
||||||
let isEnabled = (flags & (1 << 0)) == 0
|
let isEnabled = (flags & (1 << 0)) == 0
|
||||||
self.init(
|
self.init(
|
||||||
isEnabled: isEnabled,
|
isEnabled: isEnabled,
|
||||||
@ -182,7 +216,9 @@ private extension AvailableReactions.Reaction {
|
|||||||
appearAnimation: appearAnimationFile,
|
appearAnimation: appearAnimationFile,
|
||||||
selectAnimation: selectAnimationFile,
|
selectAnimation: selectAnimationFile,
|
||||||
activateAnimation: activateAnimationFile,
|
activateAnimation: activateAnimationFile,
|
||||||
effectAnimation: effectAnimationFile
|
effectAnimation: effectAnimationFile,
|
||||||
|
aroundAnimation: aroundAnimationFile,
|
||||||
|
centerAnimation: centerAnimationFile
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -251,6 +287,12 @@ func managedSynchronizeAvailableReactions(postbox: Postbox, network: Network) ->
|
|||||||
resources.append(reaction.selectAnimation.resource)
|
resources.append(reaction.selectAnimation.resource)
|
||||||
resources.append(reaction.activateAnimation.resource)
|
resources.append(reaction.activateAnimation.resource)
|
||||||
resources.append(reaction.effectAnimation.resource)
|
resources.append(reaction.effectAnimation.resource)
|
||||||
|
if let centerAnimation = reaction.centerAnimation {
|
||||||
|
resources.append(centerAnimation.resource)
|
||||||
|
}
|
||||||
|
if let aroundAnimation = reaction.aroundAnimation {
|
||||||
|
resources.append(aroundAnimation.resource)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for resource in resources {
|
for resource in resources {
|
||||||
|
|||||||
@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
|
|||||||
|
|
||||||
public class Serialization: NSObject, MTSerialization {
|
public class Serialization: NSObject, MTSerialization {
|
||||||
public func currentLayer() -> UInt {
|
public func currentLayer() -> UInt {
|
||||||
return 136
|
return 137
|
||||||
}
|
}
|
||||||
|
|
||||||
public func parseMessage(_ data: Data!) -> Any! {
|
public func parseMessage(_ data: Data!) -> Any! {
|
||||||
|
|||||||
@ -251,6 +251,7 @@ swift_library(
|
|||||||
"//submodules/InvisibleInkDustNode:InvisibleInkDustNode",
|
"//submodules/InvisibleInkDustNode:InvisibleInkDustNode",
|
||||||
"//submodules/QrCodeUI:QrCodeUI",
|
"//submodules/QrCodeUI:QrCodeUI",
|
||||||
"//submodules/Components/ReactionListContextMenuContent:ReactionListContextMenuContent",
|
"//submodules/Components/ReactionListContextMenuContent:ReactionListContextMenuContent",
|
||||||
|
"//submodules/Components/ReactionImageComponent:ReactionImageComponent",
|
||||||
"//submodules/Translate:Translate",
|
"//submodules/Translate:Translate",
|
||||||
] + select({
|
] + select({
|
||||||
"@build_bazel_rules_apple//apple:ios_armv7": [],
|
"@build_bazel_rules_apple//apple:ios_armv7": [],
|
||||||
|
|||||||
@ -1008,6 +1008,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if !reaction.isEnabled {
|
if !reaction.isEnabled {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
guard let centerAnimation = reaction.centerAnimation else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
guard let aroundAnimation = reaction.aroundAnimation else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
switch allowedReactions {
|
switch allowedReactions {
|
||||||
case let .set(set):
|
case let .set(set):
|
||||||
@ -1021,8 +1027,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
reaction: ReactionContextItem.Reaction(rawValue: reaction.value),
|
reaction: ReactionContextItem.Reaction(rawValue: reaction.value),
|
||||||
appearAnimation: reaction.appearAnimation,
|
appearAnimation: reaction.appearAnimation,
|
||||||
stillAnimation: reaction.selectAnimation,
|
stillAnimation: reaction.selectAnimation,
|
||||||
listAnimation: reaction.activateAnimation,
|
listAnimation: centerAnimation,
|
||||||
applicationAnimation: reaction.effectAnimation
|
applicationAnimation: aroundAnimation
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1038,6 +1044,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
|
|
||||||
var updatedReaction: String? = value.reaction.rawValue
|
var updatedReaction: String? = value.reaction.rawValue
|
||||||
|
var isFirst = true
|
||||||
for attribute in topMessage.attributes {
|
for attribute in topMessage.attributes {
|
||||||
if let attribute = attribute as? ReactionsMessageAttribute {
|
if let attribute = attribute as? ReactionsMessageAttribute {
|
||||||
for reaction in attribute.reactions {
|
for reaction in attribute.reactions {
|
||||||
@ -1045,6 +1052,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if reaction.isSelected {
|
if reaction.isSelected {
|
||||||
updatedReaction = nil
|
updatedReaction = nil
|
||||||
}
|
}
|
||||||
|
isFirst = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let attribute = attribute as? PendingReactionsMessageAttribute {
|
} else if let attribute = attribute as? PendingReactionsMessageAttribute {
|
||||||
@ -1065,7 +1073,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if let itemNode = itemNode, let targetView = itemNode.targetReactionView(value: updatedReaction) {
|
if let itemNode = itemNode, let targetView = itemNode.targetReactionView(value: updatedReaction) {
|
||||||
strongSelf.chatDisplayNode.messageTransitionNode.addMessageContextController(messageId: item.message.id, contextController: controller)
|
strongSelf.chatDisplayNode.messageTransitionNode.addMessageContextController(messageId: item.message.id, contextController: controller)
|
||||||
|
|
||||||
controller.dismissWithReaction(value: updatedReaction, targetView: targetView, hideNode: true, completion: { [weak itemNode, weak targetView] in
|
var hideTargetButton: UIView?
|
||||||
|
if isFirst {
|
||||||
|
hideTargetButton = targetView.superview
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.dismissWithReaction(value: updatedReaction, targetView: targetView, hideNode: true, animateTargetContainer: hideTargetButton, completion: { [weak itemNode, weak targetView] in
|
||||||
guard let strongSelf = self, let itemNode = itemNode, let targetView = targetView else {
|
guard let strongSelf = self, let itemNode = itemNode, let targetView = targetView else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1242,6 +1255,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
if let itemNode = itemNode, let item = itemNode.item, let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: updatedReaction) {
|
if let itemNode = itemNode, let item = itemNode.item, let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: updatedReaction) {
|
||||||
for reaction in availableReactions.reactions {
|
for reaction in availableReactions.reactions {
|
||||||
|
guard let centerAnimation = reaction.centerAnimation else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
guard let aroundAnimation = reaction.aroundAnimation else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if reaction.value == updatedReaction {
|
if reaction.value == updatedReaction {
|
||||||
let standaloneReactionAnimation = StandaloneReactionAnimation()
|
let standaloneReactionAnimation = StandaloneReactionAnimation()
|
||||||
|
|
||||||
@ -1256,8 +1276,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
reaction: ReactionContextItem.Reaction(rawValue: reaction.value),
|
reaction: ReactionContextItem.Reaction(rawValue: reaction.value),
|
||||||
appearAnimation: reaction.appearAnimation,
|
appearAnimation: reaction.appearAnimation,
|
||||||
stillAnimation: reaction.selectAnimation,
|
stillAnimation: reaction.selectAnimation,
|
||||||
listAnimation: reaction.activateAnimation,
|
listAnimation: centerAnimation,
|
||||||
applicationAnimation: reaction.effectAnimation
|
applicationAnimation: aroundAnimation
|
||||||
),
|
),
|
||||||
targetView: targetView,
|
targetView: targetView,
|
||||||
hideNode: true,
|
hideNode: true,
|
||||||
|
|||||||
@ -192,7 +192,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
|
|||||||
animation.animator.updatePosition(layer: titleNode.layer, position: titleFrame.center, completion: nil)
|
animation.animator.updatePosition(layer: titleNode.layer, position: titleFrame.center, completion: nil)
|
||||||
|
|
||||||
if let buttonView = node.buttonView {
|
if let buttonView = node.buttonView {
|
||||||
animation.animator.updateFrame(layer: buttonView.layer, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: 42.0)), completion: nil)
|
buttonView.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: 42.0))
|
||||||
}
|
}
|
||||||
if let iconNode = node.iconNode {
|
if let iconNode = node.iconNode {
|
||||||
animation.animator.updateFrame(layer: iconNode.layer, frame: CGRect(x: width - 16.0, y: 4.0, width: 12.0, height: 12.0), completion: nil)
|
animation.animator.updateFrame(layer: iconNode.layer, frame: CGRect(x: width - 16.0, y: 4.0, width: 12.0, height: 12.0), completion: nil)
|
||||||
@ -324,10 +324,11 @@ final class ChatMessageActionButtonsNode: ASDisplayNode {
|
|||||||
let buttonNode = buttonApply(animation)
|
let buttonNode = buttonApply(animation)
|
||||||
updatedButtons.append(buttonNode)
|
updatedButtons.append(buttonNode)
|
||||||
if buttonNode.supernode == nil {
|
if buttonNode.supernode == nil {
|
||||||
node.addSubnode(buttonNode)
|
|
||||||
buttonNode.pressed = node.buttonPressedWrapper
|
buttonNode.pressed = node.buttonPressedWrapper
|
||||||
buttonNode.longTapped = node.buttonLongTappedWrapper
|
buttonNode.longTapped = node.buttonLongTappedWrapper
|
||||||
buttonNode.frame = buttonFrame
|
buttonNode.frame = buttonFrame
|
||||||
|
|
||||||
|
node.addSubnode(buttonNode)
|
||||||
} else {
|
} else {
|
||||||
animation.animator.updateFrame(layer: buttonNode.layer, frame: buttonFrame, completion: nil)
|
animation.animator.updateFrame(layer: buttonNode.layer, frame: buttonFrame, completion: nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -807,6 +807,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
return .fail
|
return .fail
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let actionButtonsNode = strongSelf.actionButtonsNode {
|
||||||
|
if let _ = actionButtonsNode.hitTest(strongSelf.view.convert(point, to: actionButtonsNode.view), with: nil) {
|
||||||
|
return .fail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let reactionButtonsNode = strongSelf.reactionButtonsNode {
|
if let reactionButtonsNode = strongSelf.reactionButtonsNode {
|
||||||
if let _ = reactionButtonsNode.hitTest(strongSelf.view.convert(point, to: reactionButtonsNode.view), with: nil) {
|
if let _ = reactionButtonsNode.hitTest(strongSelf.view.convert(point, to: reactionButtonsNode.view), with: nil) {
|
||||||
return .fail
|
return .fail
|
||||||
@ -1182,6 +1188,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
if isAd {
|
if isAd {
|
||||||
needsShareButton = false
|
needsShareButton = false
|
||||||
}
|
}
|
||||||
|
for attribute in item.content.firstMessage.attributes {
|
||||||
|
if let attribute = attribute as? RestrictedContentMessageAttribute, attribute.platformText(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) != nil {
|
||||||
|
needsShareButton = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var tmpWidth: CGFloat
|
var tmpWidth: CGFloat
|
||||||
if allowFullWidth {
|
if allowFullWidth {
|
||||||
@ -3435,7 +3446,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !self.backgroundNode.frame.contains(point) {
|
if !self.backgroundNode.frame.contains(point) {
|
||||||
if self.actionButtonsNode == nil || !self.actionButtonsNode!.frame.contains(point) {
|
if let actionButtonsNode = self.actionButtonsNode, let result = actionButtonsNode.hitTest(self.view.convert(point, to: actionButtonsNode.view), with: event) {
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import AccountContext
|
|||||||
import AppBundle
|
import AppBundle
|
||||||
import ReactionButtonListComponent
|
import ReactionButtonListComponent
|
||||||
import WebPBinding
|
import WebPBinding
|
||||||
|
import ReactionImageComponent
|
||||||
|
|
||||||
private func maybeAddRotationAnimation(_ layer: CALayer, duration: Double) {
|
private func maybeAddRotationAnimation(_ layer: CALayer, duration: Double) {
|
||||||
if let _ = layer.animation(forKey: "clockFrameAnimation") {
|
if let _ = layer.animation(forKey: "clockFrameAnimation") {
|
||||||
@ -73,14 +74,14 @@ private final class StatusReactionNode: ASDisplayNode {
|
|||||||
let defaultImageSize = CGSize(width: 17.0, height: 17.0)
|
let defaultImageSize = CGSize(width: 17.0, height: 17.0)
|
||||||
let imageSize: CGSize
|
let imageSize: CGSize
|
||||||
if let file = file {
|
if let file = file {
|
||||||
self.iconImageDisposable.set((context.account.postbox.mediaBox.resourceData(file.resource)
|
self.iconImageDisposable.set((reactionStaticImage(context: context, animation: file, pixelSize: CGSize(width: 72.0, height: 72.0))
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] data in
|
|> deliverOnMainQueue).start(next: { [weak self] data in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.complete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
|
if data.isComplete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
|
||||||
if let image = WebP.convert(fromWebP: dataValue) {
|
if let image = UIImage(data: dataValue) {
|
||||||
strongSelf.iconView.image = image
|
strongSelf.iconView.image = image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -667,12 +668,12 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
strongSelf.reactionSelected?(value)
|
strongSelf.reactionSelected?(value)
|
||||||
},
|
},
|
||||||
reactions: arguments.reactions.map { reaction in
|
reactions: arguments.reactions.map { reaction in
|
||||||
var iconFile: TelegramMediaFile?
|
var centerAnimation: TelegramMediaFile?
|
||||||
|
|
||||||
if let availableReactions = arguments.availableReactions {
|
if let availableReactions = arguments.availableReactions {
|
||||||
for availableReaction in availableReactions.reactions {
|
for availableReaction in availableReactions.reactions {
|
||||||
if availableReaction.value == reaction.value {
|
if availableReaction.value == reaction.value {
|
||||||
iconFile = availableReaction.staticIcon
|
centerAnimation = availableReaction.centerAnimation
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -691,7 +692,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
return ReactionButtonsAsyncLayoutContainer.Reaction(
|
return ReactionButtonsAsyncLayoutContainer.Reaction(
|
||||||
reaction: ReactionButtonComponent.Reaction(
|
reaction: ReactionButtonComponent.Reaction(
|
||||||
value: reaction.value,
|
value: reaction.value,
|
||||||
iconFile: iconFile
|
centerAnimation: centerAnimation
|
||||||
),
|
),
|
||||||
count: Int(reaction.count),
|
count: Int(reaction.count),
|
||||||
peers: peers,
|
peers: peers,
|
||||||
@ -1027,18 +1028,18 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
validReactions.insert(reaction.value)
|
validReactions.insert(reaction.value)
|
||||||
|
|
||||||
var iconFile: TelegramMediaFile?
|
var centerAnimation: TelegramMediaFile?
|
||||||
|
|
||||||
if let availableReactions = arguments.availableReactions {
|
if let availableReactions = arguments.availableReactions {
|
||||||
for availableReaction in availableReactions.reactions {
|
for availableReaction in availableReactions.reactions {
|
||||||
if availableReaction.value == reaction.value {
|
if availableReaction.value == reaction.value {
|
||||||
iconFile = availableReaction.staticIcon
|
centerAnimation = availableReaction.centerAnimation
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
node.update(context: arguments.context, type: arguments.type, value: reaction.value, file: iconFile, isSelected: reaction.isSelected, count: Int(reaction.count), theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper, animated: false)
|
node.update(context: arguments.context, type: arguments.type, value: reaction.value, file: centerAnimation, isSelected: reaction.isSelected, count: Int(reaction.count), theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper, animated: false)
|
||||||
if node.supernode == nil {
|
if node.supernode == nil {
|
||||||
strongSelf.addSubnode(node)
|
strongSelf.addSubnode(node)
|
||||||
if animation.isAnimated {
|
if animation.isAnimated {
|
||||||
|
|||||||
@ -140,12 +140,12 @@ final class MessageReactionButtonsNode: ASDisplayNode {
|
|||||||
strongSelf.reactionSelected?(value)
|
strongSelf.reactionSelected?(value)
|
||||||
},
|
},
|
||||||
reactions: reactions.reactions.map { reaction in
|
reactions: reactions.reactions.map { reaction in
|
||||||
var iconFile: TelegramMediaFile?
|
var centerAnimation: TelegramMediaFile?
|
||||||
|
|
||||||
if let availableReactions = availableReactions {
|
if let availableReactions = availableReactions {
|
||||||
for availableReaction in availableReactions.reactions {
|
for availableReaction in availableReactions.reactions {
|
||||||
if availableReaction.value == reaction.value {
|
if availableReaction.value == reaction.value {
|
||||||
iconFile = availableReaction.staticIcon
|
centerAnimation = availableReaction.centerAnimation
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -170,7 +170,7 @@ final class MessageReactionButtonsNode: ASDisplayNode {
|
|||||||
return ReactionButtonsAsyncLayoutContainer.Reaction(
|
return ReactionButtonsAsyncLayoutContainer.Reaction(
|
||||||
reaction: ReactionButtonComponent.Reaction(
|
reaction: ReactionButtonComponent.Reaction(
|
||||||
value: reaction.value,
|
value: reaction.value,
|
||||||
iconFile: iconFile
|
centerAnimation: centerAnimation
|
||||||
),
|
),
|
||||||
count: Int(reaction.count),
|
count: Int(reaction.count),
|
||||||
peers: peers,
|
peers: peers,
|
||||||
|
|||||||
@ -181,7 +181,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
strongSelf.textNode.frame = textFrame
|
strongSelf.textNode.frame = textFrame
|
||||||
|
|
||||||
if let statusSizeAndApply = statusSizeAndApply {
|
if let statusSizeAndApply = statusSizeAndApply {
|
||||||
strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: textFrameWithoutInsets.minX, y: textFrameWithoutInsets.maxY), size: statusSizeAndApply.0)
|
strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: textFrameWithoutInsets.maxX - statusSizeAndApply.0.width, y: textFrameWithoutInsets.maxY), size: statusSizeAndApply.0)
|
||||||
if strongSelf.statusNode.supernode == nil {
|
if strongSelf.statusNode.supernode == nil {
|
||||||
strongSelf.addSubnode(strongSelf.statusNode)
|
strongSelf.addSubnode(strongSelf.statusNode)
|
||||||
statusSizeAndApply.1(.None)
|
statusSizeAndApply.1(.None)
|
||||||
|
|||||||
@ -9,3 +9,18 @@ CGFloat springAnimationValueAtImpl(CABasicAnimation * _Nonnull animation, CGFloa
|
|||||||
|
|
||||||
UIBlurEffect * _Nonnull makeCustomZoomBlurEffectImpl(bool isLight);
|
UIBlurEffect * _Nonnull makeCustomZoomBlurEffectImpl(bool isLight);
|
||||||
void applySmoothRoundedCornersImpl(CALayer * _Nonnull layer);
|
void applySmoothRoundedCornersImpl(CALayer * _Nonnull layer);
|
||||||
|
|
||||||
|
@protocol UIKitPortalViewProtocol <NSObject>
|
||||||
|
|
||||||
|
@property(nonatomic) __weak UIView * _Nullable sourceView;
|
||||||
|
@property(nonatomic) _Bool forwardsClientHitTestingToSourceView;
|
||||||
|
@property(nonatomic) _Bool allowsHitTesting; // @dynamic allowsHitTesting;
|
||||||
|
@property(nonatomic) _Bool allowsBackdropGroups; // @dynamic allowsBackdropGroups;
|
||||||
|
@property(nonatomic) _Bool matchesPosition; // @dynamic matchesPosition;
|
||||||
|
@property(nonatomic) _Bool matchesTransform; // @dynamic matchesTransform;
|
||||||
|
@property(nonatomic) _Bool matchesAlpha; // @dynamic matchesAlpha;
|
||||||
|
@property(nonatomic) _Bool hidesSourceView; // @dynamic hidesSourceView;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
UIView<UIKitPortalViewProtocol> * _Nullable makePortalView();
|
||||||
|
|||||||
@ -168,3 +168,47 @@ void applySmoothRoundedCornersImpl(CALayer * _Nonnull layer) {
|
|||||||
setBoolField(layer, [@[@"set", @"Continuous", @"Corners", @":"] componentsJoinedByString:@""], true);
|
setBoolField(layer, [@[@"set", @"Continuous", @"Corners", @":"] componentsJoinedByString:@""], true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*@interface _UIPortalView : UIView
|
||||||
|
|
||||||
|
@property(nonatomic, getter=_isGeometryFrozen, setter=_setGeometryFrozen:) _Bool _geometryFrozen; // @synthesize _geometryFrozen=__geometryFrozen;
|
||||||
|
@property(nonatomic) _Bool forwardsClientHitTestingToSourceView; // @synthesize forwardsClientHitTestingToSourceView=_forwardsClientHitTestingToSourceView;
|
||||||
|
@property(copy, nonatomic) NSString * _Nullable name; // @synthesize name=_name;
|
||||||
|
@property(nonatomic) __weak UIView * _Nullable sourceView; // @synthesize sourceView=_sourceView;
|
||||||
|
- (void)setCenter:(struct CGPoint)arg1;
|
||||||
|
- (void)setBounds:(struct CGRect)arg1;
|
||||||
|
- (void)setFrame:(struct CGRect)arg1;
|
||||||
|
- (void)setHidden:(_Bool)arg1;
|
||||||
|
@property(nonatomic) _Bool allowsHitTesting; // @dynamic allowsHitTesting;
|
||||||
|
@property(nonatomic) _Bool allowsBackdropGroups; // @dynamic allowsBackdropGroups;
|
||||||
|
@property(nonatomic) _Bool matchesPosition; // @dynamic matchesPosition;
|
||||||
|
@property(nonatomic) _Bool matchesTransform; // @dynamic matchesTransform;
|
||||||
|
@property(nonatomic) _Bool matchesAlpha; // @dynamic matchesAlpha;
|
||||||
|
@property(nonatomic) _Bool hidesSourceView; // @dynamic hidesSourceView;
|
||||||
|
- (instancetype _Nonnull)initWithFrame:(struct CGRect)arg1;
|
||||||
|
- (instancetype _Nonnull)initWithSourceView:(UIView * _Nullable)arg1;
|
||||||
|
|
||||||
|
@end*/
|
||||||
|
|
||||||
|
UIView<UIKitPortalViewProtocol> * _Nullable makePortalView() {
|
||||||
|
static Class portalViewClass = nil;
|
||||||
|
static dispatch_once_t onceToken;
|
||||||
|
dispatch_once(&onceToken, ^{
|
||||||
|
portalViewClass = NSClassFromString([@[@"_", @"UI", @"Portal", @"View"] componentsJoinedByString:@""]);
|
||||||
|
});
|
||||||
|
if (!portalViewClass) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
UIView<UIKitPortalViewProtocol> *view = [[portalViewClass alloc] init];
|
||||||
|
if (!view) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
view.forwardsClientHitTestingToSourceView = false;
|
||||||
|
view.matchesPosition = true;
|
||||||
|
view.matchesTransform = true;
|
||||||
|
view.matchesAlpha = false;
|
||||||
|
view.allowsHitTesting = false;
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|||||||
@ -48,17 +48,3 @@ void applyKeyboardAutocorrection(UITextView * _Nonnull textView);
|
|||||||
@property (nonatomic, copy) UIInterfaceOrientationMask (^ _Nullable supportedOrientations)(void);
|
@property (nonatomic, copy) UIInterfaceOrientationMask (^ _Nullable supportedOrientations)(void);
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
/*@interface _UIPortalView : UIView
|
|
||||||
|
|
||||||
- (void)setSourceView:(UIView * _Nullable)sourceView;
|
|
||||||
- (bool)hidesSourceView;
|
|
||||||
- (void)setHidesSourceView:(bool)arg1;
|
|
||||||
- (void)setMatchesAlpha:(bool)arg1;
|
|
||||||
- (void)setMatchesPosition:(bool)arg1;
|
|
||||||
- (void)setMatchesTransform:(bool)arg1;
|
|
||||||
- (bool)matchesTransform;
|
|
||||||
- (bool)matchesPosition;
|
|
||||||
- (bool)matchesAlpha;
|
|
||||||
|
|
||||||
@end*/
|
|
||||||
|
|||||||
@ -105,22 +105,6 @@ static bool notyfyingShiftState = false;
|
|||||||
[RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(presentingViewController) newSelector:@selector(_65087dc8_presentingViewController)];
|
[RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(presentingViewController) newSelector:@selector(_65087dc8_presentingViewController)];
|
||||||
[RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(presentViewController:animated:completion:) newSelector:@selector(_65087dc8_presentViewController:animated:completion:)];
|
[RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(presentViewController:animated:completion:) newSelector:@selector(_65087dc8_presentViewController:animated:completion:)];
|
||||||
[RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(setNeedsStatusBarAppearanceUpdate) newSelector:@selector(_65087dc8_setNeedsStatusBarAppearanceUpdate)];
|
[RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(setNeedsStatusBarAppearanceUpdate) newSelector:@selector(_65087dc8_setNeedsStatusBarAppearanceUpdate)];
|
||||||
|
|
||||||
/*#pragma clang diagnostic push
|
|
||||||
#pragma clang diagnostic ignored "-Wundeclared-selector"
|
|
||||||
if (@available(iOS 13, *)) {
|
|
||||||
Class UIUndoGestureInteractionClass = NSClassFromString(@"UIUndoGestureInteraction");
|
|
||||||
SEL addGestureRecognizersSelector = @selector(_addGestureRecognizers);
|
|
||||||
IMP doNothing = imp_implementationWithBlock(^void(__unused id _self) {
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
|
|
||||||
method_setImplementation(class_getInstanceMethod(UIUndoGestureInteractionClass, addGestureRecognizersSelector), doNothing);
|
|
||||||
}
|
|
||||||
#pragma clang diagnostic pop*/
|
|
||||||
|
|
||||||
//[RuntimeUtils swizzleInstanceMethodOfClass:NSClassFromString(@"UIKeyboardImpl") currentSelector:@selector(notifyShiftState) withAnotherClass:[UIKeyboardImpl_65087dc8 class] newSelector:@selector(notifyShiftState)];
|
|
||||||
//[RuntimeUtils swizzleInstanceMethodOfClass:NSClassFromString(@"UIInputWindowController") currentSelector:@selector(updateViewConstraints) withAnotherClass:[UIInputWindowController_65087dc8 class] newSelector:@selector(updateViewConstraints)];
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -35,7 +35,7 @@
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_animation = rlottie::Animation::loadFromData(std::string(reinterpret_cast<const char *>(data.bytes), data.length), std::string([cacheKey UTF8String]), "", true, {}, modifier);
|
_animation = rlottie::Animation::loadFromData(std::string(reinterpret_cast<const char *>(data.bytes), data.length), std::string([cacheKey UTF8String]), "", false, {}, modifier);
|
||||||
if (_animation == nullptr) {
|
if (_animation == nullptr) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user