diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift index aab9c29188..a88561e413 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift @@ -32,13 +32,20 @@ func convertFrame(_ frame: CGRect, from fromView: UIView, to toView: UIView) -> return targetWindowFrame } +public enum ChatSendMessageContextScreenMediaPreviewLayoutType { + case message + case videoMessage +} + public protocol ChatSendMessageContextScreenMediaPreview: AnyObject { var isReady: Signal { get } var view: UIView { get } var globalClippingRect: CGRect? { get } + var layoutType: ChatSendMessageContextScreenMediaPreviewLayoutType { get } func animateIn(transition: Transition) func animateOut(transition: Transition) + func animateOutOnSend(transition: Transition) func update(containerSize: CGSize, transition: Transition) -> CGSize } @@ -482,6 +489,18 @@ final class ChatSendMessageContextScreenComponent: Component { maxTextHeight -= environment.safeInsets.bottom } + let messageItemViewContainerSize: CGSize + if let mediaPreview = component.mediaPreview { + switch mediaPreview.layoutType { + case .message: + messageItemViewContainerSize = CGSize(width: availableSize.width - 16.0 - 40.0, height: availableSize.height) + case .videoMessage: + messageItemViewContainerSize = CGSize(width: availableSize.width, height: availableSize.height) + } + } else { + messageItemViewContainerSize = CGSize(width: availableSize.width - 16.0 - 40.0, height: availableSize.height) + } + let messageItemSize = messageItemView.update( context: component.context, presentationData: presentationData, @@ -494,7 +513,7 @@ final class ChatSendMessageContextScreenComponent: Component { explicitBackgroundSize: explicitMessageBackgroundSize, maxTextWidth: localSourceTextInputViewFrame.width, maxTextHeight: maxTextHeight, - containerSize: CGSize(width: availableSize.width - 16.0 - 40.0, height: availableSize.height), + containerSize: messageItemViewContainerSize, effect: self.presentationAnimationState.key == .animatedIn ? self.selectedMessageEffect : nil, transition: transition ) @@ -799,6 +818,15 @@ final class ChatSendMessageContextScreenComponent: Component { let sourceActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - actionsStackSize.width, y: sourceMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize) 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: + break + case .videoMessage: + readyMessageItemFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - messageItemSize.width) * 0.5), y: readySendButtonFrame.maxY - 6.0 - messageItemSize.height), size: messageItemSize) + } + } + var readyActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - actionsStackSize.width, y: readyMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize) let bottomOverflow = readyActionsStackFrame.maxY - (availableSize.height - environment.safeInsets.bottom) @@ -874,7 +902,7 @@ final class ChatSendMessageContextScreenComponent: Component { transition.setAlpha(view: actionsStackNode.view, alpha: 0.0) transition.setScale(view: actionsStackNode.view, scale: 0.001) - messageItemView.animateOut(transition: transition) + messageItemView.animateOut(toEmpty: self.animateOutToEmpty, transition: transition) } } else { switch self.presentationAnimationState { diff --git a/submodules/ChatSendMessageActionUI/Sources/MessageItemView.swift b/submodules/ChatSendMessageActionUI/Sources/MessageItemView.swift index 809f5dcce8..c4a827e434 100644 --- a/submodules/ChatSendMessageActionUI/Sources/MessageItemView.swift +++ b/submodules/ChatSendMessageActionUI/Sources/MessageItemView.swift @@ -166,6 +166,8 @@ final class MessageItemView: UIView { super.init(frame: frame) + self.isUserInteractionEnabled = false + self.addSubview(self.backgroundWallpaperNode.view) self.addSubview(self.backgroundNode.view) @@ -182,9 +184,13 @@ final class MessageItemView: UIView { } } - func animateOut(transition: Transition) { + func animateOut(toEmpty: Bool, transition: Transition) { if let mediaPreview = self.mediaPreview { - mediaPreview.animateOut(transition: transition) + if toEmpty { + mediaPreview.animateOutOnSend(transition: transition) + } else { + mediaPreview.animateOut(transition: transition) + } } } @@ -274,9 +280,16 @@ final class MessageItemView: UIView { let mediaPreviewSize = sourceMediaPreview.update(containerSize: containerSize, transition: transition) - let backgroundSize = CGSize(width: mediaPreviewSize.width + 7.0, height: mediaPreviewSize.height) + var backgroundSize = CGSize(width: mediaPreviewSize.width, height: mediaPreviewSize.height) + let mediaPreviewFrame: CGRect + switch sourceMediaPreview.layoutType { + case .message: + backgroundSize.width += 7.0 + mediaPreviewFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: mediaPreviewSize) + case .videoMessage: + mediaPreviewFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: mediaPreviewSize) + } - let mediaPreviewFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: mediaPreviewSize) transition.setFrame(view: sourceMediaPreview.view, frame: mediaPreviewFrame) if let effectIcon = self.effectIcon, let effectIconSize { @@ -295,10 +308,21 @@ final class MessageItemView: UIView { self.effectIconBackgroundView = effectIconBackgroundView self.insertSubview(effectIconBackgroundView, belowSubview: effectIconView) } - effectIconBackgroundView.backgroundColor = presentationData.theme.chat.message.mediaDateAndStatusFillColor let effectIconBackgroundSize = CGSize(width: effectIconSize.width + 8.0 * 2.0, height: 18.0) - let effectIconBackgroundFrame = CGRect(origin: CGPoint(x: mediaPreviewFrame.maxX - effectIconBackgroundSize.width - 6.0, y: mediaPreviewFrame.maxY - effectIconBackgroundSize.height - 6.0), size: effectIconBackgroundSize) + + let effectIconBackgroundFrame: CGRect + switch sourceMediaPreview.layoutType { + case .message: + 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: + effectIconBackgroundFrame = CGRect(origin: CGPoint(x: mediaPreviewFrame.maxX - effectIconBackgroundSize.width - 34.0, y: mediaPreviewFrame.maxY - effectIconBackgroundSize.height - 6.0), size: effectIconBackgroundSize) + + let serviceMessageColors = serviceMessageColorComponents(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper) + + effectIconBackgroundView.backgroundColor = serviceMessageColors.dateFillStatic + } let effectIconFrame = CGRect(origin: CGPoint(x: effectIconBackgroundFrame.minX + floor((effectIconBackgroundFrame.width - effectIconSize.width) * 0.5), y: effectIconBackgroundFrame.minY + floor((effectIconBackgroundFrame.height - effectIconSize.height) * 0.5)), size: effectIconSize) diff --git a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift index 708318d3e5..972efaaa99 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift @@ -511,6 +511,9 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS private let persistentItems: Bool private let isExternalPreview: Bool var globalClippingRect: CGRect? + var layoutType: ChatSendMessageContextScreenMediaPreviewLayoutType { + return .message + } fileprivate var wallpaperBackgroundNode: WallpaperBackgroundNode? private let scrollNode: ASScrollNode @@ -656,7 +659,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS } func animateOut(transition: ContainedViewLayoutTransition, completion: @escaping () -> Void = {}) { - if let wallpaperBackgroundNode = self.wallpaperBackgroundNode{ + if let wallpaperBackgroundNode = self.wallpaperBackgroundNode { wallpaperBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak self] _ in completion() @@ -714,6 +717,10 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS } } + func animateOutOnSend(transition: Transition) { + transition.setAlpha(view: self.view, alpha: 0.0) + } + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } diff --git a/submodules/TelegramUI/Components/VideoMessageCameraScreen/BUILD b/submodules/TelegramUI/Components/VideoMessageCameraScreen/BUILD index 9044bfadc1..3405ccaf0d 100644 --- a/submodules/TelegramUI/Components/VideoMessageCameraScreen/BUILD +++ b/submodules/TelegramUI/Components/VideoMessageCameraScreen/BUILD @@ -39,6 +39,7 @@ swift_library( "//submodules/TelegramUI/Components/MediaEditor", "//submodules/LegacyMediaPickerUI", "//submodules/TelegramAudio", + "//submodules/ChatSendMessageActionUI", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift index 207e788b94..d873e6011f 100644 --- a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift +++ b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift @@ -27,6 +27,7 @@ import LocalMediaResources import ImageCompression import LegacyMediaPickerUI import TelegramAudio +import ChatSendMessageActionUI struct CameraState: Equatable { enum Recording: Equatable { @@ -553,6 +554,7 @@ public class VideoMessageCameraScreen: ViewController { fileprivate let containerView: UIView fileprivate let componentHost: ComponentView fileprivate let previewContainerView: UIView + fileprivate let previewContainerContentView: UIView private var previewSnapshotView: UIView? private var previewBlurView: BlurView @@ -625,7 +627,10 @@ public class VideoMessageCameraScreen: ViewController { self.componentHost = ComponentView() self.previewContainerView = UIView() - self.previewContainerView.clipsToBounds = true + + self.previewContainerContentView = UIView() + self.previewContainerContentView.clipsToBounds = true + self.previewContainerView.addSubview(self.previewContainerContentView) let isDualCameraEnabled = Camera.isDualCameraSupported(forRoundVideo: true) let isFrontPosition = "".isEmpty @@ -666,13 +671,13 @@ public class VideoMessageCameraScreen: ViewController { self.containerView.addSubview(self.previewContainerView) - self.previewContainerView.addSubview(self.mainPreviewView) + self.previewContainerContentView.addSubview(self.mainPreviewView) if isDualCameraEnabled { - self.previewContainerView.addSubview(self.additionalPreviewView) + self.previewContainerContentView.addSubview(self.additionalPreviewView) } - self.previewContainerView.addSubview(self.progressView) - self.previewContainerView.addSubview(self.previewBlurView) - self.previewContainerView.addSubview(self.loadingView) + self.previewContainerContentView.addSubview(self.progressView) + self.previewContainerContentView.addSubview(self.previewBlurView) + self.previewContainerContentView.addSubview(self.loadingView) self.completion.connect { [weak self] result in if let self { @@ -837,7 +842,7 @@ public class VideoMessageCameraScreen: ViewController { private func animatePositionChange() { if let snapshotView = self.mainPreviewView.snapshotView(afterScreenUpdates: false) { - self.previewContainerView.insertSubview(snapshotView, belowSubview: self.progressView) + self.previewContainerContentView.insertSubview(snapshotView, belowSubview: self.progressView) self.previewSnapshotView = snapshotView let action = { [weak self] in @@ -872,7 +877,7 @@ public class VideoMessageCameraScreen: ViewController { func resumeCameraCapture() { if !self.mainPreviewView.isEnabled { if let snapshotView = self.resultPreviewView?.snapshotView(afterScreenUpdates: false) { - self.previewContainerView.insertSubview(snapshotView, belowSubview: self.previewBlurView) + self.previewContainerContentView.insertSubview(snapshotView, belowSubview: self.previewBlurView) self.previewSnapshotView = snapshotView } self.mainPreviewView.isEnabled = true @@ -1148,8 +1153,9 @@ public class VideoMessageCameraScreen: ViewController { } if !self.animatingIn { transition.setFrame(view: self.previewContainerView, frame: previewFrame) + transition.setFrame(view: self.previewContainerContentView, frame: CGRect(origin: CGPoint(), size: previewFrame.size)) } - transition.setCornerRadius(layer: self.previewContainerView.layer, cornerRadius: previewSide / 2.0) + transition.setCornerRadius(layer: self.previewContainerContentView.layer, cornerRadius: previewSide / 2.0) let previewBounds = CGRect(origin: .zero, size: previewFrame.size) @@ -1244,7 +1250,7 @@ public class VideoMessageCameraScreen: ViewController { }, transition: .easeInOut(duration: 0.2)) } } - self.previewContainerView.addSubview(resultPreviewView) + self.previewContainerContentView.addSubview(resultPreviewView) self.resultPreviewView = resultPreviewView resultPreviewView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) @@ -1730,6 +1736,10 @@ public class VideoMessageCameraScreen: ViewController { (self.displayNode as! Node).containerLayoutUpdated(layout: layout, transition: Transition(transition)) } } + + public func makeSendMessageContextPreview() -> ChatSendMessageContextScreenMediaPreview? { + return VideoMessageSendMessageContextPreview(node: self.node) + } } private func composition(with results: [VideoMessageCameraScreen.CaptureResult]) -> AVComposition { @@ -1797,3 +1807,84 @@ private class BlurView: UIVisualEffectView { self.setup() } } + +private final class VideoMessageSendMessageContextPreview: UIView, ChatSendMessageContextScreenMediaPreview { + var isReady: Signal { + return .single(true) + } + + var view: UIView { + return self + } + + var globalClippingRect: CGRect? { + return nil + } + + var layoutType: ChatSendMessageContextScreenMediaPreviewLayoutType { + return .videoMessage + } + + private weak var previewContainerContentParentView: UIView? + private let previewContainerContentView: UIView + + init(node: VideoMessageCameraScreen.Node) { + self.previewContainerContentParentView = node.previewContainerView + self.previewContainerContentView = node.previewContainerContentView + + super.init(frame: CGRect()) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + func animateIn(transition: Transition) { + self.addSubview(self.previewContainerContentView) + + guard let previewContainerContentParentView = self.previewContainerContentParentView else { + return + } + + let fromFrame = previewContainerContentParentView.convert(CGRect(origin: CGPoint(), size: self.previewContainerContentView.bounds.size), to: self) + let toFrame = self.previewContainerContentView.frame + + transition.animatePosition(view: self.previewContainerContentView, from: CGPoint(x: fromFrame.midX - toFrame.midX, y: fromFrame.midY - toFrame.midY), to: CGPoint(), additive: true) + } + + func animateOut(transition: Transition) { + guard let previewContainerContentParentView = self.previewContainerContentParentView else { + return + } + + let toFrame = previewContainerContentParentView.convert(CGRect(origin: CGPoint(), size: self.previewContainerContentView.bounds.size), to: self) + + let previewContainerContentView = self.previewContainerContentView + transition.setPosition(view: self.previewContainerContentView, position: toFrame.center, completion: { [weak previewContainerContentParentView, weak previewContainerContentView] _ in + guard let previewContainerContentParentView, let previewContainerContentView else { + return + } + + previewContainerContentView.frame = CGRect(origin: CGPoint(), size: previewContainerContentView.bounds.size) + previewContainerContentParentView.addSubview(previewContainerContentView) + }) + } + + func animateOutOnSend(transition: Transition) { + guard let previewContainerContentParentView = self.previewContainerContentParentView else { + return + } + + if let snapshotView = self.previewContainerContentView.snapshotView(afterScreenUpdates: false) { + self.addSubview(snapshotView) + transition.setAlpha(view: snapshotView, alpha: 0.0) + } + + self.previewContainerContentView.frame = CGRect(origin: CGPoint(), size: self.previewContainerContentView.bounds.size) + previewContainerContentParentView.addSubview(self.previewContainerContentView) + } + + func update(containerSize: CGSize, transition: Transition) -> CGSize { + return self.previewContainerContentView.bounds.size + } +} diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift index 28ae29b0c6..b20e3deef1 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift @@ -79,37 +79,61 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no let _ = ApplicationSpecificNotice.incrementSendWhenOnlineTip(accountManager: selfController.context.sharedContext.accountManager, count: 4).startStandalone() } - let controller = makeChatSendMessageActionSheetController(context: selfController.context, updatedPresentationData: selfController.updatedPresentationData, peerId: selfController.presentationInterfaceState.chatLocation.peerId, forwardMessageIds: selfController.presentationInterfaceState.interfaceState.forwardMessageIds, hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, sourceSendButton: node, textInputView: textInputView, emojiViewProvider: selfController.chatDisplayNode.textInputPanelNode?.emojiViewProvider, wallpaperBackgroundNode: selfController.chatDisplayNode.backgroundNode, canSendWhenOnline: sendWhenOnlineAvailable, completion: { [weak selfController] in - guard let selfController else { - return - } - selfController.supportedOrientations = previousSupportedOrientations - }, sendMessage: { [weak selfController] mode, messageEffect in - guard let selfController else { - return - } - switch mode { - case .generic: - selfController.controllerInteraction?.sendCurrentMessage(false, messageEffect.flatMap(ChatSendMessageEffect.init)) - case .silently: - selfController.controllerInteraction?.sendCurrentMessage(true, messageEffect.flatMap(ChatSendMessageEffect.init)) - case .whenOnline: - selfController.chatDisplayNode.sendCurrentMessage(scheduleTime: scheduleWhenOnlineTimestamp, messageEffect: messageEffect.flatMap(ChatSendMessageEffect.init)) { [weak selfController] in - guard let selfController else { - return - } - selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, saveInterfaceState: selfController.presentationInterfaceState.subject != .scheduledMessages, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) } - }) - selfController.openScheduledMessages() + var mediaPreview: ChatSendMessageContextScreenMediaPreview? + if let videoRecorderValue = selfController.videoRecorderValue { + mediaPreview = videoRecorderValue.makeSendMessageContextPreview() + } + + let controller = makeChatSendMessageActionSheetController( + context: selfController.context, + updatedPresentationData: selfController.updatedPresentationData, + peerId: selfController.presentationInterfaceState.chatLocation.peerId, + forwardMessageIds: selfController.presentationInterfaceState.interfaceState.forwardMessageIds, + hasEntityKeyboard: hasEntityKeyboard, + gesture: gesture, + sourceSendButton: node, + textInputView: textInputView, + mediaPreview: mediaPreview, + emojiViewProvider: selfController.chatDisplayNode.textInputPanelNode?.emojiViewProvider, + wallpaperBackgroundNode: selfController.chatDisplayNode.backgroundNode, + canSendWhenOnline: sendWhenOnlineAvailable, + completion: { [weak selfController] in + guard let selfController else { + return } - } - }, schedule: { [weak selfController] messageEffect in - guard let selfController else { - return - } - selfController.controllerInteraction?.scheduleCurrentMessage() - }, reactionItems: effectItems, availableMessageEffects: availableMessageEffects, isPremium: hasPremium) + selfController.supportedOrientations = previousSupportedOrientations + }, + sendMessage: { [weak selfController] mode, messageEffect in + guard let selfController else { + return + } + switch mode { + case .generic: + selfController.controllerInteraction?.sendCurrentMessage(false, messageEffect.flatMap(ChatSendMessageEffect.init)) + case .silently: + selfController.controllerInteraction?.sendCurrentMessage(true, messageEffect.flatMap(ChatSendMessageEffect.init)) + case .whenOnline: + selfController.chatDisplayNode.sendCurrentMessage(scheduleTime: scheduleWhenOnlineTimestamp, messageEffect: messageEffect.flatMap(ChatSendMessageEffect.init)) { [weak selfController] in + guard let selfController else { + return + } + selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, saveInterfaceState: selfController.presentationInterfaceState.subject != .scheduledMessages, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) } + }) + selfController.openScheduledMessages() + } + } + }, + schedule: { [weak selfController] messageEffect in + guard let selfController else { + return + } + selfController.controllerInteraction?.scheduleCurrentMessage() + }, + reactionItems: effectItems, + availableMessageEffects: availableMessageEffects, + isPremium: hasPremium + ) selfController.sendMessageActionsController = controller if layout.isNonExclusive { selfController.present(controller, in: .window(.root))