From 304ce900c1a25152112a4d742bb298f10a0c3fba Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Tue, 14 May 2024 12:36:13 +0400 Subject: [PATCH] Audio send message preview --- .../ChatSendMessageContextScreen.swift | 5 +- .../Sources/MessageItemView.swift | 5 +- .../Sources/MediaPickerSelectedListNode.swift | 2 +- submodules/TelegramUI/BUILD | 1 + .../Sources/ChatMessageBubbleItemNode.swift | 6 +- .../ChatMessageFileBubbleContentNode.swift | 2 + .../ChatSendAudioMessageContextPreview/BUILD | 30 ++++ .../ChatSendAudioMessageContextPreview.swift | 146 ++++++++++++++++++ .../Lottie/Public/Primitives/Vectors.mm | 2 +- ...ChatMessageDisplaySendMessageOptions.swift | 11 ++ .../Private/Utility/Extensions/MathKit.swift | 2 +- 11 files changed, 205 insertions(+), 7 deletions(-) create mode 100644 submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/BUILD create mode 100644 submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift index a88561e413..ea406ff032 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift @@ -34,6 +34,7 @@ func convertFrame(_ frame: CGRect, from fromView: UIView, to toView: UIView) -> public enum ChatSendMessageContextScreenMediaPreviewLayoutType { case message + case media case videoMessage } @@ -492,7 +493,7 @@ final class ChatSendMessageContextScreenComponent: Component { let messageItemViewContainerSize: CGSize if let mediaPreview = component.mediaPreview { switch mediaPreview.layoutType { - case .message: + case .message, .media: messageItemViewContainerSize = CGSize(width: availableSize.width - 16.0 - 40.0, height: availableSize.height) case .videoMessage: messageItemViewContainerSize = CGSize(width: availableSize.width, height: availableSize.height) @@ -820,7 +821,7 @@ final class ChatSendMessageContextScreenComponent: Component { 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 { switch mediaPreview.layoutType { - case .message: + case .message, .media: break case .videoMessage: readyMessageItemFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - messageItemSize.width) * 0.5), y: readySendButtonFrame.maxY - 6.0 - messageItemSize.height), size: messageItemSize) diff --git a/submodules/ChatSendMessageActionUI/Sources/MessageItemView.swift b/submodules/ChatSendMessageActionUI/Sources/MessageItemView.swift index c4a827e434..63d6ef7050 100644 --- a/submodules/ChatSendMessageActionUI/Sources/MessageItemView.swift +++ b/submodules/ChatSendMessageActionUI/Sources/MessageItemView.swift @@ -283,7 +283,7 @@ final class MessageItemView: UIView { var backgroundSize = CGSize(width: mediaPreviewSize.width, height: mediaPreviewSize.height) let mediaPreviewFrame: CGRect switch sourceMediaPreview.layoutType { - case .message: + case .message, .media: backgroundSize.width += 7.0 mediaPreviewFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: mediaPreviewSize) case .videoMessage: @@ -314,6 +314,9 @@ final class MessageItemView: UIView { let effectIconBackgroundFrame: CGRect switch sourceMediaPreview.layoutType { case .message: + effectIconBackgroundFrame = CGRect(origin: CGPoint(x: mediaPreviewFrame.maxX - effectIconBackgroundSize.width - 3.0, y: mediaPreviewFrame.maxY - effectIconBackgroundSize.height - 4.0), size: effectIconBackgroundSize) + effectIconBackgroundView.backgroundColor = nil + case .media: effectIconBackgroundFrame = CGRect(origin: CGPoint(x: mediaPreviewFrame.maxX - effectIconBackgroundSize.width - 6.0, y: mediaPreviewFrame.maxY - effectIconBackgroundSize.height - 6.0), size: effectIconBackgroundSize) effectIconBackgroundView.backgroundColor = presentationData.theme.chat.message.mediaDateAndStatusFillColor case .videoMessage: diff --git a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift index 972efaaa99..f1105818d9 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift @@ -512,7 +512,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS private let isExternalPreview: Bool var globalClippingRect: CGRect? var layoutType: ChatSendMessageContextScreenMediaPreviewLayoutType { - return .message + return .media } fileprivate var wallpaperBackgroundNode: WallpaperBackgroundNode? diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 867c3ef62c..11d1da5c76 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -447,6 +447,7 @@ swift_library( "//submodules/TelegramUI/Components/Ads/AdsReportScreen", "//submodules/TelegramUI/Components/Settings/BotSettingsScreen", "//submodules/TelegramUI/Components/AdminUserActionsSheet", + "//submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview", ] + select({ "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "//build-system:ios_sim_arm64": [], diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 44a2e04d39..1657f60090 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -5844,7 +5844,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI #if targetEnvironment(simulator) additionalAnimationNode = DirectAnimatedStickerNode() #else - additionalAnimationNode = LottieMetalAnimatedStickerNode() + if "".isEmpty { + additionalAnimationNode = DirectAnimatedStickerNode() + } else { + additionalAnimationNode = LottieMetalAnimatedStickerNode() + } #endif additionalAnimationNode.updateLayout(size: animationSize) additionalAnimationNode.setup(source: source, width: Int(animationSize.width), height: Int(animationSize.height), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix)) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift index 4335199520..d52d60c761 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift @@ -114,6 +114,8 @@ public class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { let statusType: ChatMessageDateAndStatusType? if case .customChatContents = item.associatedData.subject { statusType = nil + } else if item.message.timestamp == 0 { + statusType = nil } else { switch preparePosition { case .linear(_, .None), .linear(_, .Neighbour(true, _, _)): diff --git a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/BUILD b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/BUILD new file mode 100644 index 0000000000..43b1d9a9b9 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/BUILD @@ -0,0 +1,30 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatSendAudioMessageContextPreview", + module_name = "ChatSendAudioMessageContextPreview", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/TelegramPresentationData", + "//submodules/ChatPresentationInterfaceState", + "//submodules/ChatSendMessageActionUI", + "//submodules/ComponentFlow", + "//submodules/AccountContext", + "//submodules/TelegramCore", + "//submodules/Postbox", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Display", + "//submodules/WallpaperBackgroundNode", + "//submodules/AudioWaveform", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemView", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift new file mode 100644 index 0000000000..8cdbe2d773 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift @@ -0,0 +1,146 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import TelegramPresentationData +import ChatPresentationInterfaceState +import AccountContext +import ChatSendMessageActionUI +import SwiftSignalKit +import ComponentFlow +import Display +import Postbox +import TelegramCore +import WallpaperBackgroundNode +import AudioWaveform +import ChatMessageItemView + +public final class ChatSendAudioMessageContextPreview: UIView, ChatSendMessageContextScreenMediaPreview { + private let context: AccountContext + private let presentationData: PresentationData + private let wallpaperBackgroundNode: WallpaperBackgroundNode? + private let waveform: AudioWaveform + + private var messageNodes: [ListViewItemNode]? + private let messagesContainer: UIView + + public var isReady: Signal { + return .single(true) + } + + public var view: UIView { + return self + } + + public var globalClippingRect: CGRect? { + return nil + } + + public var layoutType: ChatSendMessageContextScreenMediaPreviewLayoutType { + return .message + } + + public init(context: AccountContext, presentationData: PresentationData, wallpaperBackgroundNode: WallpaperBackgroundNode?, waveform: AudioWaveform) { + self.context = context + self.presentationData = presentationData + self.wallpaperBackgroundNode = wallpaperBackgroundNode + self.waveform = waveform + + self.messagesContainer = UIView() + self.messagesContainer.layer.sublayerTransform = CATransform3DMakeScale(-1.0, -1.0, 1.0) + + super.init(frame: CGRect()) + + self.addSubview(self.messagesContainer) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + public func animateIn(transition: Transition) { + transition.animateAlpha(view: self.messagesContainer, from: 0.0, to: 1.0) + transition.animateScale(view: self.messagesContainer, from: 0.001, to: 1.0) + } + + public func animateOut(transition: Transition) { + transition.setAlpha(view: self.messagesContainer, alpha: 0.0) + transition.setScale(view: self.messagesContainer, scale: 0.001) + } + + public func animateOutOnSend(transition: Transition) { + transition.setAlpha(view: self.messagesContainer, alpha: 0.0) + } + + public func update(containerSize: CGSize, transition: Transition) -> CGSize { + let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: self.waveform.makeBitstream())] + let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes) + + let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: self.context.account.peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [voiceMedia], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + + let item = self.context.sharedContext.makeChatMessagePreviewItem( + context: self.context, + messages: [message], + theme: presentationData.theme, + strings: presentationData.strings, + wallpaper: presentationData.chatWallpaper, + fontSize: presentationData.chatFontSize, + chatBubbleCorners: presentationData.chatBubbleCorners, + dateTimeFormat: presentationData.dateTimeFormat, + nameOrder: presentationData.nameDisplayOrder, + forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .fetchStatus(.Local), fetchStatus: .Local), + tapMessage: nil, + clickThroughMessage: nil, + backgroundNode: self.wallpaperBackgroundNode, + availableReactions: nil, + accountPeer: nil, + isCentered: false, + isPreview: true, + isStandalone: true + ) + let items = [item] + + let params = ListViewItemLayoutParams(width: containerSize.width, leftInset: 0.0, rightInset: 0.0, availableHeight: containerSize.height) + if let messageNodes = self.messageNodes { + for i in 0 ..< items.count { + let itemNode = messageNodes[i] + items[i].updateNode(async: { $0() }, node: { + return itemNode + }, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .None, completion: { (layout, apply) in + let nodeFrame = CGRect(origin: itemNode.frame.origin, size: CGSize(width: containerSize.width, height: layout.size.height)) + + itemNode.contentSize = layout.contentSize + itemNode.insets = layout.insets + itemNode.frame = nodeFrame + itemNode.isUserInteractionEnabled = false + + apply(ListViewItemApply(isOnScreen: true)) + }) + } + } else { + var messageNodes: [ListViewItemNode] = [] + for i in 0 ..< items.count { + var itemNode: ListViewItemNode? + items[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], completion: { node, apply in + itemNode = node + apply().1(ListViewItemApply(isOnScreen: true)) + }) + itemNode!.isUserInteractionEnabled = false + messageNodes.append(itemNode!) + self.messagesContainer.addSubview(itemNode!.view) + } + self.messageNodes = messageNodes + } + + guard let messageNode = self.messageNodes?.first as? ChatMessageItemView else { + return CGSize(width: 10.0, height: 10.0) + } + let contentFrame = messageNode.contentFrame() + + self.messagesContainer.frame = CGRect(origin: CGPoint(x: 6.0, y: 3.0), size: CGSize(width: contentFrame.width, height: contentFrame.height)) + + return CGSize(width: contentFrame.width - 4.0, height: contentFrame.height + 2.0) + } +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Vectors.mm b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Vectors.mm index 4b18159388..81a0f4ff73 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Vectors.mm +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Vectors.mm @@ -368,7 +368,7 @@ Vector2D Vector2D::interpolate( while (!foundPoint) { refineIterations = refineIterations + 1; /// First see if the next point is still less than the projected length. - auto nextPoint = points[closestPoint + 1]; + auto nextPoint = points[std::min(closestPoint + 1, (int)points.size() - 1)]; if (nextPoint.distance < accurateDistance) { point = nextPoint; closestPoint = closestPoint + 1; diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift index b20e3deef1..0261143f1e 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift @@ -12,6 +12,7 @@ import AccountContext import TopMessageReactions import ReactionSelectionNode import ChatControllerInteraction +import ChatSendAudioMessageContextPreview extension ChatSendMessageEffect { convenience init(_ effect: ChatSendMessageActionSheetController.MessageEffect) { @@ -83,6 +84,16 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no if let videoRecorderValue = selfController.videoRecorderValue { mediaPreview = videoRecorderValue.makeSendMessageContextPreview() } + if let mediaDraftState = selfController.presentationInterfaceState.interfaceState.mediaDraftState { + if case let .audio(audio) = mediaDraftState { + mediaPreview = ChatSendAudioMessageContextPreview( + context: selfController.context, + presentationData: selfController.presentationData, + wallpaperBackgroundNode: selfController.chatDisplayNode.backgroundNode, + waveform: audio.waveform + ) + } + } let controller = makeChatSendMessageActionSheetController( context: selfController.context, diff --git a/submodules/lottie-ios/Sources/Private/Utility/Extensions/MathKit.swift b/submodules/lottie-ios/Sources/Private/Utility/Extensions/MathKit.swift index 0cf2e727fa..22382bc699 100644 --- a/submodules/lottie-ios/Sources/Private/Utility/Extensions/MathKit.swift +++ b/submodules/lottie-ios/Sources/Private/Utility/Extensions/MathKit.swift @@ -370,7 +370,7 @@ extension CGPoint { while foundPoint == false { refineIterations = refineIterations + 1 /// First see if the next point is still less than the projected length. - let nextPoint = points[closestPoint + 1] + let nextPoint = points[min(closestPoint + 1, points.indices.last!)] if nextPoint.distance < accurateDistance { point = nextPoint closestPoint = closestPoint + 1