mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Interactive story reactions
This commit is contained in:
parent
db9e286857
commit
f33537509f
@ -1242,6 +1242,8 @@ private final class DrawingScreenComponent: CombinedComponent {
|
||||
nextStyle = .regular
|
||||
case .stroke:
|
||||
nextStyle = .regular
|
||||
case .blur:
|
||||
nextStyle = .regular
|
||||
}
|
||||
textEntity.style = nextStyle
|
||||
updateEntityView.invoke((textEntity.uuid, false))
|
||||
@ -3515,6 +3517,8 @@ public final class DrawingToolsInteraction {
|
||||
nextStyle = .regular
|
||||
case .stroke:
|
||||
nextStyle = .regular
|
||||
case .blur:
|
||||
nextStyle = .regular
|
||||
}
|
||||
textEntity.style = nextStyle
|
||||
entityView.update()
|
||||
|
@ -89,6 +89,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/NavigationSearchComponent",
|
||||
"//submodules/TelegramUI/Components/TabSelectorComponent",
|
||||
"//submodules/TelegramUI/Components/OptionButtonComponent",
|
||||
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -30,18 +30,22 @@ final class StoryItemContentComponent: Component {
|
||||
let strings: PresentationStrings
|
||||
let peer: EnginePeer
|
||||
let item: EngineStoryItem
|
||||
let availableReactions: StoryAvailableReactions?
|
||||
let audioMode: StoryContentItem.AudioMode
|
||||
let isVideoBuffering: Bool
|
||||
let isCurrent: Bool
|
||||
let activateReaction: (UIView, MessageReaction.Reaction) -> Void
|
||||
|
||||
init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer, item: EngineStoryItem, audioMode: StoryContentItem.AudioMode, isVideoBuffering: Bool, isCurrent: Bool) {
|
||||
init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer, item: EngineStoryItem, availableReactions: StoryAvailableReactions?, audioMode: StoryContentItem.AudioMode, isVideoBuffering: Bool, isCurrent: Bool, activateReaction: @escaping (UIView, MessageReaction.Reaction) -> Void) {
|
||||
self.context = context
|
||||
self.strings = strings
|
||||
self.peer = peer
|
||||
self.item = item
|
||||
self.availableReactions = availableReactions
|
||||
self.audioMode = audioMode
|
||||
self.isVideoBuffering = isVideoBuffering
|
||||
self.isCurrent = isCurrent
|
||||
self.activateReaction = activateReaction
|
||||
}
|
||||
|
||||
static func ==(lhs: StoryItemContentComponent, rhs: StoryItemContentComponent) -> Bool {
|
||||
@ -57,6 +61,9 @@ final class StoryItemContentComponent: Component {
|
||||
if lhs.item != rhs.item {
|
||||
return false
|
||||
}
|
||||
if lhs.availableReactions != rhs.availableReactions {
|
||||
return false
|
||||
}
|
||||
if lhs.isVideoBuffering != rhs.isVideoBuffering {
|
||||
return false
|
||||
}
|
||||
@ -68,6 +75,7 @@ final class StoryItemContentComponent: Component {
|
||||
|
||||
final class View: StoryContentItem.View {
|
||||
private let imageView: StoryItemImageView
|
||||
private let overlaysView: StoryItemOverlaysView
|
||||
private var videoNode: UniversalVideoNode?
|
||||
private var loadingEffectView: StoryItemLoadingEffectView?
|
||||
|
||||
@ -107,12 +115,14 @@ final class StoryItemContentComponent: Component {
|
||||
override init(frame: CGRect) {
|
||||
self.hierarchyTrackingLayer = HierarchyTrackingLayer()
|
||||
self.imageView = StoryItemImageView()
|
||||
self.overlaysView = StoryItemOverlaysView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.layer.addSublayer(self.hierarchyTrackingLayer)
|
||||
|
||||
self.addSubview(self.imageView)
|
||||
self.addSubview(self.overlaysView)
|
||||
|
||||
self.hierarchyTrackingLayer.isInHierarchyUpdated = { [weak self] value in
|
||||
guard let self else {
|
||||
@ -120,6 +130,13 @@ final class StoryItemContentComponent: Component {
|
||||
}
|
||||
self.updateProgressMode(update: true)
|
||||
}
|
||||
|
||||
self.overlaysView.activate = { [weak self] view, reaction in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.activateReaction(view, reaction)
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@ -449,6 +466,9 @@ final class StoryItemContentComponent: Component {
|
||||
return result
|
||||
}
|
||||
}
|
||||
if let result = self.overlaysView.hitTest(self.convert(point, to: self.overlaysView), with: event) {
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -579,11 +599,23 @@ final class StoryItemContentComponent: Component {
|
||||
attemptSynchronous: synchronousLoad,
|
||||
transition: transition
|
||||
)
|
||||
self.overlaysView.update(
|
||||
context: component.context,
|
||||
strings: component.strings,
|
||||
peer: component.peer,
|
||||
story: component.item,
|
||||
availableReactions: component.availableReactions,
|
||||
size: availableSize,
|
||||
isCaptureProtected: component.item.isForwardingDisabled,
|
||||
attemptSynchronous: synchronousLoad,
|
||||
transition: transition
|
||||
)
|
||||
applyState = true
|
||||
if self.imageView.isContentLoaded {
|
||||
self.contentLoaded = true
|
||||
}
|
||||
transition.setFrame(view: self.imageView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
transition.setFrame(view: self.overlaysView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
||||
var dimensions: CGSize?
|
||||
switch messageMedia {
|
||||
|
@ -0,0 +1,205 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AccountContext
|
||||
import TelegramCore
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import ComponentFlow
|
||||
import TinyThumbnail
|
||||
import ImageBlur
|
||||
import MediaResources
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import BundleIconComponent
|
||||
import MultilineTextComponent
|
||||
import AppBundle
|
||||
import EmojiTextAttachmentView
|
||||
import TextFormat
|
||||
|
||||
final class StoryItemOverlaysView: UIView {
|
||||
private static let coverImage: UIImage = {
|
||||
return UIImage(bundleImageName: "Stories/ReactionOutline")!
|
||||
}()
|
||||
|
||||
private final class ItemView: HighlightTrackingButton {
|
||||
private let coverView: UIImageView
|
||||
private var stickerView: EmojiTextAttachmentView?
|
||||
private var file: TelegramMediaFile?
|
||||
|
||||
private var reaction: MessageReaction.Reaction?
|
||||
var activate: ((UIView, MessageReaction.Reaction) -> Void)?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.coverView = UIImageView(image: StoryItemOverlaysView.coverImage)
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.coverView)
|
||||
|
||||
self.highligthedChanged = { [weak self] highlighted in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
if highlighted {
|
||||
let transition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut))
|
||||
transition.setSublayerTransform(view: self, transform: CATransform3DMakeScale(0.9, 0.9, 1.0))
|
||||
} else {
|
||||
let transition: Transition = .immediate
|
||||
transition.setSublayerTransform(view: self, transform: CATransform3DIdentity)
|
||||
self.layer.animateSpring(from: 0.9 as NSNumber, to: 1.0 as NSNumber, keyPath: "sublayerTransform.scale", duration: 0.4)
|
||||
}
|
||||
}
|
||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func pressed() {
|
||||
guard let activate = self.activate, let reaction = self.reaction else {
|
||||
return
|
||||
}
|
||||
activate(self, reaction)
|
||||
}
|
||||
|
||||
func update(
|
||||
context: AccountContext,
|
||||
reaction: MessageReaction.Reaction,
|
||||
availableReactions: StoryAvailableReactions?,
|
||||
synchronous: Bool,
|
||||
size: CGSize
|
||||
) {
|
||||
self.reaction = reaction
|
||||
|
||||
let insets = UIEdgeInsets(top: -0.08, left: -0.05, bottom: -0.01, right: -0.02)
|
||||
self.coverView.frame = CGRect(origin: CGPoint(x: size.width * insets.left, y: size.height * insets.top), size: CGSize(width: size.width - size.width * insets.left - size.width * insets.right, height: size.height - size.height * insets.top - size.height * insets.bottom))
|
||||
|
||||
let minSide = floor(min(200.0, min(size.width, size.height)) * 0.65)
|
||||
let itemSize = CGSize(width: minSide, height: minSide)
|
||||
|
||||
var file: TelegramMediaFile? = self.file
|
||||
if self.file == nil {
|
||||
switch reaction {
|
||||
case .builtin:
|
||||
if let availableReactions {
|
||||
for reactionItem in availableReactions.reactionItems {
|
||||
if reactionItem.reaction.rawValue == reaction {
|
||||
file = reactionItem.stillAnimation
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .custom(fileId):
|
||||
let _ = fileId
|
||||
}
|
||||
}
|
||||
|
||||
if self.file?.fileId != file?.fileId, let file {
|
||||
self.file = file
|
||||
|
||||
let stickerView: EmojiTextAttachmentView
|
||||
if let current = self.stickerView {
|
||||
stickerView = current
|
||||
} else {
|
||||
stickerView = EmojiTextAttachmentView(
|
||||
context: context,
|
||||
userLocation: .other,
|
||||
emoji: ChatTextInputTextCustomEmojiAttribute(
|
||||
interactivelySelectedFromPackId: nil,
|
||||
fileId: file.fileId.id,
|
||||
file: file
|
||||
),
|
||||
file: file,
|
||||
cache: context.animationCache,
|
||||
renderer: context.animationRenderer,
|
||||
placeholderColor: UIColor(white: 0.0, alpha: 0.1),
|
||||
pointSize: CGSize(width: itemSize.width, height: itemSize.height)
|
||||
)
|
||||
stickerView.isUserInteractionEnabled = false
|
||||
self.stickerView = stickerView
|
||||
self.addSubview(stickerView)
|
||||
}
|
||||
|
||||
stickerView.frame = itemSize.centered(around: CGPoint(x: size.width * 0.5, y: size.height * 0.47))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var itemViews: [Int: ItemView] = [:]
|
||||
var activate: ((UIView, MessageReaction.Reaction) -> Void)?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
for (_, itemView) in self.itemViews {
|
||||
if let result = itemView.hitTest(self.convert(point, to: itemView), with: event) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func update(
|
||||
context: AccountContext,
|
||||
strings: PresentationStrings,
|
||||
peer: EnginePeer,
|
||||
story: EngineStoryItem,
|
||||
availableReactions: StoryAvailableReactions?,
|
||||
size: CGSize,
|
||||
isCaptureProtected: Bool,
|
||||
attemptSynchronous: Bool,
|
||||
transition: Transition
|
||||
) {
|
||||
var nextId = 0
|
||||
for mediaArea in story.mediaAreas {
|
||||
switch mediaArea {
|
||||
case let .reaction(coordinates, reaction):
|
||||
let referenceSize = size
|
||||
let areaSize = CGSize(width: coordinates.width / 100.0 * referenceSize.width, height: coordinates.height / 100.0 * referenceSize.height)
|
||||
let targetFrame = CGRect(x: coordinates.x / 100.0 * referenceSize.width - areaSize.width * 0.5, y: coordinates.y / 100.0 * referenceSize.height - areaSize.height * 0.5, width: areaSize.width, height: areaSize.height)
|
||||
if targetFrame.width < 5.0 || targetFrame.height < 5.0 {
|
||||
continue
|
||||
}
|
||||
|
||||
let itemView: ItemView
|
||||
let itemId = nextId
|
||||
if let current = self.itemViews[itemId] {
|
||||
itemView = current
|
||||
} else {
|
||||
itemView = ItemView(frame: CGRect())
|
||||
itemView.activate = { [weak self] view, reaction in
|
||||
self?.activate?(view, reaction)
|
||||
}
|
||||
self.itemViews[itemId] = itemView
|
||||
self.addSubview(itemView)
|
||||
}
|
||||
|
||||
transition.setPosition(view: itemView, position: targetFrame.center)
|
||||
transition.setBounds(view: itemView, bounds: CGRect(origin: CGPoint(), size: targetFrame.size))
|
||||
transition.setTransform(view: itemView, transform: CATransform3DMakeRotation(coordinates.rotation * (CGFloat.pi / 180.0), 0.0, 0.0, 1.0))
|
||||
itemView.update(
|
||||
context: context,
|
||||
reaction: reaction,
|
||||
availableReactions: availableReactions,
|
||||
synchronous: attemptSynchronous,
|
||||
size: targetFrame.size
|
||||
)
|
||||
|
||||
nextId += 1
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -920,7 +920,11 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
|
||||
for area in component.slice.item.storyItem.mediaAreas {
|
||||
if isPoint(point, in: area) {
|
||||
if case .reaction = area {
|
||||
continue
|
||||
}
|
||||
|
||||
if isPoint(point, in: area) {
|
||||
selectedMediaArea = area
|
||||
break
|
||||
}
|
||||
@ -1483,9 +1487,16 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
strings: component.strings,
|
||||
peer: component.slice.peer,
|
||||
item: item.storyItem,
|
||||
availableReactions: component.availableReactions,
|
||||
audioMode: component.audioMode,
|
||||
isVideoBuffering: visibleItem.isBuffering,
|
||||
isCurrent: index == centralIndex
|
||||
isCurrent: index == centralIndex,
|
||||
activateReaction: { [weak self] reactionView, reaction in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.sendMessageContext.activateInlineReaction(view: self, reactionView: reactionView, reaction: reaction)
|
||||
}
|
||||
)),
|
||||
environment: {
|
||||
itemEnvironment
|
||||
|
@ -3268,102 +3268,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
}
|
||||
controller?.push(locationController)
|
||||
}))
|
||||
case let .reaction(_, reaction):
|
||||
if component.slice.peer.id != component.context.account.peerId {
|
||||
let animateWithReactionItem: (ReactionItem) -> Void = { [weak self, weak view] reactionItem in
|
||||
guard let self, let view else {
|
||||
return
|
||||
}
|
||||
|
||||
self.performWithPossibleStealthModeConfirmation(view: view, action: { [weak view] in
|
||||
guard let view, let component = view.component else {
|
||||
return
|
||||
}
|
||||
let _ = component.context.engine.messages.setStoryReaction(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id, reaction: reaction).start()
|
||||
|
||||
let referenceSize = view.controlsContainerView.frame.size
|
||||
let size = CGSize(width: 16.0, height: mediaArea.coordinates.height / 100.0 * referenceSize.height * 1.1)
|
||||
var targetFrame = CGRect(x: mediaArea.coordinates.x / 100.0 * referenceSize.width - size.width / 2.0, y: mediaArea.coordinates.y / 100.0 * referenceSize.height - size.height / 2.0, width: size.width, height: size.height)
|
||||
let maxSide = min(300.0, max(targetFrame.width, targetFrame.height))
|
||||
targetFrame = CGSize(width: maxSide, height: maxSide).centered(around: targetFrame.center)
|
||||
//targetFrame = targetFrame.insetBy(dx: -50.0, dy: -50.0)
|
||||
targetFrame = view.controlsContainerView.convert(targetFrame, to: view)
|
||||
|
||||
let targetView = UIView(frame: targetFrame)
|
||||
targetView.isUserInteractionEnabled = false
|
||||
view.addSubview(targetView)
|
||||
|
||||
let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: nil, useDirectRendering: false)
|
||||
view.componentContainerView.addSubview(standaloneReactionAnimation.view)
|
||||
|
||||
if let standaloneReactionAnimation = view.standaloneReactionAnimation {
|
||||
view.standaloneReactionAnimation = nil
|
||||
standaloneReactionAnimation.view.removeFromSuperview()
|
||||
}
|
||||
view.standaloneReactionAnimation = standaloneReactionAnimation
|
||||
|
||||
standaloneReactionAnimation.frame = view.bounds
|
||||
standaloneReactionAnimation.animateReactionSelection(
|
||||
context: component.context,
|
||||
theme: component.theme,
|
||||
animationCache: component.context.animationCache,
|
||||
reaction: reactionItem,
|
||||
avatarPeers: [],
|
||||
playHaptic: true,
|
||||
isLarge: false,
|
||||
hideCenterAnimation: true,
|
||||
targetView: targetView,
|
||||
addStandaloneReactionAnimation: { [weak view] standaloneReactionAnimation in
|
||||
guard let view else {
|
||||
return
|
||||
}
|
||||
|
||||
if let standaloneReactionAnimation = view.standaloneReactionAnimation {
|
||||
view.standaloneReactionAnimation = nil
|
||||
standaloneReactionAnimation.view.removeFromSuperview()
|
||||
}
|
||||
view.standaloneReactionAnimation = standaloneReactionAnimation
|
||||
|
||||
standaloneReactionAnimation.frame = view.bounds
|
||||
view.componentContainerView.addSubview(standaloneReactionAnimation.view)
|
||||
},
|
||||
completion: { [weak targetView, weak standaloneReactionAnimation] in
|
||||
targetView?.removeFromSuperview()
|
||||
standaloneReactionAnimation?.view.removeFromSuperview()
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
switch reaction {
|
||||
case .builtin:
|
||||
if let availableReactions = component.availableReactions {
|
||||
for reactionItem in availableReactions.reactionItems {
|
||||
if reactionItem.reaction.rawValue == reaction {
|
||||
animateWithReactionItem(reactionItem)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .custom(fileId):
|
||||
let _ = (component.context.engine.stickers.resolveInlineStickers(fileIds: [fileId])
|
||||
|> deliverOnMainQueue).start(next: { files in
|
||||
if let itemFile = files[fileId] {
|
||||
let reactionItem = ReactionItem(
|
||||
reaction: ReactionItem.Reaction(rawValue: .custom(itemFile.fileId.id)),
|
||||
appearAnimation: itemFile,
|
||||
stillAnimation: itemFile,
|
||||
listAnimation: itemFile,
|
||||
largeListAnimation: itemFile,
|
||||
applicationAnimation: nil,
|
||||
largeApplicationAnimation: nil,
|
||||
isCustom: true
|
||||
)
|
||||
animateWithReactionItem(reactionItem)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
case .reaction:
|
||||
return
|
||||
}
|
||||
|
||||
@ -3397,4 +3302,100 @@ final class StoryItemSetContainerSendMessage {
|
||||
self.menuController = menuController
|
||||
view.updateIsProgressPaused()
|
||||
}
|
||||
|
||||
func activateInlineReaction(view: StoryItemSetContainerComponent.View, reactionView: UIView, reaction: MessageReaction.Reaction) {
|
||||
guard let component = view.component else {
|
||||
return
|
||||
}
|
||||
|
||||
if component.slice.peer.id != component.context.account.peerId {
|
||||
let animateWithReactionItem: (ReactionItem) -> Void = { [weak self, weak view] reactionItem in
|
||||
guard let self, let view else {
|
||||
return
|
||||
}
|
||||
|
||||
self.performWithPossibleStealthModeConfirmation(view: view, action: { [weak view] in
|
||||
guard let view, let component = view.component else {
|
||||
return
|
||||
}
|
||||
let _ = component.context.engine.messages.setStoryReaction(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id, reaction: reaction).start()
|
||||
|
||||
let targetFrame = reactionView.convert(reactionView.bounds, to: view)
|
||||
|
||||
let targetView = UIView(frame: targetFrame)
|
||||
targetView.isUserInteractionEnabled = false
|
||||
view.addSubview(targetView)
|
||||
|
||||
let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: nil, useDirectRendering: false)
|
||||
view.componentContainerView.addSubview(standaloneReactionAnimation.view)
|
||||
|
||||
if let standaloneReactionAnimation = view.standaloneReactionAnimation {
|
||||
view.standaloneReactionAnimation = nil
|
||||
standaloneReactionAnimation.view.removeFromSuperview()
|
||||
}
|
||||
view.standaloneReactionAnimation = standaloneReactionAnimation
|
||||
|
||||
standaloneReactionAnimation.frame = view.bounds
|
||||
standaloneReactionAnimation.animateReactionSelection(
|
||||
context: component.context,
|
||||
theme: component.theme,
|
||||
animationCache: component.context.animationCache,
|
||||
reaction: reactionItem,
|
||||
avatarPeers: [],
|
||||
playHaptic: true,
|
||||
isLarge: false,
|
||||
hideCenterAnimation: true,
|
||||
targetView: targetView,
|
||||
addStandaloneReactionAnimation: { [weak view] standaloneReactionAnimation in
|
||||
guard let view else {
|
||||
return
|
||||
}
|
||||
|
||||
if let standaloneReactionAnimation = view.standaloneReactionAnimation {
|
||||
view.standaloneReactionAnimation = nil
|
||||
standaloneReactionAnimation.view.removeFromSuperview()
|
||||
}
|
||||
view.standaloneReactionAnimation = standaloneReactionAnimation
|
||||
|
||||
standaloneReactionAnimation.frame = view.bounds
|
||||
view.componentContainerView.addSubview(standaloneReactionAnimation.view)
|
||||
},
|
||||
completion: { [weak targetView, weak standaloneReactionAnimation] in
|
||||
targetView?.removeFromSuperview()
|
||||
standaloneReactionAnimation?.view.removeFromSuperview()
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
switch reaction {
|
||||
case .builtin:
|
||||
if let availableReactions = component.availableReactions {
|
||||
for reactionItem in availableReactions.reactionItems {
|
||||
if reactionItem.reaction.rawValue == reaction {
|
||||
animateWithReactionItem(reactionItem)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .custom(fileId):
|
||||
let _ = (component.context.engine.stickers.resolveInlineStickers(fileIds: [fileId])
|
||||
|> deliverOnMainQueue).start(next: { files in
|
||||
if let itemFile = files[fileId] {
|
||||
let reactionItem = ReactionItem(
|
||||
reaction: ReactionItem.Reaction(rawValue: .custom(itemFile.fileId.id)),
|
||||
appearAnimation: itemFile,
|
||||
stillAnimation: itemFile,
|
||||
listAnimation: itemFile,
|
||||
largeListAnimation: itemFile,
|
||||
applicationAnimation: nil,
|
||||
largeApplicationAnimation: nil,
|
||||
isCustom: true
|
||||
)
|
||||
animateWithReactionItem(reactionItem)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
15
submodules/TelegramUI/Images.xcassets/Stories/ReactionOutline.imageset/Contents.json
vendored
Normal file
15
submodules/TelegramUI/Images.xcassets/Stories/ReactionOutline.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ReactionOutline.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Stories/ReactionOutline.imageset/ReactionOutline.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Stories/ReactionOutline.imageset/ReactionOutline.pdf
vendored
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user