mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Message preview and effect improvements
This commit is contained in:
parent
b420532822
commit
fe788ed9ea
@ -1037,6 +1037,9 @@ public protocol ChatMessageItemNodeProtocol: ListViewItemNode {
|
|||||||
func targetReactionView(value: MessageReaction.Reaction) -> UIView?
|
func targetReactionView(value: MessageReaction.Reaction) -> UIView?
|
||||||
func targetForStoryTransition(id: StoryId) -> UIView?
|
func targetForStoryTransition(id: StoryId) -> UIView?
|
||||||
func contentFrame() -> CGRect
|
func contentFrame() -> CGRect
|
||||||
|
func matchesMessage(id: MessageId) -> Bool
|
||||||
|
func cancelInsertionAnimations()
|
||||||
|
func messages() -> [Message]
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class ChatControllerNavigationData: CustomViewControllerNavigationData {
|
public final class ChatControllerNavigationData: CustomViewControllerNavigationData {
|
||||||
|
@ -1032,6 +1032,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
schedule: { [weak textInputPanelNode] messageEffect in
|
schedule: { [weak textInputPanelNode] messageEffect in
|
||||||
textInputPanelNode?.sendMessage(.schedule, messageEffect)
|
textInputPanelNode?.sendMessage(.schedule, messageEffect)
|
||||||
},
|
},
|
||||||
|
openPremiumPaywall: { [weak self] c in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.controller?.push(c)
|
||||||
|
},
|
||||||
reactionItems: effectItems,
|
reactionItems: effectItems,
|
||||||
availableMessageEffects: availableMessageEffects,
|
availableMessageEffects: availableMessageEffects,
|
||||||
isPremium: hasPremium
|
isPremium: hasPremium
|
||||||
|
@ -177,6 +177,7 @@ public func makeChatSendMessageActionSheetController(
|
|||||||
completion: @escaping () -> Void,
|
completion: @escaping () -> Void,
|
||||||
sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void,
|
sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void,
|
||||||
schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void,
|
schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void,
|
||||||
|
openPremiumPaywall: @escaping (ViewController) -> Void,
|
||||||
reactionItems: [ReactionItem]? = nil,
|
reactionItems: [ReactionItem]? = nil,
|
||||||
availableMessageEffects: AvailableMessageEffects? = nil,
|
availableMessageEffects: AvailableMessageEffects? = nil,
|
||||||
isPremium: Bool = false
|
isPremium: Bool = false
|
||||||
@ -221,6 +222,7 @@ public func makeChatSendMessageActionSheetController(
|
|||||||
completion: completion,
|
completion: completion,
|
||||||
sendMessage: sendMessage,
|
sendMessage: sendMessage,
|
||||||
schedule: schedule,
|
schedule: schedule,
|
||||||
|
openPremiumPaywall: openPremiumPaywall,
|
||||||
reactionItems: reactionItems,
|
reactionItems: reactionItems,
|
||||||
availableMessageEffects: availableMessageEffects,
|
availableMessageEffects: availableMessageEffects,
|
||||||
isPremium: isPremium
|
isPremium: isPremium
|
||||||
|
@ -71,6 +71,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
let completion: () -> Void
|
let completion: () -> Void
|
||||||
let sendMessage: (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void
|
let sendMessage: (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void
|
||||||
let schedule: (ChatSendMessageActionSheetController.SendParameters?) -> Void
|
let schedule: (ChatSendMessageActionSheetController.SendParameters?) -> Void
|
||||||
|
let openPremiumPaywall: (ViewController) -> Void
|
||||||
let reactionItems: [ReactionItem]?
|
let reactionItems: [ReactionItem]?
|
||||||
let availableMessageEffects: AvailableMessageEffects?
|
let availableMessageEffects: AvailableMessageEffects?
|
||||||
let isPremium: Bool
|
let isPremium: Bool
|
||||||
@ -94,6 +95,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
completion: @escaping () -> Void,
|
completion: @escaping () -> Void,
|
||||||
sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void,
|
sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void,
|
||||||
schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void,
|
schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void,
|
||||||
|
openPremiumPaywall: @escaping (ViewController) -> Void,
|
||||||
reactionItems: [ReactionItem]?,
|
reactionItems: [ReactionItem]?,
|
||||||
availableMessageEffects: AvailableMessageEffects?,
|
availableMessageEffects: AvailableMessageEffects?,
|
||||||
isPremium: Bool
|
isPremium: Bool
|
||||||
@ -116,6 +118,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
self.completion = completion
|
self.completion = completion
|
||||||
self.sendMessage = sendMessage
|
self.sendMessage = sendMessage
|
||||||
self.schedule = schedule
|
self.schedule = schedule
|
||||||
|
self.openPremiumPaywall = openPremiumPaywall
|
||||||
self.reactionItems = reactionItems
|
self.reactionItems = reactionItems
|
||||||
self.availableMessageEffects = availableMessageEffects
|
self.availableMessageEffects = availableMessageEffects
|
||||||
self.isPremium = isPremium
|
self.isPremium = isPremium
|
||||||
@ -153,6 +156,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
|
|
||||||
private var sendButton: SendButton?
|
private var sendButton: SendButton?
|
||||||
private var messageItemView: MessageItemView?
|
private var messageItemView: MessageItemView?
|
||||||
|
private var internalWallpaperBackgroundNode: WallpaperBackgroundNode?
|
||||||
private var actionsStackNode: ContextControllerActionsStackNode?
|
private var actionsStackNode: ContextControllerActionsStackNode?
|
||||||
private var reactionContextNode: ReactionContextNode?
|
private var reactionContextNode: ReactionContextNode?
|
||||||
|
|
||||||
@ -174,6 +178,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
private var loadEffectAnimationDisposable: Disposable?
|
private var loadEffectAnimationDisposable: Disposable?
|
||||||
|
|
||||||
private var animateInTimestamp: Double?
|
private var animateInTimestamp: Double?
|
||||||
|
private var performedActionsOnAnimateOut: Bool = false
|
||||||
private var presentationAnimationState: PresentationAnimationState = .initial
|
private var presentationAnimationState: PresentationAnimationState = .initial
|
||||||
private var appliedAnimationState: PresentationAnimationState = .initial
|
private var appliedAnimationState: PresentationAnimationState = .initial
|
||||||
private var animateOutToEmpty: Bool = false
|
private var animateOutToEmpty: Bool = false
|
||||||
@ -226,13 +231,13 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
self.animateOutToEmpty = true
|
self.animateOutToEmpty = true
|
||||||
|
|
||||||
|
self.environment?.controller()?.dismiss()
|
||||||
|
|
||||||
let sendParameters = ChatSendMessageActionSheetController.SendParameters(
|
let sendParameters = ChatSendMessageActionSheetController.SendParameters(
|
||||||
effect: self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.SendParameters.Effect(id: $0.id) }),
|
effect: self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.SendParameters.Effect(id: $0.id) }),
|
||||||
textIsAboveMedia: self.mediaCaptionIsAbove
|
textIsAboveMedia: self.mediaCaptionIsAbove
|
||||||
)
|
)
|
||||||
|
|
||||||
component.sendMessage(.generic, sendParameters)
|
component.sendMessage(.generic, sendParameters)
|
||||||
self.environment?.controller()?.dismiss()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateIn() {
|
func animateIn() {
|
||||||
@ -247,6 +252,15 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func animateOut(completion: @escaping () -> Void) {
|
func animateOut(completion: @escaping () -> Void) {
|
||||||
|
if let controller = self.environment?.controller() {
|
||||||
|
controller.forEachController { c in
|
||||||
|
if let c = c as? UndoOverlayController {
|
||||||
|
c.dismiss()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if case .animatedOut = self.presentationAnimationState {
|
if case .animatedOut = self.presentationAnimationState {
|
||||||
} else {
|
} else {
|
||||||
self.presentationAnimationState = .animatedOut(completion: completion)
|
self.presentationAnimationState = .animatedOut(completion: completion)
|
||||||
@ -361,9 +375,14 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isMessageVisible = component.mediaPreview != nil
|
||||||
|
|
||||||
let textString: NSAttributedString
|
let textString: NSAttributedString
|
||||||
if let attributedText = component.textInputView.attributedText {
|
if let attributedText = component.textInputView.attributedText {
|
||||||
textString = attributedText
|
textString = attributedText
|
||||||
|
if textString.length != 0 {
|
||||||
|
isMessageVisible = true
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
textString = NSAttributedString(string: " ", font: Font.regular(17.0), textColor: .black)
|
textString = NSAttributedString(string: " ", font: Font.regular(17.0), textColor: .black)
|
||||||
}
|
}
|
||||||
@ -439,6 +458,8 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)))
|
)))
|
||||||
|
|
||||||
|
items.append(.separator)
|
||||||
}
|
}
|
||||||
if !reminders {
|
if !reminders {
|
||||||
items.append(.action(ContextMenuActionItem(
|
items.append(.action(ContextMenuActionItem(
|
||||||
@ -508,6 +529,10 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if case .separator = items.last {
|
||||||
|
items.removeLast()
|
||||||
|
}
|
||||||
|
|
||||||
let actionsStackNode: ContextControllerActionsStackNode
|
let actionsStackNode: ContextControllerActionsStackNode
|
||||||
if let current = self.actionsStackNode {
|
if let current = self.actionsStackNode {
|
||||||
actionsStackNode = current
|
actionsStackNode = current
|
||||||
@ -536,7 +561,9 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
actionsStackNode.layer.anchorPoint = CGPoint(x: 1.0, y: 0.0)
|
if isMessageVisible {
|
||||||
|
actionsStackNode.layer.anchorPoint = CGPoint(x: 1.0, y: 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
actionsStackNode.push(
|
actionsStackNode.push(
|
||||||
item: ContextControllerActionsListStackItem(
|
item: ContextControllerActionsListStackItem(
|
||||||
@ -570,6 +597,24 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
self.addSubview(messageItemView)
|
self.addSubview(messageItemView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let wallpaperBackgroundNode: WallpaperBackgroundNode
|
||||||
|
if let externalWallpaperBackgroundNode = component.wallpaperBackgroundNode {
|
||||||
|
wallpaperBackgroundNode = externalWallpaperBackgroundNode
|
||||||
|
} else if let current = self.internalWallpaperBackgroundNode {
|
||||||
|
wallpaperBackgroundNode = current
|
||||||
|
wallpaperBackgroundNode.frame = CGRect(origin: CGPoint(), size: availableSize)
|
||||||
|
wallpaperBackgroundNode.updateLayout(size: availableSize, displayMode: .aspectFill, transition: .immediate)
|
||||||
|
} else {
|
||||||
|
wallpaperBackgroundNode = createWallpaperBackgroundNode(context: component.context, forChatDisplay: true, useSharedAnimationPhase: false)
|
||||||
|
wallpaperBackgroundNode.frame = CGRect(origin: CGPoint(), size: availableSize)
|
||||||
|
wallpaperBackgroundNode.updateLayout(size: availableSize, displayMode: .aspectFill, transition: .immediate)
|
||||||
|
wallpaperBackgroundNode.updateBubbleTheme(bubbleTheme: presentationData.theme, bubbleCorners: presentationData.chatBubbleCorners)
|
||||||
|
wallpaperBackgroundNode.update(wallpaper: presentationData.chatWallpaper, animated: false)
|
||||||
|
self.internalWallpaperBackgroundNode = wallpaperBackgroundNode
|
||||||
|
self.insertSubview(wallpaperBackgroundNode.view, at: 0)
|
||||||
|
wallpaperBackgroundNode.alpha = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
let localSourceTextInputViewFrame = convertFrame(component.textInputView.bounds, from: component.textInputView, to: self)
|
let localSourceTextInputViewFrame = convertFrame(component.textInputView.bounds, from: component.textInputView, to: self)
|
||||||
|
|
||||||
let sourceMessageTextInsets = UIEdgeInsets(top: 7.0, left: 12.0, bottom: 6.0, right: 20.0)
|
let sourceMessageTextInsets = UIEdgeInsets(top: 7.0, left: 12.0, bottom: 6.0, right: 20.0)
|
||||||
@ -605,7 +650,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
let messageItemSize = messageItemView.update(
|
let messageItemSize = messageItemView.update(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
backgroundNode: component.wallpaperBackgroundNode,
|
backgroundNode: wallpaperBackgroundNode,
|
||||||
textString: textString,
|
textString: textString,
|
||||||
sourceTextInputView: component.textInputView as? ChatInputTextView,
|
sourceTextInputView: component.textInputView as? ChatInputTextView,
|
||||||
emojiViewProvider: component.emojiViewProvider,
|
emojiViewProvider: component.emojiViewProvider,
|
||||||
@ -882,13 +927,23 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
guard let self, let component = self.component else {
|
guard let self, let component = self.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let controller = self.environment?.controller() {
|
||||||
|
controller.forEachController { c in
|
||||||
|
if let c = c as? UndoOverlayController {
|
||||||
|
c.dismiss()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
let presentationData = component.updatedPresentationData?.initial ?? component.context.sharedContext.currentPresentationData.with({ $0 })
|
let presentationData = component.updatedPresentationData?.initial ?? component.context.sharedContext.currentPresentationData.with({ $0 })
|
||||||
self.environment?.controller()?.present(UndoOverlayController(
|
self.environment?.controller()?.present(UndoOverlayController(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
content: .premiumPaywall(
|
content: .premiumPaywall(
|
||||||
title: nil,
|
title: nil,
|
||||||
text: "Subscribe to [TelegramPremium]() to add this animated effect.",
|
text: "Subscribe to [Telegram Premium]() to add this animated effect.",
|
||||||
customUndoText: nil,
|
customUndoText: nil,
|
||||||
timeout: nil,
|
timeout: nil,
|
||||||
linkAction: nil
|
linkAction: nil
|
||||||
@ -900,11 +955,12 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
if case .info = action {
|
if case .info = action {
|
||||||
self.window?.endEditing(true)
|
self.window?.endEditing(true)
|
||||||
|
self.animateOutToEmpty = true
|
||||||
|
self.environment?.controller()?.dismiss()
|
||||||
|
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
let premiumController = component.context.sharedContext.makePremiumIntroController(context: component.context, source: .animatedEmoji, forceDark: false, dismissed: nil)
|
let premiumController = component.context.sharedContext.makePremiumIntroController(context: component.context, source: .animatedEmoji, forceDark: false, dismissed: nil)
|
||||||
let _ = premiumController
|
component.openPremiumPaywall(premiumController)
|
||||||
//parentNavigationController.pushViewController(premiumController)
|
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -922,7 +978,10 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
let sendButtonSize = CGSize(width: min(sourceSendButtonFrame.width, 44.0), height: sourceSendButtonFrame.height)
|
let sendButtonSize = CGSize(width: min(sourceSendButtonFrame.width, 44.0), height: sourceSendButtonFrame.height)
|
||||||
var readySendButtonFrame = CGRect(origin: CGPoint(x: sourceSendButtonFrame.maxX - sendButtonSize.width, y: sourceSendButtonFrame.minY), size: sendButtonSize)
|
var readySendButtonFrame = CGRect(origin: CGPoint(x: sourceSendButtonFrame.maxX - sendButtonSize.width, y: sourceSendButtonFrame.minY), size: sendButtonSize)
|
||||||
|
|
||||||
let sourceActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - actionsStackSize.width, y: sourceMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize)
|
var sourceActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - actionsStackSize.width, y: sourceMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize)
|
||||||
|
if !isMessageVisible {
|
||||||
|
sourceActionsStackFrame.origin.y = sourceSendButtonFrame.maxY - sourceActionsStackFrame.height - 5.0
|
||||||
|
}
|
||||||
|
|
||||||
var readyMessageItemFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 8.0 - messageItemSize.width, y: readySendButtonFrame.maxY - 6.0 - messageItemSize.height), size: messageItemSize)
|
var readyMessageItemFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 8.0 - messageItemSize.width, y: readySendButtonFrame.maxY - 6.0 - messageItemSize.height), size: messageItemSize)
|
||||||
if let mediaPreview = component.mediaPreview {
|
if let mediaPreview = component.mediaPreview {
|
||||||
@ -935,6 +994,9 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var readyActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - actionsStackSize.width, y: readyMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize)
|
var readyActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - actionsStackSize.width, y: readyMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize)
|
||||||
|
if !isMessageVisible {
|
||||||
|
readyActionsStackFrame.origin.y = readySendButtonFrame.maxY - readyActionsStackFrame.height - 5.0
|
||||||
|
}
|
||||||
|
|
||||||
let bottomOverflow = readyActionsStackFrame.maxY - (availableSize.height - environment.safeInsets.bottom)
|
let bottomOverflow = readyActionsStackFrame.maxY - (availableSize.height - environment.safeInsets.bottom)
|
||||||
if bottomOverflow > 0.0 {
|
if bottomOverflow > 0.0 {
|
||||||
@ -1002,6 +1064,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
transition.setFrame(view: messageItemView, frame: messageItemFrame)
|
transition.setFrame(view: messageItemView, frame: messageItemFrame)
|
||||||
|
transition.setAlpha(view: messageItemView, alpha: isMessageVisible ? 1.0 : 0.0)
|
||||||
messageItemView.updateClippingRect(
|
messageItemView.updateClippingRect(
|
||||||
sourceMediaPreview: component.mediaPreview,
|
sourceMediaPreview: component.mediaPreview,
|
||||||
isAnimatedIn: self.presentationAnimationState.key == .animatedIn,
|
isAnimatedIn: self.presentationAnimationState.key == .animatedIn,
|
||||||
@ -1010,7 +1073,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
transition: transition
|
transition: transition
|
||||||
)
|
)
|
||||||
|
|
||||||
transition.setPosition(view: actionsStackNode.view, position: CGPoint(x: actionsStackFrame.maxX, y: actionsStackFrame.minY))
|
transition.setPosition(view: actionsStackNode.view, position: CGPoint(x: actionsStackFrame.minX + actionsStackNode.layer.anchorPoint.x * actionsStackFrame.width, y: actionsStackFrame.minY + actionsStackNode.layer.anchorPoint.y * actionsStackFrame.height))
|
||||||
transition.setBounds(view: actionsStackNode.view, bounds: CGRect(origin: CGPoint(), size: actionsStackFrame.size))
|
transition.setBounds(view: actionsStackNode.view, bounds: CGRect(origin: CGPoint(), size: actionsStackFrame.size))
|
||||||
if !transition.animation.isImmediate && previousAnimationState.key != self.presentationAnimationState.key {
|
if !transition.animation.isImmediate && previousAnimationState.key != self.presentationAnimationState.key {
|
||||||
switch self.presentationAnimationState {
|
switch self.presentationAnimationState {
|
||||||
@ -1155,7 +1218,7 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
component.sourceSendButton.isHidden = false
|
component.sourceSendButton.isHidden = false
|
||||||
|
|
||||||
transition.setAlpha(view: sendButton, alpha: 0.0)
|
transition.setAlpha(view: sendButton, alpha: 0.0)
|
||||||
if let messageItemView = self.messageItemView {
|
if let messageItemView = self.messageItemView, isMessageVisible {
|
||||||
transition.setAlpha(view: messageItemView, alpha: 0.0)
|
transition.setAlpha(view: messageItemView, alpha: 0.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1168,13 +1231,16 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if case let .animatedOut(completion) = self.presentationAnimationState {
|
if case let .animatedOut(completion) = self.presentationAnimationState {
|
||||||
if let component = self.component, !self.animateOutToEmpty {
|
if !self.performedActionsOnAnimateOut {
|
||||||
if component.mediaPreview == nil {
|
self.performedActionsOnAnimateOut = true
|
||||||
component.textInputView.isHidden = false
|
if let component = self.component, !self.animateOutToEmpty {
|
||||||
|
if component.mediaPreview == nil {
|
||||||
|
component.textInputView.isHidden = false
|
||||||
|
}
|
||||||
|
component.sourceSendButton.isHidden = false
|
||||||
}
|
}
|
||||||
component.sourceSendButton.isHidden = false
|
completion()
|
||||||
}
|
}
|
||||||
completion()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1226,6 +1292,7 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
|
|||||||
completion: @escaping () -> Void,
|
completion: @escaping () -> Void,
|
||||||
sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void,
|
sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void,
|
||||||
schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void,
|
schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void,
|
||||||
|
openPremiumPaywall: @escaping (ViewController) -> Void,
|
||||||
reactionItems: [ReactionItem]?,
|
reactionItems: [ReactionItem]?,
|
||||||
availableMessageEffects: AvailableMessageEffects?,
|
availableMessageEffects: AvailableMessageEffects?,
|
||||||
isPremium: Bool
|
isPremium: Bool
|
||||||
@ -1253,6 +1320,7 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
|
|||||||
completion: completion,
|
completion: completion,
|
||||||
sendMessage: sendMessage,
|
sendMessage: sendMessage,
|
||||||
schedule: schedule,
|
schedule: schedule,
|
||||||
|
openPremiumPaywall: openPremiumPaywall,
|
||||||
reactionItems: reactionItems,
|
reactionItems: reactionItems,
|
||||||
availableMessageEffects: availableMessageEffects,
|
availableMessageEffects: availableMessageEffects,
|
||||||
isPremium: isPremium
|
isPremium: isPremium
|
||||||
|
@ -574,7 +574,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
|
|
||||||
modalStyleOverlayTransitionFactor = max(modalStyleOverlayTransitionFactor, overlayContainer.controller.modalStyleOverlayTransitionFactor)
|
modalStyleOverlayTransitionFactor = max(modalStyleOverlayTransitionFactor, overlayContainer.controller.modalStyleOverlayTransitionFactor)
|
||||||
|
|
||||||
if overlayContainer.isReady {
|
if overlayContainer.isReady && !overlayContainer.isRemoved {
|
||||||
let wasNotAdded = overlayContainer.supernode == nil
|
let wasNotAdded = overlayContainer.supernode == nil
|
||||||
|
|
||||||
if overlayWantsToBeBelowKeyboard {
|
if overlayWantsToBeBelowKeyboard {
|
||||||
@ -1529,26 +1529,26 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if inGlobal {
|
|
||||||
for i in 0 ..< strongSelf.globalOverlayContainers.count {
|
for i in 0 ..< strongSelf.globalOverlayContainers.count {
|
||||||
let overlayContainer = strongSelf.globalOverlayContainers[i]
|
let overlayContainer = strongSelf.globalOverlayContainers[i]
|
||||||
if overlayContainer.controller === controller {
|
if overlayContainer.controller === controller {
|
||||||
overlayContainer.removeFromSupernode()
|
overlayContainer.isRemoved = true
|
||||||
strongSelf.globalOverlayContainers.remove(at: i)
|
overlayContainer.removeFromSupernode()
|
||||||
strongSelf.internalGlobalOverlayControllersUpdated()
|
strongSelf.globalOverlayContainers.remove(at: i)
|
||||||
break
|
strongSelf.internalGlobalOverlayControllersUpdated()
|
||||||
}
|
break
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
for i in 0 ..< strongSelf.overlayContainers.count {
|
for i in 0 ..< strongSelf.overlayContainers.count {
|
||||||
let overlayContainer = strongSelf.overlayContainers[i]
|
let overlayContainer = strongSelf.overlayContainers[i]
|
||||||
if overlayContainer.controller === controller {
|
if overlayContainer.controller === controller {
|
||||||
overlayContainer.removeFromSupernode()
|
overlayContainer.isRemoved = true
|
||||||
strongSelf.overlayContainers.remove(at: i)
|
overlayContainer.removeFromSupernode()
|
||||||
strongSelf._overlayControllersPromise.set(strongSelf.overlayContainers.map({ $0.controller }))
|
strongSelf.overlayContainers.remove(at: i)
|
||||||
strongSelf.internalOverlayControllersUpdated()
|
strongSelf._overlayControllersPromise.set(strongSelf.overlayContainers.map({ $0.controller }))
|
||||||
break
|
strongSelf.internalOverlayControllersUpdated()
|
||||||
}
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ final class NavigationOverlayContainer: ASDisplayNode {
|
|||||||
let blocksInteractionUntilReady: Bool
|
let blocksInteractionUntilReady: Bool
|
||||||
|
|
||||||
private(set) var isReady: Bool = false
|
private(set) var isReady: Bool = false
|
||||||
|
var isRemoved: Bool = false
|
||||||
var isReadyUpdated: (() -> Void)?
|
var isReadyUpdated: (() -> Void)?
|
||||||
private var isReadyDisposable: Disposable?
|
private var isReadyDisposable: Disposable?
|
||||||
|
|
||||||
|
@ -461,7 +461,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
if case .media = controller.subject {
|
if case .media = controller.subject {
|
||||||
persistentItems = true
|
persistentItems = true
|
||||||
}
|
}
|
||||||
let previewNode = MediaPickerSelectedListNode(context: controller.context, persistentItems: persistentItems, isExternalPreview: true)
|
var isObscuredExternalPreview = false
|
||||||
|
if case .selected = self.currentDisplayMode {
|
||||||
|
isObscuredExternalPreview = true
|
||||||
|
}
|
||||||
|
let previewNode = MediaPickerSelectedListNode(context: controller.context, persistentItems: persistentItems, isExternalPreview: true, isObscuredExternalPreview: isObscuredExternalPreview)
|
||||||
let clippingRect = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: layout.size.width, height: max(0.0, layout.size.height - navigationHeight - layout.intrinsicInsets.bottom - layout.additionalInsets.bottom - 1.0)))
|
let clippingRect = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: layout.size.width, height: max(0.0, layout.size.height - navigationHeight - layout.intrinsicInsets.bottom - layout.additionalInsets.bottom - 1.0)))
|
||||||
previewNode.globalClippingRect = self.view.convert(clippingRect, to: nil)
|
previewNode.globalClippingRect = self.view.convert(clippingRect, to: nil)
|
||||||
previewNode.interaction = self.controller?.interaction
|
previewNode.interaction = self.controller?.interaction
|
||||||
@ -1000,7 +1004,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
persistentItems = true
|
persistentItems = true
|
||||||
}
|
}
|
||||||
|
|
||||||
let selectionNode = MediaPickerSelectedListNode(context: controller.context, persistentItems: persistentItems, isExternalPreview: false)
|
let selectionNode = MediaPickerSelectedListNode(context: controller.context, persistentItems: persistentItems, isExternalPreview: false, isObscuredExternalPreview: false)
|
||||||
selectionNode.alpha = animated ? 0.0 : 1.0
|
selectionNode.alpha = animated ? 0.0 : 1.0
|
||||||
selectionNode.layer.allowsGroupOpacity = true
|
selectionNode.layer.allowsGroupOpacity = true
|
||||||
selectionNode.isUserInteractionEnabled = false
|
selectionNode.isUserInteractionEnabled = false
|
||||||
|
@ -510,6 +510,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
|
|||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let persistentItems: Bool
|
private let persistentItems: Bool
|
||||||
private let isExternalPreview: Bool
|
private let isExternalPreview: Bool
|
||||||
|
private let isObscuredExternalPreview: Bool
|
||||||
var globalClippingRect: CGRect?
|
var globalClippingRect: CGRect?
|
||||||
var layoutType: ChatSendMessageContextScreenMediaPreviewLayoutType {
|
var layoutType: ChatSendMessageContextScreenMediaPreviewLayoutType {
|
||||||
return .media
|
return .media
|
||||||
@ -539,10 +540,11 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
|
|||||||
return self.ready.get()
|
return self.ready.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(context: AccountContext, persistentItems: Bool, isExternalPreview: Bool) {
|
init(context: AccountContext, persistentItems: Bool, isExternalPreview: Bool, isObscuredExternalPreview: Bool) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.persistentItems = persistentItems
|
self.persistentItems = persistentItems
|
||||||
self.isExternalPreview = isExternalPreview
|
self.isExternalPreview = isExternalPreview
|
||||||
|
self.isObscuredExternalPreview = isObscuredExternalPreview
|
||||||
|
|
||||||
self.scrollNode = ASScrollNode()
|
self.scrollNode = ASScrollNode()
|
||||||
self.scrollNode.clipsToBounds = false
|
self.scrollNode.clipsToBounds = false
|
||||||
@ -633,7 +635,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (identifier, itemNode) in strongSelf.itemNodes {
|
for (identifier, itemNode) in strongSelf.itemNodes {
|
||||||
if let (transitionView, _, _) = strongSelf.getTransitionView(identifier) {
|
if !strongSelf.isObscuredExternalPreview, let (transitionView, _, _) = strongSelf.getTransitionView(identifier) {
|
||||||
itemNode.animateFrom(transitionView, transition: transition)
|
itemNode.animateFrom(transitionView, transition: transition)
|
||||||
} else {
|
} else {
|
||||||
if strongSelf.isExternalPreview {
|
if strongSelf.isExternalPreview {
|
||||||
@ -694,7 +696,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (identifier, itemNode) in self.itemNodes {
|
for (identifier, itemNode) in self.itemNodes {
|
||||||
if let (transitionView, maybeDustNode, completion) = self.getTransitionView(identifier) {
|
if !self.isObscuredExternalPreview, let (transitionView, maybeDustNode, completion) = self.getTransitionView(identifier) {
|
||||||
itemNode.animateTo(transitionView, dustNode: maybeDustNode, transition: transition, completion: completion)
|
itemNode.animateTo(transitionView, dustNode: maybeDustNode, transition: transition, completion: completion)
|
||||||
} else {
|
} else {
|
||||||
if self.isExternalPreview {
|
if self.isExternalPreview {
|
||||||
|
@ -1844,9 +1844,11 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return context.availableMessageEffects
|
return combineLatest(
|
||||||
|> take(1)
|
context.availableMessageEffects |> take(1),
|
||||||
|> mapToSignal { availableMessageEffects -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
|
hasPremium |> take(1)
|
||||||
|
)
|
||||||
|
|> mapToSignal { availableMessageEffects, hasPremium -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
|
||||||
guard let availableMessageEffects else {
|
guard let availableMessageEffects else {
|
||||||
return .single([])
|
return .single([])
|
||||||
}
|
}
|
||||||
@ -1895,13 +1897,30 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
tintMode = .primary
|
tintMode = .primary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let icon: EmojiPagerContentComponent.Item.Icon
|
||||||
|
if i == 0 {
|
||||||
|
if !hasPremium && item.isPremium {
|
||||||
|
icon = .locked
|
||||||
|
} else {
|
||||||
|
icon = .none
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !hasPremium && item.isPremium {
|
||||||
|
icon = .locked
|
||||||
|
} else if let staticIcon = item.staticIcon {
|
||||||
|
icon = .customFile(staticIcon)
|
||||||
|
} else {
|
||||||
|
icon = .text(item.emoticon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let animationData = EntityKeyboardAnimationData(file: itemFile, partialReference: .none)
|
let animationData = EntityKeyboardAnimationData(file: itemFile, partialReference: .none)
|
||||||
let resultItem = EmojiPagerContentComponent.Item(
|
let resultItem = EmojiPagerContentComponent.Item(
|
||||||
animationData: animationData,
|
animationData: animationData,
|
||||||
content: .animation(animationData),
|
content: .animation(animationData),
|
||||||
itemFile: itemFile,
|
itemFile: itemFile,
|
||||||
subgroupId: nil,
|
subgroupId: nil,
|
||||||
icon: .none,
|
icon: icon,
|
||||||
tintMode: tintMode
|
tintMode: tintMode
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2160,6 +2179,15 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
let context = self.context
|
let context = self.context
|
||||||
let resultSignal: Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError>
|
let resultSignal: Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError>
|
||||||
if self.isMessageEffects {
|
if self.isMessageEffects {
|
||||||
|
let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||||
|
|> map { peer -> Bool in
|
||||||
|
guard case let .user(user) = peer else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return user.isPremium
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
|
|
||||||
let keywords: Signal<[String], NoError> = .single(value.identifiers)
|
let keywords: Signal<[String], NoError> = .single(value.identifiers)
|
||||||
resultSignal = keywords
|
resultSignal = keywords
|
||||||
|> mapToSignal { keywords -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in
|
|> mapToSignal { keywords -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in
|
||||||
@ -2168,9 +2196,11 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
allEmoticons[keyword] = keyword
|
allEmoticons[keyword] = keyword
|
||||||
}
|
}
|
||||||
|
|
||||||
return context.availableMessageEffects
|
return combineLatest(
|
||||||
|> take(1)
|
context.availableMessageEffects |> take(1),
|
||||||
|> mapToSignal { availableMessageEffects -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in
|
hasPremium |> take(1)
|
||||||
|
)
|
||||||
|
|> mapToSignal { availableMessageEffects, hasPremium -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in
|
||||||
guard let availableMessageEffects else {
|
guard let availableMessageEffects else {
|
||||||
return .single(([], true))
|
return .single(([], true))
|
||||||
}
|
}
|
||||||
@ -2219,13 +2249,30 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
tintMode = .primary
|
tintMode = .primary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let icon: EmojiPagerContentComponent.Item.Icon
|
||||||
|
if i == 0 {
|
||||||
|
if !hasPremium && item.isPremium {
|
||||||
|
icon = .locked
|
||||||
|
} else {
|
||||||
|
icon = .none
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !hasPremium && item.isPremium {
|
||||||
|
icon = .locked
|
||||||
|
} else if let staticIcon = item.staticIcon {
|
||||||
|
icon = .customFile(staticIcon)
|
||||||
|
} else {
|
||||||
|
icon = .text(item.emoticon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let animationData = EntityKeyboardAnimationData(file: itemFile, partialReference: .none)
|
let animationData = EntityKeyboardAnimationData(file: itemFile, partialReference: .none)
|
||||||
let resultItem = EmojiPagerContentComponent.Item(
|
let resultItem = EmojiPagerContentComponent.Item(
|
||||||
animationData: animationData,
|
animationData: animationData,
|
||||||
content: .animation(animationData),
|
content: .animation(animationData),
|
||||||
itemFile: itemFile,
|
itemFile: itemFile,
|
||||||
subgroupId: nil,
|
subgroupId: nil,
|
||||||
icon: .none,
|
icon: icon,
|
||||||
tintMode: tintMode
|
tintMode: tintMode
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -371,7 +371,6 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode",
|
"//submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode",
|
||||||
"//submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode",
|
"//submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode",
|
||||||
"//submodules/TelegramUI/Components/Chat/ChatMessageItem",
|
"//submodules/TelegramUI/Components/Chat/ChatMessageItem",
|
||||||
"//submodules/TelegramUI/Components/Chat/ChatMessageItemView",
|
|
||||||
"//submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode",
|
"//submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode",
|
||||||
"//submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode",
|
"//submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode",
|
||||||
"//submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode",
|
"//submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode",
|
||||||
|
@ -141,6 +141,8 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
private var wasPending: Bool = false
|
private var wasPending: Bool = false
|
||||||
private var didChangeFromPendingToSent: Bool = false
|
private var didChangeFromPendingToSent: Bool = false
|
||||||
|
|
||||||
|
private var fetchEffectDisposable: Disposable?
|
||||||
|
|
||||||
required public init(rotated: Bool) {
|
required public init(rotated: Bool) {
|
||||||
self.contextSourceNode = ContextExtractedContentContainingNode()
|
self.contextSourceNode = ContextExtractedContentContainingNode()
|
||||||
self.containerNode = ContextControllerSourceNode()
|
self.containerNode = ContextControllerSourceNode()
|
||||||
@ -593,7 +595,9 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
let isPlaying = self.visibilityStatus == true && !self.forceStopAnimations
|
let isPlaying = self.visibilityStatus == true && !self.forceStopAnimations
|
||||||
if !isPlaying {
|
if !isPlaying {
|
||||||
self.removeAdditionalAnimations()
|
self.removeAdditionalAnimations()
|
||||||
|
self.removeEffectAnimations()
|
||||||
}
|
}
|
||||||
|
|
||||||
if let animationNode = self.animationNode as? AnimatedStickerNode {
|
if let animationNode = self.animationNode as? AnimatedStickerNode {
|
||||||
if self.isPlaying != isPlaying || (isPlaying && !self.didSetUpAnimationNode) {
|
if self.isPlaying != isPlaying || (isPlaying && !self.didSetUpAnimationNode) {
|
||||||
self.isPlaying = isPlaying
|
self.isPlaying = isPlaying
|
||||||
@ -623,6 +627,23 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isPlaying, let animationNode = self.animationNode as? AnimatedStickerNode {
|
if isPlaying, let animationNode = self.animationNode as? AnimatedStickerNode {
|
||||||
|
var effectAlreadySeen = true
|
||||||
|
if item.message.flags.contains(.Incoming) {
|
||||||
|
if let unreadRange = item.controllerInteraction.unreadMessageRange[UnreadMessageRangeKey(peerId: item.message.id.peerId, namespace: item.message.id.namespace)] {
|
||||||
|
if unreadRange.contains(item.message.id.id) {
|
||||||
|
if !item.controllerInteraction.seenOneTimeAnimatedMedia.contains(item.message.id) {
|
||||||
|
effectAlreadySeen = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if self.didChangeFromPendingToSent {
|
||||||
|
if !item.controllerInteraction.seenOneTimeAnimatedMedia.contains(item.message.id) {
|
||||||
|
effectAlreadySeen = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var alreadySeen = true
|
var alreadySeen = true
|
||||||
if isEmoji && self.emojiString == nil {
|
if isEmoji && self.emojiString == nil {
|
||||||
if !item.controllerInteraction.seenOneTimeAnimatedMedia.contains(item.message.id) {
|
if !item.controllerInteraction.seenOneTimeAnimatedMedia.contains(item.message.id) {
|
||||||
@ -659,6 +680,10 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
animationNode.playOnce()
|
animationNode.playOnce()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !effectAlreadySeen {
|
||||||
|
self.playMessageEffect(force: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1030,6 +1055,8 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
isReplyThread = true
|
isReplyThread = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let messageEffect = item.message.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects)
|
||||||
|
|
||||||
let statusSuggestedWidthAndContinue = makeDateAndStatusLayout(ChatMessageDateAndStatusNode.Arguments(
|
let statusSuggestedWidthAndContinue = makeDateAndStatusLayout(ChatMessageDateAndStatusNode.Arguments(
|
||||||
context: item.context,
|
context: item.context,
|
||||||
presentationData: item.presentationData,
|
presentationData: item.presentationData,
|
||||||
@ -1045,7 +1072,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
reactionPeers: dateReactionsAndPeers.peers,
|
reactionPeers: dateReactionsAndPeers.peers,
|
||||||
displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser,
|
displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser,
|
||||||
areReactionsTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId),
|
areReactionsTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId),
|
||||||
messageEffect: item.message.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
|
messageEffect: messageEffect,
|
||||||
replyCount: dateReplies,
|
replyCount: dateReplies,
|
||||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
||||||
hasAutoremove: item.message.isSelfExpiring,
|
hasAutoremove: item.message.isSelfExpiring,
|
||||||
@ -1757,6 +1784,13 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.dateAndStatusNode)
|
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.dateAndStatusNode)
|
||||||
}
|
}
|
||||||
|
} else if messageEffect != nil {
|
||||||
|
strongSelf.dateAndStatusNode.pressed = {
|
||||||
|
guard let strongSelf = weakSelf.value, let item = strongSelf.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item.controllerInteraction.playMessageEffect(item.message)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
strongSelf.dateAndStatusNode.pressed = nil
|
strongSelf.dateAndStatusNode.pressed = nil
|
||||||
}
|
}
|
||||||
@ -2987,6 +3021,14 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
return (image, self.imageNode.frame)
|
return (image, self.imageNode.frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override public func messageEffectTargetView() -> UIView? {
|
||||||
|
if let result = self.dateAndStatusNode.messageEffectTargetView() {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct AnimatedEmojiSoundsConfiguration {
|
public struct AnimatedEmojiSoundsConfiguration {
|
||||||
|
@ -638,10 +638,6 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
private var replyRecognizer: ChatSwipeToReplyRecognizer?
|
private var replyRecognizer: ChatSwipeToReplyRecognizer?
|
||||||
private var currentSwipeAction: ChatControllerInteractionSwipeAction?
|
private var currentSwipeAction: ChatControllerInteractionSwipeAction?
|
||||||
|
|
||||||
private var fetchEffectDisposable: Disposable?
|
|
||||||
|
|
||||||
//private let debugNode: ASDisplayNode
|
|
||||||
|
|
||||||
override public var visibility: ListViewItemNodeVisibility {
|
override public var visibility: ListViewItemNodeVisibility {
|
||||||
didSet {
|
didSet {
|
||||||
if self.visibility != oldValue {
|
if self.visibility != oldValue {
|
||||||
@ -842,7 +838,6 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.fetchEffectDisposable?.dispose()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func cancelInsertionAnimations() {
|
override public func cancelInsertionAnimations() {
|
||||||
@ -5878,172 +5873,6 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private var forceStopAnimations: Bool = false
|
|
||||||
private var playedPremiumStickerAnimation: Bool = false
|
|
||||||
private var additionalAnimationNodes: [ChatMessageTransitionNode.DecorationItemNode] = []
|
|
||||||
|
|
||||||
private func playPremiumStickerAnimation(effect: AvailableMessageEffects.MessageEffect, force: Bool) {
|
|
||||||
guard let item = self.item else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if self.playedPremiumStickerAnimation && !force {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.playedPremiumStickerAnimation = true
|
|
||||||
|
|
||||||
if let effectAnimation = effect.effectAnimation {
|
|
||||||
self.playEffectAnimation(resource: effectAnimation.resource, isStickerEffect: true)
|
|
||||||
if self.fetchEffectDisposable == nil {
|
|
||||||
self.fetchEffectDisposable = freeMediaFileResourceInteractiveFetched(account: item.context.account, userLocation: .other, fileReference: .standalone(media: effectAnimation), resource: effectAnimation.resource).startStrict()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let effectSticker = effect.effectSticker
|
|
||||||
if let effectFile = effectSticker.videoThumbnails.first {
|
|
||||||
self.playEffectAnimation(resource: effectFile.resource, isStickerEffect: true)
|
|
||||||
if self.fetchEffectDisposable == nil {
|
|
||||||
self.fetchEffectDisposable = freeMediaFileResourceInteractiveFetched(account: item.context.account, userLocation: .other, fileReference: .standalone(media: effectSticker), resource: effectFile.resource).startStrict()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func playEffectAnimation(resource: MediaResource, isStickerEffect: Bool = false) {
|
|
||||||
guard let item = self.item else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let transitionNode = item.controllerInteraction.getMessageTransitionNode() as? ChatMessageTransitionNode else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let source = AnimatedStickerResourceSource(account: item.context.account, resource: resource, fitzModifier: nil)
|
|
||||||
|
|
||||||
let animationSize = CGSize(width: 380.0, height: 380.0)
|
|
||||||
let animationNodeFrame: CGRect
|
|
||||||
|
|
||||||
var messageEffectView: UIView?
|
|
||||||
for contentNode in self.contentNodes {
|
|
||||||
if let result = contentNode.messageEffectTargetView() {
|
|
||||||
messageEffectView = result
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if messageEffectView == nil {
|
|
||||||
if let mosaicStatusNode = self.mosaicStatusNode, let result = mosaicStatusNode.messageEffectTargetView() {
|
|
||||||
messageEffectView = result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let messageEffectView {
|
|
||||||
animationNodeFrame = animationSize.centered(around: messageEffectView.convert(messageEffectView.bounds, to: self.view).center)
|
|
||||||
} else {
|
|
||||||
animationNodeFrame = animationSize.centered(around: self.backgroundNode.frame.center)
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.additionalAnimationNodes.count >= 4 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let incomingMessage = item.message.effectivelyIncoming(item.context.account.peerId)
|
|
||||||
|
|
||||||
do {
|
|
||||||
let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(resource.id)
|
|
||||||
|
|
||||||
let additionalAnimationNode: AnimatedStickerNode
|
|
||||||
var effectiveScale: CGFloat = 1.0
|
|
||||||
#if targetEnvironment(simulator)
|
|
||||||
additionalAnimationNode = DirectAnimatedStickerNode()
|
|
||||||
effectiveScale = 1.4
|
|
||||||
#else
|
|
||||||
if "".isEmpty {
|
|
||||||
additionalAnimationNode = DirectAnimatedStickerNode()
|
|
||||||
effectiveScale = 1.4
|
|
||||||
} else {
|
|
||||||
additionalAnimationNode = LottieMetalAnimatedStickerNode()
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
additionalAnimationNode.updateLayout(size: animationSize)
|
|
||||||
additionalAnimationNode.setup(source: source, width: Int(animationSize.width * effectiveScale), height: Int(animationSize.height * effectiveScale), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix))
|
|
||||||
var animationFrame: CGRect
|
|
||||||
if isStickerEffect {
|
|
||||||
let offsetScale: CGFloat = 0.3
|
|
||||||
animationFrame = animationNodeFrame.offsetBy(dx: incomingMessage ? animationNodeFrame.width * offsetScale : -animationNodeFrame.width * offsetScale, dy: -10.0)
|
|
||||||
} else {
|
|
||||||
animationFrame = animationNodeFrame.insetBy(dx: -animationNodeFrame.width, dy: -animationNodeFrame.height)
|
|
||||||
.offsetBy(dx: incomingMessage ? animationNodeFrame.width - 10.0 : -animationNodeFrame.width + 10.0, dy: 0.0)
|
|
||||||
animationFrame = animationFrame.offsetBy(dx: CGFloat.random(in: -30.0 ... 30.0), dy: CGFloat.random(in: -30.0 ... 30.0))
|
|
||||||
}
|
|
||||||
|
|
||||||
animationFrame = animationFrame.offsetBy(dx: 0.0, dy: self.insets.top)
|
|
||||||
additionalAnimationNode.frame = animationFrame
|
|
||||||
if incomingMessage {
|
|
||||||
additionalAnimationNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
let decorationNode = transitionNode.add(decorationView: additionalAnimationNode.view, itemNode: self)
|
|
||||||
additionalAnimationNode.completed = { [weak self, weak decorationNode, weak transitionNode] _ in
|
|
||||||
guard let decorationNode = decorationNode else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self?.additionalAnimationNodes.removeAll(where: { $0 === decorationNode })
|
|
||||||
transitionNode?.remove(decorationNode: decorationNode)
|
|
||||||
}
|
|
||||||
additionalAnimationNode.isPlayingChanged = { [weak self, weak decorationNode, weak transitionNode] isPlaying in
|
|
||||||
if !isPlaying {
|
|
||||||
guard let decorationNode = decorationNode else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self?.additionalAnimationNodes.removeAll(where: { $0 === decorationNode })
|
|
||||||
transitionNode?.remove(decorationNode: decorationNode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.additionalAnimationNodes.append(decorationNode)
|
|
||||||
|
|
||||||
additionalAnimationNode.visibility = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func removeAdditionalAnimations() {
|
|
||||||
for decorationNode in self.additionalAnimationNodes {
|
|
||||||
if let additionalAnimationNode = decorationNode.contentView.asyncdisplaykit_node as? AnimatedStickerNode {
|
|
||||||
additionalAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak additionalAnimationNode] _ in
|
|
||||||
additionalAnimationNode?.visibility = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func currentMessageEffect() -> AvailableMessageEffects.MessageEffect? {
|
|
||||||
guard let item = self.item else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var messageEffect: AvailableMessageEffects.MessageEffect?
|
|
||||||
for attribute in item.message.attributes {
|
|
||||||
if let attribute = attribute as? EffectMessageAttribute {
|
|
||||||
if let availableMessageEffects = item.associatedData.availableMessageEffects {
|
|
||||||
for effect in availableMessageEffects.messageEffects {
|
|
||||||
if effect.id == attribute.id {
|
|
||||||
messageEffect = effect
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return messageEffect
|
|
||||||
}
|
|
||||||
|
|
||||||
private func playMessageEffect(force: Bool) {
|
|
||||||
if let messageEffect = self.currentMessageEffect() {
|
|
||||||
self.playPremiumStickerAnimation(effect: messageEffect, force: force)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func playMessageEffect() {
|
|
||||||
self.playMessageEffect(force: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateVisibility() {
|
private func updateVisibility() {
|
||||||
guard let item = self.item else {
|
guard let item = self.item else {
|
||||||
return
|
return
|
||||||
@ -6058,12 +5887,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
isPlaying = false
|
isPlaying = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.forceStopAnimations {
|
|
||||||
isPlaying = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isPlaying {
|
if !isPlaying {
|
||||||
self.removeAdditionalAnimations()
|
self.removeEffectAnimations()
|
||||||
}
|
}
|
||||||
|
|
||||||
if isPlaying {
|
if isPlaying {
|
||||||
@ -6091,4 +5916,17 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override public func messageEffectTargetView() -> UIView? {
|
||||||
|
for contentNode in self.contentNodes {
|
||||||
|
if let result = contentNode.messageEffectTargetView() {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let mosaicStatusNode = self.mosaicStatusNode, let result = mosaicStatusNode.messageEffectTargetView() {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,8 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon",
|
"//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon",
|
||||||
"//submodules/TextFormat",
|
"//submodules/TextFormat",
|
||||||
"//submodules/TelegramUI/Components/Chat/ChatMessageItem",
|
"//submodules/TelegramUI/Components/Chat/ChatMessageItem",
|
||||||
|
"//submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode",
|
||||||
|
"//submodules/TelegramAnimatedStickerNode",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -14,6 +14,10 @@ import ChatControllerInteraction
|
|||||||
import ChatMessageItemCommon
|
import ChatMessageItemCommon
|
||||||
import TextFormat
|
import TextFormat
|
||||||
import ChatMessageItem
|
import ChatMessageItem
|
||||||
|
import ChatMessageTransitionNode
|
||||||
|
import AnimatedStickerNode
|
||||||
|
import TelegramAnimatedStickerNode
|
||||||
|
import LottieMetal
|
||||||
|
|
||||||
public func chatMessageItemLayoutConstants(_ constants: (ChatMessageItemLayoutConstants, ChatMessageItemLayoutConstants), params: ListViewItemLayoutParams, presentationData: ChatPresentationData) -> ChatMessageItemLayoutConstants {
|
public func chatMessageItemLayoutConstants(_ constants: (ChatMessageItemLayoutConstants, ChatMessageItemLayoutConstants), params: ListViewItemLayoutParams, presentationData: ChatPresentationData) -> ChatMessageItemLayoutConstants {
|
||||||
var result: ChatMessageItemLayoutConstants
|
var result: ChatMessageItemLayoutConstants
|
||||||
@ -653,6 +657,11 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
|
|||||||
|
|
||||||
open var awaitingAppliedReaction: (MessageReaction.Reaction?, () -> Void)?
|
open var awaitingAppliedReaction: (MessageReaction.Reaction?, () -> Void)?
|
||||||
|
|
||||||
|
private var fetchEffectDisposable: Disposable?
|
||||||
|
|
||||||
|
public var playedEffectAnimation: Bool = false
|
||||||
|
public var effectAnimationNodes: [ChatMessageTransitionNode.DecorationItemNode] = []
|
||||||
|
|
||||||
public required init(rotated: Bool) {
|
public required init(rotated: Bool) {
|
||||||
super.init(layerBacked: false, dynamicBounce: true, rotated: rotated)
|
super.init(layerBacked: false, dynamicBounce: true, rotated: rotated)
|
||||||
if rotated {
|
if rotated {
|
||||||
@ -664,6 +673,10 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.fetchEffectDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
override open func reuse() {
|
override open func reuse() {
|
||||||
super.reuse()
|
super.reuse()
|
||||||
|
|
||||||
@ -710,6 +723,28 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func matchesMessage(id: MessageId) -> Bool {
|
||||||
|
if let item = self.item {
|
||||||
|
for (message, _) in item.content {
|
||||||
|
if message.id == id {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public func messages() -> [Message] {
|
||||||
|
guard let item = self.item else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
var messages: [Message] = []
|
||||||
|
for (message, _) in item.content {
|
||||||
|
messages.append(message)
|
||||||
|
}
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
|
||||||
open func transitionNode(id: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
|
open func transitionNode(id: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -888,6 +923,146 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
|
|||||||
return self.bounds
|
return self.bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
open func playMessageEffect() {
|
private func playEffectAnimation(effect: AvailableMessageEffects.MessageEffect, force: Bool) {
|
||||||
|
guard let item = self.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.playedEffectAnimation && !force {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.playedEffectAnimation = true
|
||||||
|
|
||||||
|
if let effectAnimation = effect.effectAnimation {
|
||||||
|
self.playEffectAnimation(resource: effectAnimation.resource)
|
||||||
|
if self.fetchEffectDisposable == nil {
|
||||||
|
self.fetchEffectDisposable = freeMediaFileResourceInteractiveFetched(account: item.context.account, userLocation: .other, fileReference: .standalone(media: effectAnimation), resource: effectAnimation.resource).startStrict()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let effectSticker = effect.effectSticker
|
||||||
|
if let effectFile = effectSticker.videoThumbnails.first {
|
||||||
|
self.playEffectAnimation(resource: effectFile.resource)
|
||||||
|
if self.fetchEffectDisposable == nil {
|
||||||
|
self.fetchEffectDisposable = freeMediaFileResourceInteractiveFetched(account: item.context.account, userLocation: .other, fileReference: .standalone(media: effectSticker), resource: effectFile.resource).startStrict()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open func messageEffectTargetView() -> UIView? {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func playEffectAnimation(resource: MediaResource) {
|
||||||
|
guard let item = self.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let transitionNode = item.controllerInteraction.getMessageTransitionNode() as? ChatMessageTransitionNode else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let source = AnimatedStickerResourceSource(account: item.context.account, resource: resource, fitzModifier: nil)
|
||||||
|
|
||||||
|
let animationSize = CGSize(width: 380.0, height: 380.0)
|
||||||
|
let animationNodeFrame: CGRect
|
||||||
|
|
||||||
|
guard let messageEffectView = self.messageEffectTargetView() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
animationNodeFrame = animationSize.centered(around: messageEffectView.convert(messageEffectView.bounds, to: self.view).center)
|
||||||
|
|
||||||
|
if self.effectAnimationNodes.count >= 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let incomingMessage = item.message.effectivelyIncoming(item.context.account.peerId)
|
||||||
|
|
||||||
|
do {
|
||||||
|
let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(resource.id)
|
||||||
|
|
||||||
|
let additionalAnimationNode: AnimatedStickerNode
|
||||||
|
var effectiveScale: CGFloat = 1.0
|
||||||
|
#if targetEnvironment(simulator)
|
||||||
|
additionalAnimationNode = DirectAnimatedStickerNode()
|
||||||
|
effectiveScale = 1.4
|
||||||
|
#else
|
||||||
|
if "".isEmpty {
|
||||||
|
additionalAnimationNode = DirectAnimatedStickerNode()
|
||||||
|
effectiveScale = 1.4
|
||||||
|
} else {
|
||||||
|
additionalAnimationNode = LottieMetalAnimatedStickerNode()
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
additionalAnimationNode.updateLayout(size: animationSize)
|
||||||
|
additionalAnimationNode.setup(source: source, width: Int(animationSize.width * effectiveScale), height: Int(animationSize.height * effectiveScale), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix))
|
||||||
|
var animationFrame: CGRect
|
||||||
|
let offsetScale: CGFloat = 0.3
|
||||||
|
animationFrame = animationNodeFrame.offsetBy(dx: incomingMessage ? animationNodeFrame.width * offsetScale : -animationNodeFrame.width * offsetScale, dy: -10.0)
|
||||||
|
|
||||||
|
animationFrame = animationFrame.offsetBy(dx: 0.0, dy: self.insets.top)
|
||||||
|
additionalAnimationNode.frame = animationFrame
|
||||||
|
if incomingMessage {
|
||||||
|
additionalAnimationNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
let decorationNode = transitionNode.add(decorationView: additionalAnimationNode.view, itemNode: self)
|
||||||
|
additionalAnimationNode.completed = { [weak self, weak decorationNode, weak transitionNode] _ in
|
||||||
|
guard let decorationNode = decorationNode else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self?.effectAnimationNodes.removeAll(where: { $0 === decorationNode })
|
||||||
|
transitionNode?.remove(decorationNode: decorationNode)
|
||||||
|
}
|
||||||
|
additionalAnimationNode.isPlayingChanged = { [weak self, weak decorationNode, weak transitionNode] isPlaying in
|
||||||
|
if !isPlaying {
|
||||||
|
guard let decorationNode = decorationNode else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self?.effectAnimationNodes.removeAll(where: { $0 === decorationNode })
|
||||||
|
transitionNode?.remove(decorationNode: decorationNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.effectAnimationNodes.append(decorationNode)
|
||||||
|
|
||||||
|
additionalAnimationNode.visibility = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func removeEffectAnimations() {
|
||||||
|
for decorationNode in self.effectAnimationNodes {
|
||||||
|
if let additionalAnimationNode = decorationNode.contentView.asyncdisplaykit_node as? AnimatedStickerNode {
|
||||||
|
additionalAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak additionalAnimationNode] _ in
|
||||||
|
additionalAnimationNode?.visibility = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func currentMessageEffect() -> AvailableMessageEffects.MessageEffect? {
|
||||||
|
guard let item = self.item else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var messageEffect: AvailableMessageEffects.MessageEffect?
|
||||||
|
for attribute in item.message.attributes {
|
||||||
|
if let attribute = attribute as? EffectMessageAttribute {
|
||||||
|
if let availableMessageEffects = item.associatedData.availableMessageEffects {
|
||||||
|
for effect in availableMessageEffects.messageEffects {
|
||||||
|
if effect.id == attribute.id {
|
||||||
|
messageEffect = effect
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return messageEffect
|
||||||
|
}
|
||||||
|
|
||||||
|
public func playMessageEffect(force: Bool) {
|
||||||
|
if let messageEffect = self.currentMessageEffect() {
|
||||||
|
self.playEffectAnimation(effect: messageEffect, force: force)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,6 +76,9 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
private var enableSynchronousImageApply: Bool = false
|
private var enableSynchronousImageApply: Bool = false
|
||||||
|
|
||||||
|
private var wasPending: Bool = false
|
||||||
|
private var didChangeFromPendingToSent: Bool = false
|
||||||
|
|
||||||
override public var visibility: ListViewItemNodeVisibility {
|
override public var visibility: ListViewItemNodeVisibility {
|
||||||
didSet {
|
didSet {
|
||||||
let wasVisible = oldValue != .none
|
let wasVisible = oldValue != .none
|
||||||
@ -92,6 +95,8 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
if self.visibilityStatus != oldValue {
|
if self.visibilityStatus != oldValue {
|
||||||
self.threadInfoNode?.visibility = self.visibilityStatus == true
|
self.threadInfoNode?.visibility = self.visibilityStatus == true
|
||||||
self.replyInfoNode?.visibility = self.visibilityStatus == true
|
self.replyInfoNode?.visibility = self.visibilityStatus == true
|
||||||
|
|
||||||
|
self.updateVisibility()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -280,6 +285,13 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
override public func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) {
|
override public func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) {
|
||||||
super.setupItem(item, synchronousLoad: synchronousLoad)
|
super.setupItem(item, synchronousLoad: synchronousLoad)
|
||||||
|
|
||||||
|
if item.message.id.namespace == Namespaces.Message.Local || item.message.id.namespace == Namespaces.Message.ScheduledLocal || item.message.id.namespace == Namespaces.Message.QuickReplyLocal {
|
||||||
|
self.wasPending = true
|
||||||
|
}
|
||||||
|
if self.wasPending && (item.message.id.namespace != Namespaces.Message.Local && item.message.id.namespace != Namespaces.Message.ScheduledLocal && item.message.id.namespace != Namespaces.Message.QuickReplyLocal) {
|
||||||
|
self.didChangeFromPendingToSent = true
|
||||||
|
}
|
||||||
|
|
||||||
self.replyRecognizer?.allowBothDirections = false//!item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
|
self.replyRecognizer?.allowBothDirections = false//!item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
|
||||||
if self.isNodeLoaded {
|
if self.isNodeLoaded {
|
||||||
self.view.disablesInteractiveTransitionGestureRecognizer = false//!item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
|
self.view.disablesInteractiveTransitionGestureRecognizer = false//!item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
|
||||||
@ -1352,6 +1364,8 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
f()
|
f()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strongSelf.updateVisibility()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2130,4 +2144,53 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
return (image, self.imageNode.frame)
|
return (image, self.imageNode.frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateVisibility() {
|
||||||
|
guard let item = self.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var isPlaying = true
|
||||||
|
if case .visible = self.visibility {
|
||||||
|
} else {
|
||||||
|
isPlaying = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isPlaying {
|
||||||
|
self.removeEffectAnimations()
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPlaying {
|
||||||
|
var alreadySeen = true
|
||||||
|
if item.message.flags.contains(.Incoming) {
|
||||||
|
if let unreadRange = item.controllerInteraction.unreadMessageRange[UnreadMessageRangeKey(peerId: item.message.id.peerId, namespace: item.message.id.namespace)] {
|
||||||
|
if unreadRange.contains(item.message.id.id) {
|
||||||
|
if !item.controllerInteraction.seenOneTimeAnimatedMedia.contains(item.message.id) {
|
||||||
|
alreadySeen = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if self.didChangeFromPendingToSent {
|
||||||
|
if !item.controllerInteraction.seenOneTimeAnimatedMedia.contains(item.message.id) {
|
||||||
|
alreadySeen = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !alreadySeen {
|
||||||
|
item.controllerInteraction.seenOneTimeAnimatedMedia.insert(item.message.id)
|
||||||
|
|
||||||
|
self.playMessageEffect(force: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func messageEffectTargetView() -> UIView? {
|
||||||
|
if let result = self.dateAndStatusNode.messageEffectTargetView() {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ swift_library(
|
|||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
"//submodules/AsyncDisplayKit",
|
"//submodules/AsyncDisplayKit",
|
||||||
"//submodules/TelegramUI/Components/Chat/ChatMessageItemView",
|
"//submodules/AccountContext",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import ChatMessageItemView
|
import AccountContext
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
|
|
||||||
public protocol ChatMessageTransitionNodeDecorationItemNode: ASDisplayNode {
|
public protocol ChatMessageTransitionNodeDecorationItemNode: ASDisplayNode {
|
||||||
@ -10,6 +10,6 @@ public protocol ChatMessageTransitionNodeDecorationItemNode: ASDisplayNode {
|
|||||||
public protocol ChatMessageTransitionNode: AnyObject {
|
public protocol ChatMessageTransitionNode: AnyObject {
|
||||||
typealias DecorationItemNode = ChatMessageTransitionNodeDecorationItemNode
|
typealias DecorationItemNode = ChatMessageTransitionNodeDecorationItemNode
|
||||||
|
|
||||||
func add(decorationView: UIView, itemNode: ChatMessageItemView) -> DecorationItemNode
|
func add(decorationView: UIView, itemNode: ChatMessageItemNodeProtocol) -> DecorationItemNode
|
||||||
func remove(decorationNode: DecorationItemNode)
|
func remove(decorationNode: DecorationItemNode)
|
||||||
}
|
}
|
||||||
|
@ -1384,6 +1384,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
|
|
||||||
private var component: EmojiPagerContentComponent?
|
private var component: EmojiPagerContentComponent?
|
||||||
private weak var state: EmptyComponentState?
|
private weak var state: EmptyComponentState?
|
||||||
|
private var isUpdating: Bool = false
|
||||||
private var pagerEnvironment: PagerComponentChildEnvironment?
|
private var pagerEnvironment: PagerComponentChildEnvironment?
|
||||||
private var keyboardChildEnvironment: EntityKeyboardChildEnvironment?
|
private var keyboardChildEnvironment: EntityKeyboardChildEnvironment?
|
||||||
private var activeItemUpdated: ActionSlot<(AnyHashable, AnyHashable?, Transition)>?
|
private var activeItemUpdated: ActionSlot<(AnyHashable, AnyHashable?, Transition)>?
|
||||||
@ -4055,6 +4056,11 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func update(component: EmojiPagerContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
func update(component: EmojiPagerContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||||
|
self.isUpdating = true
|
||||||
|
defer {
|
||||||
|
self.isUpdating = false
|
||||||
|
}
|
||||||
|
|
||||||
let previousComponent = self.component
|
let previousComponent = self.component
|
||||||
|
|
||||||
self.component = component
|
self.component = component
|
||||||
@ -4581,6 +4587,10 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !strongSelf.isUpdating {
|
||||||
|
strongSelf.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, updateQuery: { [weak self] query in
|
}, updateQuery: { [weak self] query in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
|
@ -213,6 +213,20 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
if let textField = self.textField, let text = textField.text, text.isEmpty {
|
||||||
|
if self.bounds.contains(point), let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View {
|
||||||
|
let leftTextPosition = placeholderContentView.leftTextPosition()
|
||||||
|
if point.x >= 0.0 && point.x <= placeholderContentView.frame.minX + leftTextPosition {
|
||||||
|
if let result = placeholderContentView.hitTest(self.convert(point, to: placeholderContentView), with: event) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.hitTest(point, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
private func activateTextInput() {
|
private func activateTextInput() {
|
||||||
guard let params = self.params else {
|
guard let params = self.params else {
|
||||||
return
|
return
|
||||||
@ -226,7 +240,11 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
textField.autocorrectionType = .no
|
textField.autocorrectionType = .no
|
||||||
textField.returnKeyType = .search
|
textField.returnKeyType = .search
|
||||||
self.textField = textField
|
self.textField = textField
|
||||||
self.insertSubview(textField, belowSubview: self.clearIconView)
|
if let placeholderContentView = self.placeholderContent.view {
|
||||||
|
self.insertSubview(textField, belowSubview: placeholderContentView)
|
||||||
|
} else {
|
||||||
|
self.insertSubview(textField, belowSubview: self.clearIconView)
|
||||||
|
}
|
||||||
textField.delegate = self
|
textField.delegate = self
|
||||||
textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
|
textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
|
||||||
}
|
}
|
||||||
@ -244,15 +262,15 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc private func cancelPressed() {
|
@objc private func cancelPressed() {
|
||||||
|
let textField = self.textField
|
||||||
|
self.textField = nil
|
||||||
|
|
||||||
self.currentPresetSearchTerm = nil
|
self.currentPresetSearchTerm = nil
|
||||||
self.updateQuery(nil)
|
self.updateQuery(nil)
|
||||||
|
|
||||||
self.clearIconView.isHidden = true
|
self.clearIconView.isHidden = true
|
||||||
self.clearIconTintView.isHidden = true
|
self.clearIconTintView.isHidden = true
|
||||||
self.clearIconButton.isHidden = true
|
self.clearIconButton.isHidden = true
|
||||||
|
|
||||||
let textField = self.textField
|
|
||||||
self.textField = nil
|
|
||||||
|
|
||||||
self.deactivated(textField?.isFirstResponder ?? false)
|
self.deactivated(textField?.isFirstResponder ?? false)
|
||||||
|
|
||||||
@ -601,7 +619,6 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
}
|
}
|
||||||
let _ = hasText
|
let _ = hasText
|
||||||
|
|
||||||
/*self.tintTextView.view?.isHidden = hasText
|
|
||||||
self.textView.view?.isHidden = hasText*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -429,6 +429,15 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
return super.hitTest(point, with: event)
|
return super.hitTest(point, with: event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func leftTextPosition() -> CGFloat {
|
||||||
|
guard let itemLayout = self.itemLayout else {
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
let visibleBounds = self.scrollView.bounds
|
||||||
|
return (itemLayout.itemStartX - itemLayout.textSpacing) + visibleBounds.minX
|
||||||
|
}
|
||||||
|
|
||||||
private func updateScrolling(transition: Transition, fromScrolling: Bool) {
|
private func updateScrolling(transition: Transition, fromScrolling: Bool) {
|
||||||
guard let component = self.component, let itemLayout = self.itemLayout else {
|
guard let component = self.component, let itemLayout = self.itemLayout else {
|
||||||
return
|
return
|
||||||
|
@ -703,6 +703,11 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}, schedule: { [weak textInputPanelNode] messageEffect in
|
}, schedule: { [weak textInputPanelNode] messageEffect in
|
||||||
textInputPanelNode?.sendMessage(.schedule, messageEffect)
|
textInputPanelNode?.sendMessage(.schedule, messageEffect)
|
||||||
|
}, openPremiumPaywall: { [weak controller] c in
|
||||||
|
guard let controller else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.push(c)
|
||||||
})
|
})
|
||||||
strongSelf.presentInGlobalOverlay(controller, nil)
|
strongSelf.presentInGlobalOverlay(controller, nil)
|
||||||
}, openScheduledMessages: {
|
}, openScheduledMessages: {
|
||||||
|
@ -2124,6 +2124,11 @@ extension ChatControllerImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var invertedMediaAttribute: InvertMediaMessageAttribute?
|
||||||
|
if let attribute = message.attributes.first(where: { $0 is InvertMediaMessageAttribute }) {
|
||||||
|
invertedMediaAttribute = attribute as? InvertMediaMessageAttribute
|
||||||
|
}
|
||||||
|
|
||||||
let text = trimChatInputText(convertMarkdownToAttributes(editMessage.inputState.inputText))
|
let text = trimChatInputText(convertMarkdownToAttributes(editMessage.inputState.inputText))
|
||||||
|
|
||||||
let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text))
|
let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text))
|
||||||
@ -2209,7 +2214,7 @@ extension ChatControllerImpl {
|
|||||||
let currentWebpagePreviewAttribute = currentMessage.webpagePreviewAttribute ?? WebpagePreviewMessageAttribute(leadingPreview: false, forceLargeMedia: nil, isManuallyAdded: true, isSafe: false)
|
let currentWebpagePreviewAttribute = currentMessage.webpagePreviewAttribute ?? WebpagePreviewMessageAttribute(leadingPreview: false, forceLargeMedia: nil, isManuallyAdded: true, isSafe: false)
|
||||||
|
|
||||||
if currentMessage.text != text.string || currentEntities != entities || updatingMedia || webpagePreviewAttribute != currentWebpagePreviewAttribute || disableUrlPreview {
|
if currentMessage.text != text.string || currentEntities != entities || updatingMedia || webpagePreviewAttribute != currentWebpagePreviewAttribute || disableUrlPreview {
|
||||||
strongSelf.context.account.pendingUpdateMessageManager.add(messageId: editMessage.messageId, text: text.string, media: media, entities: entitiesAttribute, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview)
|
strongSelf.context.account.pendingUpdateMessageManager.add(messageId: editMessage.messageId, text: text.string, media: media, entities: entitiesAttribute, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, invertMediaAttribute: invertedMediaAttribute, disableUrlPreview: disableUrlPreview)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,6 @@ extension ChatControllerImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
messageItemNode?.playMessageEffect()
|
messageItemNode?.playMessageEffect(force: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,8 +140,13 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
selfController.controllerInteraction?.scheduleCurrentMessage()
|
selfController.controllerInteraction?.scheduleCurrentMessage()
|
||||||
|
}, openPremiumPaywall: { [weak selfController] c in
|
||||||
|
guard let selfController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
selfController.push(c)
|
||||||
},
|
},
|
||||||
reactionItems: effectItems,
|
reactionItems: (!textInputView.text.isEmpty || mediaPreview != nil) ? effectItems : nil,
|
||||||
availableMessageEffects: availableMessageEffects,
|
availableMessageEffects: availableMessageEffects,
|
||||||
isPremium: hasPremium
|
isPremium: hasPremium
|
||||||
)
|
)
|
||||||
|
@ -448,7 +448,7 @@ private var nextClientId: Int32 = 1
|
|||||||
public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHistoryListNode {
|
public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHistoryListNode {
|
||||||
static let fixedAdMessageStableId: UInt32 = UInt32.max - 5000
|
static let fixedAdMessageStableId: UInt32 = UInt32.max - 5000
|
||||||
|
|
||||||
private let context: AccountContext
|
public let context: AccountContext
|
||||||
private let chatLocation: ChatLocation
|
private let chatLocation: ChatLocation
|
||||||
private let chatLocationContextHolder: Atomic<ChatLocationContextHolder?>
|
private let chatLocationContextHolder: Atomic<ChatLocationContextHolder?>
|
||||||
private let source: ChatHistoryListSource
|
private let source: ChatHistoryListSource
|
||||||
|
@ -13,7 +13,6 @@ import ChatControllerInteraction
|
|||||||
import FeaturedStickersScreen
|
import FeaturedStickersScreen
|
||||||
import ChatTextInputMediaRecordingButton
|
import ChatTextInputMediaRecordingButton
|
||||||
import ReplyAccessoryPanelNode
|
import ReplyAccessoryPanelNode
|
||||||
import ChatMessageItemView
|
|
||||||
import ChatMessageStickerItemNode
|
import ChatMessageStickerItemNode
|
||||||
import ChatMessageInstantVideoItemNode
|
import ChatMessageInstantVideoItemNode
|
||||||
import ChatMessageAnimatedStickerItemNode
|
import ChatMessageAnimatedStickerItemNode
|
||||||
@ -21,6 +20,7 @@ import ChatMessageTransitionNode
|
|||||||
import ChatMessageBubbleItemNode
|
import ChatMessageBubbleItemNode
|
||||||
import ChatEmptyNode
|
import ChatEmptyNode
|
||||||
import ChatMediaInputStickerGridItem
|
import ChatMediaInputStickerGridItem
|
||||||
|
import AccountContext
|
||||||
|
|
||||||
private func convertAnimatingSourceRect(_ rect: CGRect, fromView: UIView, toView: UIView?) -> CGRect {
|
private func convertAnimatingSourceRect(_ rect: CGRect, fromView: UIView, toView: UIView?) -> CGRect {
|
||||||
if let presentationLayer = fromView.layer.presentation() {
|
if let presentationLayer = fromView.layer.presentation() {
|
||||||
@ -241,7 +241,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class DecorationItemNodeImpl: ASDisplayNode, ChatMessageTransitionNode.DecorationItemNode {
|
final class DecorationItemNodeImpl: ASDisplayNode, ChatMessageTransitionNode.DecorationItemNode {
|
||||||
let itemNode: ChatMessageItemView
|
let itemNode: ChatMessageItemNodeProtocol
|
||||||
let contentView: UIView
|
let contentView: UIView
|
||||||
private let getContentAreaInScreenSpace: () -> CGRect
|
private let getContentAreaInScreenSpace: () -> CGRect
|
||||||
|
|
||||||
@ -251,7 +251,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
|
|||||||
|
|
||||||
fileprivate weak var overlayController: OverlayTransitionContainerController?
|
fileprivate weak var overlayController: OverlayTransitionContainerController?
|
||||||
|
|
||||||
init(itemNode: ChatMessageItemView, contentView: UIView, getContentAreaInScreenSpace: @escaping () -> CGRect) {
|
init(itemNode: ChatMessageItemNodeProtocol, contentView: UIView, getContentAreaInScreenSpace: @escaping () -> CGRect) {
|
||||||
self.itemNode = itemNode
|
self.itemNode = itemNode
|
||||||
self.contentView = contentView
|
self.contentView = contentView
|
||||||
self.getContentAreaInScreenSpace = getContentAreaInScreenSpace
|
self.getContentAreaInScreenSpace = getContentAreaInScreenSpace
|
||||||
@ -291,17 +291,17 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class CustomOffsetHandlerImpl {
|
final class CustomOffsetHandlerImpl {
|
||||||
weak var itemNode: ChatMessageItemView?
|
weak var itemNode: ChatMessageItemNodeProtocol?
|
||||||
let update: (CGFloat, ContainedViewLayoutTransition) -> Bool
|
let update: (CGFloat, ContainedViewLayoutTransition) -> Bool
|
||||||
|
|
||||||
init(itemNode: ChatMessageItemView, update: @escaping (CGFloat, ContainedViewLayoutTransition) -> Bool) {
|
init(itemNode: ChatMessageItemNodeProtocol, update: @escaping (CGFloat, ContainedViewLayoutTransition) -> Bool) {
|
||||||
self.itemNode = itemNode
|
self.itemNode = itemNode
|
||||||
self.update = update
|
self.update = update
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class AnimatingItemNode: ASDisplayNode {
|
private final class AnimatingItemNode: ASDisplayNode {
|
||||||
let itemNode: ChatMessageItemView
|
let itemNode: ChatMessageItemNodeProtocol
|
||||||
private let contextSourceNode: ContextExtractedContentContainingNode
|
private let contextSourceNode: ContextExtractedContentContainingNode
|
||||||
private let source: ChatMessageTransitionNodeImpl.Source
|
private let source: ChatMessageTransitionNodeImpl.Source
|
||||||
private let getContentAreaInScreenSpace: () -> CGRect
|
private let getContentAreaInScreenSpace: () -> CGRect
|
||||||
@ -315,7 +315,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
|
|||||||
var animationEnded: (() -> Void)?
|
var animationEnded: (() -> Void)?
|
||||||
var updateAfterCompletion: Bool = false
|
var updateAfterCompletion: Bool = false
|
||||||
|
|
||||||
init(itemNode: ChatMessageItemView, contextSourceNode: ContextExtractedContentContainingNode, source: ChatMessageTransitionNodeImpl.Source, getContentAreaInScreenSpace: @escaping () -> CGRect) {
|
init(itemNode: ChatMessageItemNodeProtocol, contextSourceNode: ContextExtractedContentContainingNode, source: ChatMessageTransitionNodeImpl.Source, getContentAreaInScreenSpace: @escaping () -> CGRect) {
|
||||||
self.itemNode = itemNode
|
self.itemNode = itemNode
|
||||||
self.getContentAreaInScreenSpace = getContentAreaInScreenSpace
|
self.getContentAreaInScreenSpace = getContentAreaInScreenSpace
|
||||||
|
|
||||||
@ -962,7 +962,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
|
|||||||
self.listNode.setCurrentSendAnimationCorrelationIds(correlationIds)
|
self.listNode.setCurrentSendAnimationCorrelationIds(correlationIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func add(decorationView: UIView, itemNode: ChatMessageItemView) -> DecorationItemNode {
|
public func add(decorationView: UIView, itemNode: ChatMessageItemNodeProtocol) -> DecorationItemNode {
|
||||||
let decorationItemNode = DecorationItemNodeImpl(itemNode: itemNode, contentView: decorationView, getContentAreaInScreenSpace: self.getContentAreaInScreenSpace)
|
let decorationItemNode = DecorationItemNodeImpl(itemNode: itemNode, contentView: decorationView, getContentAreaInScreenSpace: self.getContentAreaInScreenSpace)
|
||||||
decorationItemNode.updateLayout(size: self.bounds.size)
|
decorationItemNode.updateLayout(size: self.bounds.size)
|
||||||
|
|
||||||
@ -980,7 +980,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func addCustomOffsetHandler(itemNode: ChatMessageItemView, update: @escaping (CGFloat, ContainedViewLayoutTransition) -> Bool) -> Disposable {
|
public func addCustomOffsetHandler(itemNode: ChatMessageItemNodeProtocol, update: @escaping (CGFloat, ContainedViewLayoutTransition) -> Bool) -> Disposable {
|
||||||
let handler = CustomOffsetHandlerImpl(itemNode: itemNode, update: update)
|
let handler = CustomOffsetHandlerImpl(itemNode: itemNode, update: update)
|
||||||
self.customOffsetHandlers.append(handler)
|
self.customOffsetHandlers.append(handler)
|
||||||
|
|
||||||
@ -994,7 +994,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func beginAnimation(itemNode: ChatMessageItemView, source: Source) {
|
private func beginAnimation(itemNode: ChatMessageItemNodeProtocol, source: Source) {
|
||||||
var contextSourceNode: ContextExtractedContentContainingNode?
|
var contextSourceNode: ContextExtractedContentContainingNode?
|
||||||
if let itemNode = itemNode as? ChatMessageBubbleItemNode {
|
if let itemNode = itemNode as? ChatMessageBubbleItemNode {
|
||||||
contextSourceNode = itemNode.mainContextSourceNode
|
contextSourceNode = itemNode.mainContextSourceNode
|
||||||
@ -1016,7 +1016,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
|
|||||||
let overlayController = OverlayTransitionContainerController()
|
let overlayController = OverlayTransitionContainerController()
|
||||||
overlayController.displayNode.addSubnode(animatingItemNode)
|
overlayController.displayNode.addSubnode(animatingItemNode)
|
||||||
animatingItemNode.overlayController = overlayController
|
animatingItemNode.overlayController = overlayController
|
||||||
itemNode.item?.context.sharedContext.mainWindow?.presentInGlobalOverlay(overlayController)
|
self.listNode.context.sharedContext.mainWindow?.presentInGlobalOverlay(overlayController)
|
||||||
default:
|
default:
|
||||||
self.addSubnode(animatingItemNode)
|
self.addSubnode(animatingItemNode)
|
||||||
}
|
}
|
||||||
@ -1031,8 +1031,8 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
|
|||||||
strongSelf.animatingItemNodes.remove(at: index)
|
strongSelf.animatingItemNodes.remove(at: index)
|
||||||
}
|
}
|
||||||
|
|
||||||
if animatingItemNode.updateAfterCompletion, let item = animatingItemNode.itemNode.item {
|
if animatingItemNode.updateAfterCompletion {
|
||||||
for (message, _) in item.content {
|
for message in animatingItemNode.itemNode.messages() {
|
||||||
strongSelf.listNode.requestMessageUpdate(stableId: message.stableId)
|
strongSelf.listNode.requestMessageUpdate(stableId: message.stableId)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -1081,14 +1081,9 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
|
|||||||
|
|
||||||
var messageItemNode: ListViewItemNode?
|
var messageItemNode: ListViewItemNode?
|
||||||
self.listNode.forEachItemNode { itemNode in
|
self.listNode.forEachItemNode { itemNode in
|
||||||
if let itemNode = itemNode as? ChatMessageItemView {
|
if let itemNode = itemNode as? ChatMessageItemNodeProtocol {
|
||||||
if let item = itemNode.item {
|
if itemNode.matchesMessage(id: messageId) {
|
||||||
for (message, _) in item.content {
|
messageItemNode = itemNode
|
||||||
if message.id == messageId {
|
|
||||||
messageItemNode = itemNode
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1153,11 +1148,9 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
|
|||||||
|
|
||||||
func isAnimatingMessage(stableId: UInt32) -> Bool {
|
func isAnimatingMessage(stableId: UInt32) -> Bool {
|
||||||
for itemNode in self.animatingItemNodes {
|
for itemNode in self.animatingItemNodes {
|
||||||
if let item = itemNode.itemNode.item {
|
for message in itemNode.itemNode.messages() {
|
||||||
for (message, _) in item.content {
|
if message.stableId == stableId {
|
||||||
if message.stableId == stableId {
|
return true
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1166,11 +1159,9 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
|
|||||||
|
|
||||||
func scheduleUpdateMessageAfterAnimationCompleted(stableId: UInt32) {
|
func scheduleUpdateMessageAfterAnimationCompleted(stableId: UInt32) {
|
||||||
for itemNode in self.animatingItemNodes {
|
for itemNode in self.animatingItemNodes {
|
||||||
if let item = itemNode.itemNode.item {
|
for message in itemNode.itemNode.messages() {
|
||||||
for (message, _) in item.content {
|
if message.stableId == stableId {
|
||||||
if message.stableId == stableId {
|
itemNode.updateAfterCompletion = true
|
||||||
itemNode.updateAfterCompletion = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1178,11 +1169,9 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
|
|||||||
|
|
||||||
func hasScheduledUpdateMessageAfterAnimationCompleted(stableId: UInt32) -> Bool {
|
func hasScheduledUpdateMessageAfterAnimationCompleted(stableId: UInt32) -> Bool {
|
||||||
for itemNode in self.animatingItemNodes {
|
for itemNode in self.animatingItemNodes {
|
||||||
if let item = itemNode.itemNode.item {
|
for message in itemNode.itemNode.messages() {
|
||||||
for (message, _) in item.content {
|
if message.stableId == stableId {
|
||||||
if message.stableId == stableId {
|
return itemNode.updateAfterCompletion
|
||||||
return itemNode.updateAfterCompletion
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user