mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
[WIP] View-once audio messages
This commit is contained in:
@@ -1,11 +1,18 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import ContextUI
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import ChatMessageItemView
|
||||
import AccountContext
|
||||
import WallpaperBackgroundNode
|
||||
import TelegramPresentationData
|
||||
import DustEffect
|
||||
import TooltipUI
|
||||
import TelegramNotices
|
||||
|
||||
final class ChatMessageContextLocationContentSource: ContextLocationContentSource {
|
||||
private let controller: ViewController
|
||||
@@ -97,6 +104,249 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatViewOnceMessageContextExtractedContentSource: ContextExtractedContentSource {
|
||||
let keepInPlace: Bool = false
|
||||
let ignoreContentTouches: Bool = false
|
||||
let blurBackground: Bool = true
|
||||
let centerVertically: Bool = true
|
||||
|
||||
private let context: AccountContext
|
||||
private let presentationData: PresentationData
|
||||
private weak var chatNode: ChatControllerNode?
|
||||
private weak var backgroundNode: WallpaperBackgroundNode?
|
||||
private let engine: TelegramEngine
|
||||
private let message: Message
|
||||
private let present: (ViewController) -> Void
|
||||
|
||||
private var messageNodeCopy: ChatMessageItemView?
|
||||
private weak var tooltipController: TooltipScreen?
|
||||
|
||||
var shouldBeDismissed: Signal<Bool, NoError> {
|
||||
return self.context.sharedContext.mediaManager.globalMediaPlayerState
|
||||
|> filter { playlistStateAndType in
|
||||
if let (_, state, _) = playlistStateAndType, case .state = state {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|> take(1)
|
||||
|> map { _ in
|
||||
return false
|
||||
}
|
||||
|> then(
|
||||
self.context.sharedContext.mediaManager.globalMediaPlayerState
|
||||
|> filter { playlistStateAndType in
|
||||
return playlistStateAndType == nil
|
||||
}
|
||||
|> take(1)
|
||||
|> map { _ in
|
||||
return true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, chatNode: ChatControllerNode, backgroundNode: WallpaperBackgroundNode, engine: TelegramEngine, message: Message, present: @escaping (ViewController) -> Void) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.chatNode = chatNode
|
||||
self.backgroundNode = backgroundNode
|
||||
self.engine = engine
|
||||
self.message = message
|
||||
self.present = present
|
||||
}
|
||||
|
||||
func takeView() -> ContextControllerTakeViewInfo? {
|
||||
guard let chatNode = self.chatNode, let backgroundNode = self.backgroundNode, let validLayout = chatNode.validLayout?.0 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var result: ContextControllerTakeViewInfo?
|
||||
var sourceNode: ContextExtractedContentContainingNode?
|
||||
var sourceRect: CGRect = .zero
|
||||
chatNode.historyNode.forEachItemNode { itemNode in
|
||||
guard let itemNode = itemNode as? ChatMessageItemView else {
|
||||
return
|
||||
}
|
||||
guard let item = itemNode.item else {
|
||||
return
|
||||
}
|
||||
if item.content.contains(where: { $0.0.stableId == self.message.stableId }), let contentNode = itemNode.getMessageContextSourceNode(stableId: self.message.stableId) {
|
||||
sourceNode = contentNode
|
||||
sourceRect = itemNode.frame
|
||||
}
|
||||
}
|
||||
|
||||
let isIncoming = self.message.effectivelyIncoming(self.context.account.peerId)
|
||||
let isVideo = (self.message.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile)?.isInstantVideo ?? false
|
||||
|
||||
var tooltipSourceRect: CGRect = .zero
|
||||
|
||||
if let sourceNode {
|
||||
var bubbleWidth: CGFloat = 0.0
|
||||
|
||||
if (isIncoming || "".isEmpty) && !isVideo {
|
||||
let messageItem = self.context.sharedContext.makeChatMessagePreviewItem(
|
||||
context: self.context,
|
||||
messages: [self.message],
|
||||
theme: self.presentationData.theme,
|
||||
strings: self.presentationData.strings,
|
||||
wallpaper: self.presentationData.chatWallpaper,
|
||||
fontSize: self.presentationData.chatFontSize,
|
||||
chatBubbleCorners: self.presentationData.chatBubbleCorners,
|
||||
dateTimeFormat: self.presentationData.dateTimeFormat,
|
||||
nameOrder: self.presentationData.nameDisplayOrder,
|
||||
forcedResourceStatus: nil,
|
||||
tapMessage: nil,
|
||||
clickThroughMessage: nil,
|
||||
backgroundNode: backgroundNode,
|
||||
availableReactions: nil,
|
||||
accountPeer: nil,
|
||||
isCentered: false,
|
||||
isPreview: false
|
||||
)
|
||||
|
||||
let params = ListViewItemLayoutParams(width: chatNode.historyNode.frame.width, leftInset: validLayout.safeInsets.left, rightInset: validLayout.safeInsets.right, availableHeight: chatNode.historyNode.frame.height, isStandalone: false)
|
||||
var node: ListViewItemNode?
|
||||
|
||||
messageItem.nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: nil, nextItem: nil, completion: { messageNode, apply in
|
||||
node = messageNode
|
||||
apply().1(ListViewItemApply(isOnScreen: true))
|
||||
})
|
||||
|
||||
if let messageNode = node as? ChatMessageItemView, let copyContentNode = messageNode.getMessageContextSourceNode(stableId: self.message.stableId) {
|
||||
messageNode.frame.origin.y = chatNode.frame.height - sourceRect.origin.y - sourceRect.size.height
|
||||
chatNode.addSubnode(messageNode)
|
||||
result = ContextControllerTakeViewInfo(containingItem: .node(copyContentNode), contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil))
|
||||
|
||||
bubbleWidth = copyContentNode.contentNode.subnodes?.first?.frame.width ?? messageNode.frame.width
|
||||
}
|
||||
|
||||
self.messageNodeCopy = node as? ChatMessageItemView
|
||||
} else {
|
||||
result = ContextControllerTakeViewInfo(containingItem: .node(sourceNode), contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil))
|
||||
}
|
||||
|
||||
tooltipSourceRect = CGRect(x: isIncoming ? 22.0 : chatNode.frame.width - bubbleWidth + 10.0, y: floorToScreenPixels((chatNode.frame.height - 75.0) / 2.0) - 43.0, width: 44.0, height: 44.0)
|
||||
}
|
||||
|
||||
if !isVideo {
|
||||
let displayTooltip = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let absoluteFrame = tooltipSourceRect
|
||||
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.maxY), size: CGSize())
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
var tooltipText: String?
|
||||
if isIncoming {
|
||||
tooltipText = presentationData.strings.Chat_PlayOnceVoiceMessageTooltip
|
||||
} else if let peer = self.message.peers[self.message.id.peerId] {
|
||||
let peerName = EnginePeer(peer).compactDisplayTitle
|
||||
tooltipText = presentationData.strings.Chat_PlayOnceVoiceMessageYourTooltip(peerName).string
|
||||
}
|
||||
|
||||
if let tooltipText {
|
||||
let tooltipController = TooltipScreen(
|
||||
account: self.context.account,
|
||||
sharedContext: self.context.sharedContext,
|
||||
text: .markdown(text: tooltipText),
|
||||
balancedTextLayout: true,
|
||||
constrainWidth: 240.0,
|
||||
style: .customBlur(UIColor(rgb: 0x18181a), 0.0),
|
||||
arrowStyle: .small,
|
||||
icon: nil,
|
||||
location: .point(location, .bottom),
|
||||
displayDuration: .custom(3.0),
|
||||
inset: 8.0,
|
||||
cornerRadius: 11.0,
|
||||
shouldDismissOnTouch: { _, _ in
|
||||
return .ignore
|
||||
}
|
||||
)
|
||||
self.tooltipController = tooltipController
|
||||
self.present(tooltipController)
|
||||
}
|
||||
}
|
||||
|
||||
if isIncoming {
|
||||
let _ = (ApplicationSpecificNotice.getIncomingVoiceMessagePlayOnceTip(accountManager: self.context.sharedContext.accountManager)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] counter in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if counter >= 2 {
|
||||
return
|
||||
}
|
||||
Queue.mainQueue().after(0.3) {
|
||||
displayTooltip()
|
||||
}
|
||||
let _ = ApplicationSpecificNotice.incrementIncomingVoiceMessagePlayOnceTip(accountManager: self.context.sharedContext.accountManager).startStandalone()
|
||||
})
|
||||
} else {
|
||||
let _ = (ApplicationSpecificNotice.getOutgoingVoiceMessagePlayOnceTip(accountManager: self.context.sharedContext.accountManager)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] counter in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if counter >= 2 {
|
||||
return
|
||||
}
|
||||
Queue.mainQueue().after(0.3) {
|
||||
displayTooltip()
|
||||
}
|
||||
let _ = ApplicationSpecificNotice.incrementOutgoingVoiceMessagePlayOnceTip(accountManager: self.context.sharedContext.accountManager).startStandalone()
|
||||
})
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private var dustEffectLayer: DustEffectLayer?
|
||||
func putBack() -> ContextControllerPutBackViewInfo? {
|
||||
guard let chatNode = self.chatNode else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let tooltipController = self.tooltipController {
|
||||
tooltipController.dismiss()
|
||||
}
|
||||
|
||||
if let messageNodeCopy = self.messageNodeCopy, let sourceView = messageNodeCopy.supernode?.view, let contentNode = messageNodeCopy.getMessageContextSourceNode(stableId: nil)?.contentNode, let parentNode = contentNode.supernode?.supernode?.supernode {
|
||||
let dustEffectLayer = DustEffectLayer()
|
||||
dustEffectLayer.position = sourceView.bounds.center
|
||||
dustEffectLayer.bounds = CGRect(origin: CGPoint(), size: sourceView.bounds.size)
|
||||
dustEffectLayer.zPosition = 10.0
|
||||
parentNode.layer.addSublayer(dustEffectLayer)
|
||||
|
||||
guard let (image, subFrame) = messageNodeCopy.makeContentSnapshot() else {
|
||||
return nil
|
||||
}
|
||||
var itemFrame = subFrame //messageNodeCopy.layer.convert(subFrame, to: dustEffectLayer)
|
||||
itemFrame.origin.y = floorToScreenPixels((sourceView.frame.height - subFrame.height) / 2.0)
|
||||
dustEffectLayer.addItem(frame: itemFrame, image: image)
|
||||
messageNodeCopy.removeFromSupernode()
|
||||
contentNode.removeFromSupernode()
|
||||
return nil
|
||||
} else {
|
||||
var result: ContextControllerPutBackViewInfo?
|
||||
chatNode.historyNode.forEachItemNode { itemNode in
|
||||
guard let itemNode = itemNode as? ChatMessageItemView else {
|
||||
return
|
||||
}
|
||||
guard let item = itemNode.item else {
|
||||
return
|
||||
}
|
||||
if item.content.contains(where: { $0.0.stableId == self.message.stableId }) {
|
||||
result = ContextControllerPutBackViewInfo(contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatMessageReactionContextExtractedContentSource: ContextExtractedContentSource {
|
||||
let keepInPlace: Bool = false
|
||||
let ignoreContentTouches: Bool = true
|
||||
|
||||
Reference in New Issue
Block a user