Message effects improvements

This commit is contained in:
Isaac 2024-05-13 20:14:15 +04:00
parent 3beaf8d580
commit 8736981248
43 changed files with 1045 additions and 505 deletions

View File

@ -123,7 +123,7 @@ public final class ViewController: UIViewController {
if #available(iOS 13.0, *) {
self.test = ReferenceCompareTest(view: self.view)
}
} else if "".isEmpty {
} else if !"".isEmpty {
let cachedAnimation = cacheLottieMetalAnimation(path: filePath)!
let animation = parseCachedLottieMetalAnimation(data: cachedAnimation)!

View File

@ -1068,6 +1068,29 @@ public protocol AccountGroupCallContext: AnyObject {
public protocol AccountGroupCallContextCache: AnyObject {
}
public final class ChatSendMessageActionSheetControllerMessageEffect {
public let id: Int64
public init(id: Int64) {
self.id = id
}
}
public enum ChatSendMessageActionSheetControllerSendMode {
case generic
case silently
case whenOnline
}
public protocol ChatSendMessageActionSheetControllerSourceSendButtonNode: ASDisplayNode {
func makeCustomContents() -> UIView?
}
public protocol ChatSendMessageActionSheetController: ViewController {
typealias SendMode = ChatSendMessageActionSheetControllerSendMode
typealias MessageEffect = ChatSendMessageActionSheetControllerMessageEffect
}
public protocol AccountContext: AnyObject {
var sharedContext: SharedAccountContext { get }
var account: Account { get }

View File

@ -147,7 +147,7 @@ public enum PeerSelectionControllerContext {
public protocol PeerSelectionController: ViewController {
var peerSelected: ((EnginePeer, Int64?) -> Void)? { get set }
var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?) -> Void)? { get set }
var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?, ChatSendMessageActionSheetController.MessageEffect?) -> Void)? { get set }
var inProgress: Bool { get set }
var customDismiss: (() -> Void)? { get set }
}

View File

@ -6,8 +6,10 @@ import TelegramCore
import TelegramPresentationData
import ContextUI
import ChatPresentationInterfaceState
import ComponentFlow
import AccountContext
final class AttachmentTextInputActionButtonsNode: ASDisplayNode {
final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageActionSheetControllerSourceSendButtonNode {
private let strings: PresentationStrings
let sendContainerNode: ASDisplayNode
@ -17,6 +19,8 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode {
var animatingSendButton = false
let textNode: ImmediateTextNode
private var theme: PresentationTheme
var sendButtonLongPressed: ((ASDisplayNode, ContextGesture) -> Void)?
private var gestureRecognizer: ContextGesture?
@ -31,9 +35,8 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode {
private var validLayout: CGSize?
init(presentationInterfaceState: ChatPresentationInterfaceState, presentController: @escaping (ViewController) -> Void) {
let theme = presentationInterfaceState.theme
let strings = presentationInterfaceState.strings
self.strings = strings
self.theme = presentationInterfaceState.theme
self.strings = presentationInterfaceState.strings
self.sendContainerNode = ASDisplayNode()
self.sendContainerNode.layer.allowsGroupOpacity = true
@ -64,9 +67,11 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode {
}
} else {
if highlighted {
strongSelf.sendContainerNode.layer.animateScale(from: 1.0, to: 0.75, duration: 0.4, removeOnCompletion: false)
} else if let presentationLayer = strongSelf.sendButton.layer.presentation() {
strongSelf.sendContainerNode.layer.animateScale(from: CGFloat((presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0), to: 1.0, duration: 0.25, removeOnCompletion: false)
let transition: Transition = .easeInOut(duration: 0.4)
transition.setScale(layer: strongSelf.sendContainerNode.layer, scale: 0.75)
} else {
let transition: Transition = .easeInOut(duration: 0.25)
transition.setScale(layer: strongSelf.sendContainerNode.layer, scale: 1.0)
}
}
}
@ -90,7 +95,7 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode {
return
}
if !strongSelf.sendButtonHasApplyIcon {
strongSelf.sendButtonLongPressed?(strongSelf.sendContainerNode, recognizer)
strongSelf.sendButtonLongPressed?(strongSelf, recognizer)
}
}
@ -112,7 +117,7 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode {
self.validLayout = size
let width: CGFloat
let textSize = self.textNode.updateLayout(CGSize(width: 100.0, height: size.height))
let textSize = self.textNode.updateLayout(CGSize(width: 100.0, height: 100.0))
if minimized {
width = 44.0
} else {
@ -140,4 +145,16 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode {
self.accessibilityLabel = self.strings.MediaPicker_Send
self.accessibilityHint = nil
}
func makeCustomContents() -> UIView? {
if !self.textNode.alpha.isZero {
let textView = ImmediateTextView()
textView.attributedText = NSAttributedString(string: self.strings.MediaPicker_Send, font: Font.semibold(17.0), textColor: self.theme.chat.inputPanel.actionControlForegroundColor)
let textSize = textView.updateLayout(CGSize(width: 100.0, height: 100.0))
let _ = textSize
textView.frame = self.textNode.frame
return textView
}
return nil
}
}

View File

@ -269,7 +269,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
private var validLayout: (CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, LayoutMetrics, Bool)?
public var sendMessage: (AttachmentTextInputPanelSendMode) -> Void = { _ in }
public var sendMessage: (AttachmentTextInputPanelSendMode, ChatSendMessageActionSheetController.MessageEffect?) -> Void = { _, _ in }
public var updateHeight: (Bool) -> Void = { _ in }
private var updatingInputState = false
@ -1843,7 +1843,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
sendPressed(effectiveInputText)
return
}
self.sendMessage(.generic)
self.sendMessage(.generic, nil)
}
@objc func textInputBackgroundViewTap(_ recognizer: UITapGestureRecognizer) {

View File

@ -14,6 +14,7 @@ import MediaResources
import LegacyMessageInputPanel
import LegacyMessageInputPanelInputView
import AttachmentTextInputPanelNode
import ChatSendMessageActionUI
public enum AttachmentButtonType: Equatable {
case gallery
@ -97,6 +98,7 @@ public protocol AttachmentContainable: ViewController {
var isContainerExpanded: () -> Bool { get set }
var isPanGestureEnabled: (() -> Bool)? { get }
var mediaPickerContext: AttachmentMediaPickerContext? { get }
var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? { get }
func isContainerPanningUpdated(_ panning: Bool)
@ -131,6 +133,10 @@ public extension AttachmentContainable {
var isPanGestureEnabled: (() -> Bool)? {
return nil
}
var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? {
return nil
}
}
public enum AttachmentMediaPickerSendMode {
@ -154,8 +160,8 @@ public protocol AttachmentMediaPickerContext {
func mainButtonAction()
func setCaption(_ caption: NSAttributedString)
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode)
func schedule()
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?)
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?)
}
private func generateShadowImage() -> UIImage? {
@ -417,17 +423,17 @@ public class AttachmentController: ViewController {
}
}
self.panel.sendMessagePressed = { [weak self] mode in
self.panel.sendMessagePressed = { [weak self] mode, messageEffect in
if let strongSelf = self {
switch mode {
case .generic:
strongSelf.mediaPickerContext?.send(mode: .generic, attachmentMode: .media)
strongSelf.mediaPickerContext?.send(mode: .generic, attachmentMode: .media, messageEffect: messageEffect)
case .silent:
strongSelf.mediaPickerContext?.send(mode: .silently, attachmentMode: .media)
strongSelf.mediaPickerContext?.send(mode: .silently, attachmentMode: .media, messageEffect: messageEffect)
case .schedule:
strongSelf.mediaPickerContext?.schedule()
strongSelf.mediaPickerContext?.schedule(messageEffect: messageEffect)
case .whenOnline:
strongSelf.mediaPickerContext?.send(mode: .whenOnline, attachmentMode: .media)
strongSelf.mediaPickerContext?.send(mode: .whenOnline, attachmentMode: .media, messageEffect: messageEffect)
}
}
}
@ -455,6 +461,14 @@ public class AttachmentController: ViewController {
strongSelf.controller?.presentInGlobalOverlay(c, with: nil)
}
}
self.panel.getCurrentSendMessageContextMediaPreview = { [weak self] in
guard let self, let currentController = self.currentControllers.last else {
return nil
}
return currentController.getCurrentSendMessageContextMediaPreview?()
}
}
deinit {

View File

@ -728,11 +728,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
var beganTextEditing: () -> Void = {}
var textUpdated: (NSAttributedString) -> Void = { _ in }
var sendMessagePressed: (AttachmentTextInputPanelSendMode) -> Void = { _ in }
var sendMessagePressed: (AttachmentTextInputPanelSendMode, ChatSendMessageActionSheetController.MessageEffect?) -> Void = { _, _ in }
var requestLayout: () -> Void = {}
var present: (ViewController) -> Void = { _ in }
var presentInGlobalOverlay: (ViewController) -> Void = { _ in }
var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)?
var mainButtonPressed: () -> Void = { }
init(context: AccountContext, chatLocation: ChatLocation?, isScheduledMessages: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) {
@ -967,20 +969,56 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
sendWhenOnlineAvailable = false
}
let controller = makeChatSendMessageActionSheetController(context: strongSelf.context, peerId: strongSelf.presentationInterfaceState.chatLocation.peerId, forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds, hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, sourceSendButton: node, textInputView: textInputNode.textView, emojiViewProvider: textInputPanelNode.emojiViewProvider, attachment: true, canSendWhenOnline: sendWhenOnlineAvailable, completion: {
}, sendMessage: { [weak textInputPanelNode] mode, _ in
switch mode {
case .generic:
textInputPanelNode?.sendMessage(.generic)
case .silently:
textInputPanelNode?.sendMessage(.silent)
case .whenOnline:
textInputPanelNode?.sendMessage(.whenOnline)
let mediaPreview = strongSelf.getCurrentSendMessageContextMediaPreview?()
let isReady: Signal<Bool, NoError>
if let mediaPreview {
isReady = mediaPreview.isReady
|> filter { $0 }
|> take(1)
|> timeout(0.5, queue: .mainQueue(), alternate: .single(true))
} else {
isReady = .single(true)
}
let _ = (isReady
|> deliverOnMainQueue).start(next: { [weak strongSelf] _ in
guard let strongSelf else {
return
}
}, schedule: { [weak textInputPanelNode] _ in
textInputPanelNode?.sendMessage(.schedule)
}, reactionItems: effectItems, availableMessageEffects: availableMessageEffects, isPremium: hasPremium)
strongSelf.presentInGlobalOverlay(controller)
let controller = makeChatSendMessageActionSheetController(
context: strongSelf.context,
peerId: strongSelf.presentationInterfaceState.chatLocation.peerId,
forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds,
hasEntityKeyboard: hasEntityKeyboard,
gesture: gesture,
sourceSendButton: node,
textInputView: textInputNode.textView,
mediaPreview: mediaPreview,
emojiViewProvider: textInputPanelNode.emojiViewProvider,
attachment: true,
canSendWhenOnline: sendWhenOnlineAvailable,
completion: {
},
sendMessage: { [weak textInputPanelNode] mode, messageEffect in
switch mode {
case .generic:
textInputPanelNode?.sendMessage(.generic, messageEffect)
case .silently:
textInputPanelNode?.sendMessage(.silent, messageEffect)
case .whenOnline:
textInputPanelNode?.sendMessage(.whenOnline, messageEffect)
}
},
schedule: { [weak textInputPanelNode] messageEffect in
textInputPanelNode?.sendMessage(.schedule, messageEffect)
},
reactionItems: effectItems,
availableMessageEffects: availableMessageEffects,
isPremium: hasPremium
)
strongSelf.presentInGlobalOverlay(controller)
})
})
}, openScheduledMessages: {
}, openPeersNearby: {
@ -1249,9 +1287,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
}
}, makeEntityInputView: self.makeEntityInputView)
textInputPanelNode.interfaceInteraction = self.interfaceInteraction
textInputPanelNode.sendMessage = { [weak self] mode in
textInputPanelNode.sendMessage = { [weak self] mode, messageEffect in
if let strongSelf = self {
strongSelf.sendMessagePressed(mode)
strongSelf.sendMessagePressed(mode, messageEffect)
}
}
textInputPanelNode.focusUpdated = { [weak self] focus in

View File

@ -1340,7 +1340,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.context.engine.messages.ensureMessagesAreLocallyAvailable(messages: messages.values.filter { messageIds.contains($0.id) })
let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.onlyWriteable, .excludeDisabled], multipleSelection: true, selectForumThreads: true))
peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions in
peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions, _ in
guard let strongSelf = self, let strongController = peerSelectionController else {
return
}

View File

@ -11,25 +11,6 @@ import TextFormat
import ReactionSelectionNode
import WallpaperBackgroundNode
public enum ChatSendMessageActionSheetControllerSendMode {
case generic
case silently
case whenOnline
}
public final class ChatSendMessageActionSheetControllerMessageEffect {
public let id: Int64
public init(id: Int64) {
self.id = id
}
}
public protocol ChatSendMessageActionSheetController: ViewController {
typealias SendMode = ChatSendMessageActionSheetControllerSendMode
typealias MessageEffect = ChatSendMessageActionSheetControllerMessageEffect
}
private final class ChatSendMessageActionSheetControllerImpl: ViewController, ChatSendMessageActionSheetController {
private var controllerNode: ChatSendMessageActionSheetControllerNode {
return self.displayNode as! ChatSendMessageActionSheetControllerNode
@ -203,6 +184,7 @@ public func makeChatSendMessageActionSheetController(
gesture: ContextGesture,
sourceSendButton: ASDisplayNode,
textInputView: UITextView,
mediaPreview: ChatSendMessageContextScreenMediaPreview? = nil,
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
wallpaperBackgroundNode: WallpaperBackgroundNode? = nil,
attachment: Bool = false,
@ -214,7 +196,7 @@ public func makeChatSendMessageActionSheetController(
availableMessageEffects: AvailableMessageEffects? = nil,
isPremium: Bool = false
) -> ChatSendMessageActionSheetController {
if textInputView.text.isEmpty {
if textInputView.text.isEmpty && !"".isEmpty {
return ChatSendMessageActionSheetControllerImpl(
context: context,
updatedPresentationData: updatedPresentationData,
@ -245,6 +227,7 @@ public func makeChatSendMessageActionSheetController(
gesture: gesture,
sourceSendButton: sourceSendButton,
textInputView: textInputView,
mediaPreview: mediaPreview,
emojiViewProvider: emojiViewProvider,
wallpaperBackgroundNode: wallpaperBackgroundNode,
attachment: attachment,

View File

@ -32,10 +32,21 @@ func convertFrame(_ frame: CGRect, from fromView: UIView, to toView: UIView) ->
return targetWindowFrame
}
public protocol ChatSendMessageContextScreenMediaPreview: AnyObject {
var isReady: Signal<Bool, NoError> { get }
var view: UIView { get }
var globalClippingRect: CGRect? { get }
func animateIn(transition: Transition)
func animateOut(transition: Transition)
func update(containerSize: CGSize, transition: Transition) -> CGSize
}
final class ChatSendMessageContextScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
let peerId: EnginePeer.Id?
let isScheduledMessages: Bool
let forwardMessageIds: [EngineMessage.Id]?
@ -43,6 +54,7 @@ final class ChatSendMessageContextScreenComponent: Component {
let gesture: ContextGesture
let sourceSendButton: ASDisplayNode
let textInputView: UITextView
let mediaPreview: ChatSendMessageContextScreenMediaPreview?
let emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?
let wallpaperBackgroundNode: WallpaperBackgroundNode?
let attachment: Bool
@ -56,6 +68,7 @@ final class ChatSendMessageContextScreenComponent: Component {
init(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
peerId: EnginePeer.Id?,
isScheduledMessages: Bool,
forwardMessageIds: [EngineMessage.Id]?,
@ -63,6 +76,7 @@ final class ChatSendMessageContextScreenComponent: Component {
gesture: ContextGesture,
sourceSendButton: ASDisplayNode,
textInputView: UITextView,
mediaPreview: ChatSendMessageContextScreenMediaPreview?,
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
wallpaperBackgroundNode: WallpaperBackgroundNode?,
attachment: Bool,
@ -75,6 +89,7 @@ final class ChatSendMessageContextScreenComponent: Component {
isPremium: Bool
) {
self.context = context
self.updatedPresentationData = updatedPresentationData
self.peerId = peerId
self.isScheduledMessages = isScheduledMessages
self.forwardMessageIds = forwardMessageIds
@ -82,6 +97,7 @@ final class ChatSendMessageContextScreenComponent: Component {
self.gesture = gesture
self.sourceSendButton = sourceSendButton
self.textInputView = textInputView
self.mediaPreview = mediaPreview
self.emojiViewProvider = emojiViewProvider
self.wallpaperBackgroundNode = wallpaperBackgroundNode
self.attachment = attachment
@ -141,6 +157,7 @@ final class ChatSendMessageContextScreenComponent: Component {
private var standaloneReactionAnimation: AnimatedStickerNode?
private var isLoadingEffectAnimation: Bool = false
private var isLoadingEffectAnimationTimerDisposable: Disposable?
private var loadEffectAnimationDisposable: Disposable?
private var presentationAnimationState: PresentationAnimationState = .initial
@ -177,6 +194,7 @@ final class ChatSendMessageContextScreenComponent: Component {
deinit {
self.messageEffectDisposable.dispose()
self.loadEffectAnimationDisposable?.dispose()
self.isLoadingEffectAnimationTimerDisposable?.dispose()
}
@objc private func onBackgroundTap(_ recognizer: UITapGestureRecognizer) {
@ -196,6 +214,8 @@ final class ChatSendMessageContextScreenComponent: Component {
func animateIn() {
if case .initial = self.presentationAnimationState {
HapticFeedback().impact()
self.presentationAnimationState = .animatedIn
self.state?.updated(transition: .spring(duration: 0.42))
}
@ -270,7 +290,7 @@ final class ChatSendMessageContextScreenComponent: Component {
self.environment = environment
self.state = state
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
let presentationData = component.updatedPresentationData?.initial ?? component.context.sharedContext.currentPresentationData.with({ $0 })
if themeUpdated {
self.backgroundView.updateColor(
@ -298,15 +318,6 @@ final class ChatSendMessageContextScreenComponent: Component {
let sourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton.view, to: self)
sendButton.update(
context: component.context,
presentationData: presentationData,
backgroundNode: component.wallpaperBackgroundNode,
isLoadingEffectAnimation: self.isLoadingEffectAnimation,
size: sourceSendButtonFrame.size,
transition: transition
)
let sendButtonScale: CGFloat
switch self.presentationAnimationState {
case .initial:
@ -389,7 +400,8 @@ final class ChatSendMessageContextScreenComponent: Component {
guard let self, let component = self.component else {
return
}
component.schedule(nil)
self.animateOutToEmpty = true
component.schedule(self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.MessageEffect(id: $0.id) }))
self.environment?.controller()?.dismiss()
}
)))
@ -476,10 +488,13 @@ final class ChatSendMessageContextScreenComponent: Component {
backgroundNode: component.wallpaperBackgroundNode,
textString: textString,
sourceTextInputView: component.textInputView as? ChatInputTextView,
emojiViewProvider: component.emojiViewProvider,
sourceMediaPreview: component.mediaPreview,
textInsets: messageTextInsets,
explicitBackgroundSize: explicitMessageBackgroundSize,
maxTextWidth: localSourceTextInputViewFrame.width,
maxTextHeight: maxTextHeight,
containerSize: CGSize(width: availableSize.width - 16.0 - 40.0, height: availableSize.height),
effect: self.presentationAnimationState.key == .animatedIn ? self.selectedMessageEffect : nil,
transition: transition
)
@ -586,6 +601,8 @@ final class ChatSendMessageContextScreenComponent: Component {
self.selectedMessageEffect = nil
reactionContextNode.selectedItems = Set([])
self.loadEffectAnimationDisposable?.dispose()
self.isLoadingEffectAnimationTimerDisposable?.dispose()
self.isLoadingEffectAnimationTimerDisposable = nil
self.isLoadingEffectAnimation = false
if let standaloneReactionAnimation = self.standaloneReactionAnimation {
@ -619,10 +636,16 @@ final class ChatSendMessageContextScreenComponent: Component {
}
self.loadEffectAnimationDisposable?.dispose()
self.isLoadingEffectAnimation = true
if !self.isUpdating {
self.state?.updated(transition: .easeInOut(duration: 0.2))
}
self.isLoadingEffectAnimationTimerDisposable?.dispose()
self.isLoadingEffectAnimationTimerDisposable = (Signal<Never, NoError>.complete() |> delay(0.2, queue: .mainQueue()) |> deliverOnMainQueue).startStrict(completed: { [weak self] in
guard let self else {
return
}
self.isLoadingEffectAnimation = true
if !self.isUpdating {
self.state?.updated(transition: .easeInOut(duration: 0.2))
}
})
if let standaloneReactionAnimation = self.standaloneReactionAnimation {
self.standaloneReactionAnimation = nil
@ -666,9 +689,9 @@ final class ChatSendMessageContextScreenComponent: Component {
dataDisposabke.dispose()
}
}
#if DEBUG
/*#if DEBUG
loadEffectAnimationSignal = loadEffectAnimationSignal |> delay(1.0, queue: .mainQueue())
#endif
#endif*/
self.loadEffectAnimationDisposable = (loadEffectAnimationSignal
|> deliverOnMainQueue).start(completed: { [weak self] in
@ -676,6 +699,8 @@ final class ChatSendMessageContextScreenComponent: Component {
return
}
self.isLoadingEffectAnimationTimerDisposable?.dispose()
self.isLoadingEffectAnimationTimerDisposable = nil
self.isLoadingEffectAnimation = false
guard let targetView = self.messageItemView?.effectIconView else {
@ -689,7 +714,11 @@ final class ChatSendMessageContextScreenComponent: Component {
#if targetEnvironment(simulator)
standaloneReactionAnimation = DirectAnimatedStickerNode()
#else
standaloneReactionAnimation = LottieMetalAnimatedStickerNode()
if "".isEmpty {
standaloneReactionAnimation = DirectAnimatedStickerNode()
} else {
standaloneReactionAnimation = LottieMetalAnimatedStickerNode()
}
#endif
standaloneReactionAnimation.isUserInteractionEnabled = false
@ -728,7 +757,7 @@ final class ChatSendMessageContextScreenComponent: Component {
return
}
//TODO:localize
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
let presentationData = component.updatedPresentationData?.initial ?? component.context.sharedContext.currentPresentationData.with({ $0 })
self.environment?.controller()?.present(UndoOverlayController(
presentationData: presentationData,
content: .premiumPaywall(
@ -764,9 +793,11 @@ final class ChatSendMessageContextScreenComponent: Component {
}
}
let sourceActionsStackFrame = CGRect(origin: CGPoint(x: sourceSendButtonFrame.minX + 1.0 - actionsStackSize.width, y: sourceMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize)
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)
let sourceActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - actionsStackSize.width, y: sourceMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize)
var readySendButtonFrame = CGRect(origin: CGPoint(x: sourceSendButtonFrame.minX, y: sourceSendButtonFrame.minY), size: sourceSendButtonFrame.size)
var readyMessageItemFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 8.0 - messageItemSize.width, 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)
@ -789,8 +820,13 @@ final class ChatSendMessageContextScreenComponent: Component {
let sendButtonFrame: CGRect
switch self.presentationAnimationState {
case .initial:
messageItemFrame = sourceMessageItemFrame
actionsStackFrame = sourceActionsStackFrame
if component.mediaPreview != nil {
messageItemFrame = readyMessageItemFrame
actionsStackFrame = readyActionsStackFrame
} else {
messageItemFrame = sourceMessageItemFrame
actionsStackFrame = sourceActionsStackFrame
}
sendButtonFrame = sourceSendButtonFrame
case .animatedOut:
if self.animateOutToEmpty {
@ -798,8 +834,13 @@ final class ChatSendMessageContextScreenComponent: Component {
actionsStackFrame = readyActionsStackFrame
sendButtonFrame = readySendButtonFrame
} else {
messageItemFrame = sourceMessageItemFrame
actionsStackFrame = sourceActionsStackFrame
if component.mediaPreview != nil {
messageItemFrame = readyMessageItemFrame
actionsStackFrame = readyActionsStackFrame
} else {
messageItemFrame = sourceMessageItemFrame
actionsStackFrame = sourceActionsStackFrame
}
sendButtonFrame = sourceSendButtonFrame
}
case .animatedIn:
@ -809,6 +850,13 @@ final class ChatSendMessageContextScreenComponent: Component {
}
transition.setFrame(view: messageItemView, frame: messageItemFrame)
messageItemView.updateClippingRect(
sourceMediaPreview: component.mediaPreview,
isAnimatedIn: self.presentationAnimationState.key == .animatedIn,
localFrame: messageItemFrame,
containerSize: availableSize,
transition: transition
)
transition.setPosition(view: actionsStackNode.view, position: CGPoint(x: actionsStackFrame.maxX, y: actionsStackFrame.minY))
transition.setBounds(view: actionsStackNode.view, bounds: CGRect(origin: CGPoint(), size: actionsStackFrame.size))
@ -820,9 +868,13 @@ final class ChatSendMessageContextScreenComponent: Component {
transition.setAlpha(view: actionsStackNode.view, alpha: 1.0)
Transition.immediate.setScale(view: actionsStackNode.view, scale: 1.0)
actionsStackNode.layer.animateSpring(from: 0.001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.42, damping: 104.0)
messageItemView.animateIn(transition: transition)
case .animatedOut:
transition.setAlpha(view: actionsStackNode.view, alpha: 0.0)
transition.setScale(view: actionsStackNode.view, scale: 0.001)
messageItemView.animateOut(transition: transition)
}
} else {
switch self.presentationAnimationState {
@ -837,7 +889,11 @@ final class ChatSendMessageContextScreenComponent: Component {
if let reactionContextNode = self.reactionContextNode {
let size = availableSize
let reactionsAnchorRect = messageItemFrame
var reactionsAnchorRect = messageItemFrame
if component.mediaPreview != nil {
reactionsAnchorRect.size.width += 100.0
reactionsAnchorRect.origin.x -= 4.0
}
transition.setFrame(view: reactionContextNode.view, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
reactionContextNode.updateLayout(size: size, insets: UIEdgeInsets(), anchorRect: reactionsAnchorRect, centerAligned: false, isCoveredByInput: false, isAnimatingOut: false, transition: transition.containedViewLayoutTransition)
reactionContextNode.updateIsIntersectingContent(isIntersectingContent: false, transition: .immediate)
@ -856,6 +912,16 @@ final class ChatSendMessageContextScreenComponent: Component {
}
}
sendButton.update(
context: component.context,
presentationData: presentationData,
backgroundNode: component.wallpaperBackgroundNode,
sourceSendButton: component.sourceSendButton,
isAnimatedIn: self.presentationAnimationState.key == .animatedIn,
isLoadingEffectAnimation: self.isLoadingEffectAnimation,
size: sendButtonFrame.size,
transition: transition
)
transition.setPosition(view: sendButton, position: sendButtonFrame.center)
transition.setBounds(view: sendButton, bounds: CGRect(origin: CGPoint(), size: sendButtonFrame.size))
transition.setScale(view: sendButton, scale: sendButtonScale)
@ -866,21 +932,32 @@ final class ChatSendMessageContextScreenComponent: Component {
let backgroundAlpha: CGFloat
switch self.presentationAnimationState {
case .animatedIn:
if previousAnimationState.key == .initial && self.initializationDisplayLink == nil {
self.initializationDisplayLink = SharedDisplayLinkDriver.shared.add({ [weak self] _ in
guard let self else {
return
}
if previousAnimationState.key == .initial {
if environment.inputHeight != 0.0 {
if self.initializationDisplayLink == nil {
self.initializationDisplayLink = SharedDisplayLinkDriver.shared.add({ [weak self] _ in
guard let self else {
return
}
self.initializationDisplayLink?.invalidate()
self.initializationDisplayLink = nil
self.initializationDisplayLink?.invalidate()
self.initializationDisplayLink = nil
guard let component = self.component else {
return
guard let component = self.component else {
return
}
if component.mediaPreview == nil {
component.textInputView.isHidden = true
}
component.sourceSendButton.isHidden = true
})
}
} else {
if component.mediaPreview == nil {
component.textInputView.isHidden = true
}
component.textInputView.isHidden = true
component.sourceSendButton.isHidden = true
})
}
}
backgroundAlpha = 1.0
@ -888,7 +965,9 @@ final class ChatSendMessageContextScreenComponent: Component {
backgroundAlpha = 0.0
if self.animateOutToEmpty {
component.textInputView.isHidden = false
if component.mediaPreview == nil {
component.textInputView.isHidden = false
}
component.sourceSendButton.isHidden = false
transition.setAlpha(view: sendButton, alpha: 0.0)
@ -906,7 +985,9 @@ final class ChatSendMessageContextScreenComponent: Component {
}
if case let .animatedOut(completion) = self.presentationAnimationState {
if let component = self.component, !self.animateOutToEmpty {
component.textInputView.isHidden = false
if component.mediaPreview == nil {
component.textInputView.isHidden = false
}
component.sourceSendButton.isHidden = false
}
completion()
@ -950,6 +1031,7 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
gesture: ContextGesture,
sourceSendButton: ASDisplayNode,
textInputView: UITextView,
mediaPreview: ChatSendMessageContextScreenMediaPreview?,
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
wallpaperBackgroundNode: WallpaperBackgroundNode?,
attachment: Bool,
@ -967,6 +1049,7 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
context: context,
component: ChatSendMessageContextScreenComponent(
context: context,
updatedPresentationData: updatedPresentationData,
peerId: peerId,
isScheduledMessages: isScheduledMessages,
forwardMessageIds: forwardMessageIds,
@ -974,6 +1057,7 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
gesture: gesture,
sourceSendButton: sourceSendButton,
textInputView: textInputView,
mediaPreview: mediaPreview,
emojiViewProvider: emojiViewProvider,
wallpaperBackgroundNode: wallpaperBackgroundNode,
attachment: attachment,
@ -987,18 +1071,12 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
),
navigationBarAppearance: .none,
statusBarStyle: .none,
presentationMode: .default
presentationMode: .default,
updatedPresentationData: updatedPresentationData
)
self.lockOrientation = true
self.blocksBackgroundWhenInOverlay = true
/*gesture.externalEnded = { [weak self] _ in
guard let self else {
return
}
self.dismiss()
}*/
}
required public init(coder aDecoder: NSCoder) {

View File

@ -97,10 +97,12 @@ private final class EffectIcon: Component {
textView = ComponentView()
self.textView = textView
}
let textInsets = UIEdgeInsets(top: 2.0, left: 2.0, bottom: 2.0, right: 2.0)
let textSize = textView.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: text, font: Font.regular(10.0), textColor: .black))
text: .plain(NSAttributedString(string: text, font: Font.regular(10.0), textColor: .black)),
insets: textInsets
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
@ -138,12 +140,19 @@ final class MessageItemView: UIView {
private let textClippingContainer: UIView
private var textNode: ChatInputTextNode?
private var customEmojiContainerView: CustomEmojiContainerView?
private var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?
private var mediaPreviewClippingView: UIView?
private var mediaPreview: ChatSendMessageContextScreenMediaPreview?
private var effectIcon: ComponentView<Empty>?
var effectIconView: UIView? {
return self.effectIcon?.view
}
private var effectIconBackgroundView: UIImageView?
private var chatTheme: ChatPresentationThemeData?
private var currentSize: CGSize?
@ -167,19 +176,36 @@ final class MessageItemView: UIView {
preconditionFailure()
}
func animateIn(transition: Transition) {
if let mediaPreview = self.mediaPreview {
mediaPreview.animateIn(transition: transition)
}
}
func animateOut(transition: Transition) {
if let mediaPreview = self.mediaPreview {
mediaPreview.animateOut(transition: transition)
}
}
func update(
context: AccountContext,
presentationData: PresentationData,
backgroundNode: WallpaperBackgroundNode?,
textString: NSAttributedString,
sourceTextInputView: ChatInputTextView?,
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
sourceMediaPreview: ChatSendMessageContextScreenMediaPreview?,
textInsets: UIEdgeInsets,
explicitBackgroundSize: CGSize?,
maxTextWidth: CGFloat,
maxTextHeight: CGFloat,
containerSize: CGSize,
effect: AvailableMessageEffects.MessageEffect?,
transition: Transition
) -> CGSize {
self.emojiViewProvider = emojiViewProvider
var effectIconSize: CGSize?
if let effect {
let effectIcon: ComponentView<Empty>
@ -206,89 +232,6 @@ final class MessageItemView: UIView {
)
}
var textCutout: TextNodeCutout?
if let effectIconSize {
textCutout = TextNodeCutout(bottomRight: CGSize(width: effectIconSize.width + 4.0, height: effectIconSize.height))
}
let _ = textCutout
let textNode: ChatInputTextNode
if let current = self.textNode {
textNode = current
} else {
textNode = ChatInputTextNode(disableTiling: true)
textNode.textView.isScrollEnabled = false
textNode.isUserInteractionEnabled = false
self.textNode = textNode
self.textClippingContainer.addSubview(textNode.view)
if let sourceTextInputView {
textNode.textView.defaultTextContainerInset = sourceTextInputView.defaultTextContainerInset
}
let messageAttributedText = NSMutableAttributedString(attributedString: textString)
messageAttributedText.addAttribute(NSAttributedString.Key.foregroundColor, value: presentationData.theme.chat.message.outgoing.primaryTextColor, range: NSMakeRange(0, (messageAttributedText.string as NSString).length))
textNode.attributedText = messageAttributedText
}
let mainColor = presentationData.theme.chat.message.outgoing.accentControlColor
let mappedLineStyle: ChatInputTextView.Theme.Quote.LineStyle
if let sourceTextInputView, let textTheme = sourceTextInputView.theme {
switch textTheme.quote.lineStyle {
case .solid:
mappedLineStyle = .solid(color: mainColor)
case .doubleDashed:
mappedLineStyle = .doubleDashed(mainColor: mainColor, secondaryColor: .clear)
case .tripleDashed:
mappedLineStyle = .tripleDashed(mainColor: mainColor, secondaryColor: .clear, tertiaryColor: .clear)
}
} else {
mappedLineStyle = .solid(color: mainColor)
}
textNode.textView.theme = ChatInputTextView.Theme(
quote: ChatInputTextView.Theme.Quote(
background: mainColor.withMultipliedAlpha(0.1),
foreground: mainColor,
lineStyle: mappedLineStyle,
codeBackground: mainColor.withMultipliedAlpha(0.1),
codeForeground: mainColor
)
)
let textPositioningInsets = UIEdgeInsets(top: -5.0, left: 0.0, bottom: -4.0, right: -4.0)
var currentRightInset: CGFloat = 0.0
if let sourceTextInputView {
currentRightInset = sourceTextInputView.currentRightInset
}
let textHeight = textNode.textHeightForWidth(maxTextWidth, rightInset: currentRightInset)
textNode.updateLayout(size: CGSize(width: maxTextWidth, height: textHeight))
let textBoundingRect = textNode.textView.currentTextBoundingRect().integral
let lastLineBoundingRect = textNode.textView.lastLineBoundingRect().integral
let textWidth = textBoundingRect.width
let textSize = CGSize(width: textWidth, height: textHeight)
var positionedTextSize = CGSize(width: textSize.width + textPositioningInsets.left + textPositioningInsets.right, height: textSize.height + textPositioningInsets.top + textPositioningInsets.bottom)
let effectInset: CGFloat = 12.0
if effect != nil, lastLineBoundingRect.width > textSize.width - effectInset {
if lastLineBoundingRect != textBoundingRect {
positionedTextSize.height += 11.0
} else {
positionedTextSize.width += effectInset
}
}
let unclippedPositionedTextHeight = positionedTextSize.height - (textPositioningInsets.top + textPositioningInsets.bottom)
positionedTextSize.height = min(positionedTextSize.height, maxTextHeight)
let size = CGSize(width: positionedTextSize.width + textInsets.left + textInsets.right, height: positionedTextSize.height + textInsets.top + textInsets.bottom)
let textFrame = CGRect(origin: CGPoint(x: textInsets.left, y: textInsets.top), size: positionedTextSize)
let chatTheme: ChatPresentationThemeData
if let current = self.chatTheme, current.theme === presentationData.theme {
chatTheme = current
@ -305,89 +248,329 @@ final class MessageItemView: UIView {
maskMode: true,
backgroundNode: backgroundNode
)
self.backgroundNode.setType(
type: .outgoing(.None),
highlighted: false,
graphics: themeGraphics,
maskMode: true,
hasWallpaper: true,
transition: transition.containedViewLayoutTransition,
backgroundNode: backgroundNode
)
let backgroundSize = explicitBackgroundSize ?? size
let previousSize = self.currentSize
self.currentSize = backgroundSize
let textClippingContainerFrame = CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: backgroundSize.width - 1.0 - 7.0, height: backgroundSize.height - 1.0 - 1.0))
var textClippingContainerBounds = CGRect(origin: CGPoint(), size: textClippingContainerFrame.size)
if explicitBackgroundSize != nil, let sourceTextInputView {
textClippingContainerBounds.origin.y = sourceTextInputView.contentOffset.y
} else {
textClippingContainerBounds.origin.y = unclippedPositionedTextHeight - backgroundSize.height + 4.0
textClippingContainerBounds.origin.y = max(0.0, textClippingContainerBounds.origin.y)
}
transition.setPosition(view: self.textClippingContainer, position: textClippingContainerFrame.center)
transition.setBounds(view: self.textClippingContainer, bounds: textClippingContainerBounds)
textNode.view.frame = CGRect(origin: CGPoint(x: textFrame.minX + textPositioningInsets.left - textClippingContainerFrame.minX, y: textFrame.minY + textPositioningInsets.top - textClippingContainerFrame.minY), size: CGSize(width: maxTextWidth, height: textHeight))
if let effectIcon = self.effectIcon, let effectIconSize {
if let effectIconView = effectIcon.view {
var animateIn = false
if effectIconView.superview == nil {
animateIn = true
self.addSubview(effectIconView)
}
let effectIconFrame = CGRect(origin: CGPoint(x: backgroundSize.width - textInsets.right + 2.0 - effectIconSize.width, y: backgroundSize.height - textInsets.bottom - 2.0 - effectIconSize.height), size: effectIconSize)
if animateIn {
if let previousSize {
let previousEffectIconFrame = CGRect(origin: CGPoint(x: previousSize.width - textInsets.right + 2.0 - effectIconSize.width, y: previousSize.height - textInsets.bottom - 2.0 - effectIconSize.height), size: effectIconSize)
effectIconView.frame = previousEffectIconFrame
} else {
effectIconView.frame = effectIconFrame
}
transition.animateAlpha(view: effectIconView, from: 0.0, to: 1.0)
if !transition.animation.isImmediate {
effectIconView.layer.animateSpring(from: 0.001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
}
}
transition.setFrame(view: effectIconView, frame: effectIconFrame)
if let sourceMediaPreview {
let mediaPreviewClippingView: UIView
if let current = self.mediaPreviewClippingView {
mediaPreviewClippingView = current
} else {
mediaPreviewClippingView = UIView()
mediaPreviewClippingView.layer.anchorPoint = CGPoint()
mediaPreviewClippingView.clipsToBounds = true
mediaPreviewClippingView.isUserInteractionEnabled = false
self.mediaPreviewClippingView = mediaPreviewClippingView
self.addSubview(mediaPreviewClippingView)
}
} else {
if let effectIcon = self.effectIcon {
self.effectIcon = nil
if self.mediaPreview !== sourceMediaPreview {
self.mediaPreview?.view.removeFromSuperview()
self.mediaPreview = nil
self.mediaPreview = sourceMediaPreview
if let mediaPreview = self.mediaPreview {
mediaPreviewClippingView.addSubview(mediaPreview.view)
}
}
let mediaPreviewSize = sourceMediaPreview.update(containerSize: containerSize, transition: transition)
let backgroundSize = CGSize(width: mediaPreviewSize.width + 7.0, height: mediaPreviewSize.height)
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 {
if let effectIconView = effectIcon.view {
let effectIconSize = effectIconView.bounds.size
let effectIconFrame = CGRect(origin: CGPoint(x: backgroundSize.width - textInsets.right - effectIconSize.width, y: backgroundSize.height - textInsets.bottom - effectIconSize.height), size: effectIconSize)
var animateIn = false
if effectIconView.superview == nil {
animateIn = true
self.addSubview(effectIconView)
}
let effectIconBackgroundView: UIImageView
if let current = self.effectIconBackgroundView {
effectIconBackgroundView = current
} else {
effectIconBackgroundView = UIImageView()
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 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)
if animateIn {
effectIconView.frame = effectIconFrame
effectIconBackgroundView.frame = effectIconBackgroundFrame
effectIconBackgroundView.layer.cornerRadius = effectIconBackgroundFrame.height * 0.5
transition.animateAlpha(view: effectIconView, from: 0.0, to: 1.0)
if !transition.animation.isImmediate {
effectIconView.layer.animateSpring(from: 0.001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
}
transition.animateAlpha(view: effectIconBackgroundView, from: 0.0, to: 1.0)
}
transition.setFrame(view: effectIconView, frame: effectIconFrame)
transition.setScale(view: effectIconView, scale: 0.001)
transition.setAlpha(view: effectIconView, alpha: 0.0, completion: { [weak effectIconView] _ in
effectIconView?.removeFromSuperview()
transition.setFrame(view: effectIconBackgroundView, frame: effectIconBackgroundFrame)
transition.setCornerRadius(layer: effectIconBackgroundView.layer, cornerRadius: effectIconBackgroundFrame.height * 0.5)
}
} else {
if let effectIcon = self.effectIcon {
self.effectIcon = nil
if let effectIconView = effectIcon.view {
transition.setScale(view: effectIconView, scale: 0.001)
transition.setAlpha(view: effectIconView, alpha: 0.0, completion: { [weak effectIconView] _ in
effectIconView?.removeFromSuperview()
})
}
}
if let effectIconBackgroundView = self.effectIconBackgroundView {
self.effectIconBackgroundView = nil
transition.setAlpha(view: effectIconBackgroundView, alpha: 0.0, completion: { [weak effectIconBackgroundView] _ in
effectIconBackgroundView?.removeFromSuperview()
})
}
}
}
let backgroundAlpha: CGFloat
if explicitBackgroundSize != nil {
backgroundAlpha = 0.0
return backgroundSize
} else {
backgroundAlpha = 1.0
let textNode: ChatInputTextNode
if let current = self.textNode {
textNode = current
} else {
textNode = ChatInputTextNode(disableTiling: true)
textNode.textView.isScrollEnabled = false
textNode.isUserInteractionEnabled = false
self.textNode = textNode
self.textClippingContainer.addSubview(textNode.view)
if let sourceTextInputView {
textNode.textView.defaultTextContainerInset = sourceTextInputView.defaultTextContainerInset
}
let messageAttributedText = NSMutableAttributedString(attributedString: textString)
//messageAttributedText.addAttribute(NSAttributedString.Key.foregroundColor, value: presentationData.theme.chat.message.outgoing.primaryTextColor, range: NSMakeRange(0, (messageAttributedText.string as NSString).length))
textNode.attributedText = messageAttributedText
}
let mainColor = presentationData.theme.chat.message.outgoing.accentControlColor
let mappedLineStyle: ChatInputTextView.Theme.Quote.LineStyle
if let sourceTextInputView, let textTheme = sourceTextInputView.theme {
switch textTheme.quote.lineStyle {
case .solid:
mappedLineStyle = .solid(color: mainColor)
case .doubleDashed:
mappedLineStyle = .doubleDashed(mainColor: mainColor, secondaryColor: .clear)
case .tripleDashed:
mappedLineStyle = .tripleDashed(mainColor: mainColor, secondaryColor: .clear, tertiaryColor: .clear)
}
} else {
mappedLineStyle = .solid(color: mainColor)
}
textNode.textView.theme = ChatInputTextView.Theme(
quote: ChatInputTextView.Theme.Quote(
background: mainColor.withMultipliedAlpha(0.1),
foreground: mainColor,
lineStyle: mappedLineStyle,
codeBackground: mainColor.withMultipliedAlpha(0.1),
codeForeground: mainColor
)
)
let textPositioningInsets = UIEdgeInsets(top: -5.0, left: 0.0, bottom: -4.0, right: -4.0)
var currentRightInset: CGFloat = 0.0
if let sourceTextInputView {
currentRightInset = sourceTextInputView.currentRightInset
}
let textHeight = textNode.textHeightForWidth(maxTextWidth, rightInset: currentRightInset)
textNode.updateLayout(size: CGSize(width: maxTextWidth, height: textHeight))
let textBoundingRect = textNode.textView.currentTextBoundingRect().integral
let lastLineBoundingRect = textNode.textView.lastLineBoundingRect().integral
let textWidth = textBoundingRect.width
let textSize = CGSize(width: textWidth, height: textHeight)
var positionedTextSize = CGSize(width: textSize.width + textPositioningInsets.left + textPositioningInsets.right, height: textSize.height + textPositioningInsets.top + textPositioningInsets.bottom)
let effectInset: CGFloat = 12.0
if effect != nil, lastLineBoundingRect.width > textSize.width - effectInset {
if lastLineBoundingRect != textBoundingRect {
positionedTextSize.height += 11.0
} else {
positionedTextSize.width += effectInset
}
}
let unclippedPositionedTextHeight = positionedTextSize.height - (textPositioningInsets.top + textPositioningInsets.bottom)
positionedTextSize.height = min(positionedTextSize.height, maxTextHeight)
let size = CGSize(width: positionedTextSize.width + textInsets.left + textInsets.right, height: positionedTextSize.height + textInsets.top + textInsets.bottom)
let textFrame = CGRect(origin: CGPoint(x: textInsets.left, y: textInsets.top), size: positionedTextSize)
self.backgroundNode.setType(
type: .outgoing(.None),
highlighted: false,
graphics: themeGraphics,
maskMode: true,
hasWallpaper: true,
transition: transition.containedViewLayoutTransition,
backgroundNode: backgroundNode
)
let backgroundSize = explicitBackgroundSize ?? size
let previousSize = self.currentSize
self.currentSize = backgroundSize
let textClippingContainerFrame = CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: backgroundSize.width - 1.0 - 7.0, height: backgroundSize.height - 1.0 - 1.0))
var textClippingContainerBounds = CGRect(origin: CGPoint(), size: textClippingContainerFrame.size)
if explicitBackgroundSize != nil, let sourceTextInputView {
textClippingContainerBounds.origin.y = sourceTextInputView.contentOffset.y
} else {
textClippingContainerBounds.origin.y = unclippedPositionedTextHeight - backgroundSize.height + 4.0
textClippingContainerBounds.origin.y = max(0.0, textClippingContainerBounds.origin.y)
}
transition.setPosition(view: self.textClippingContainer, position: textClippingContainerFrame.center)
transition.setBounds(view: self.textClippingContainer, bounds: textClippingContainerBounds)
textNode.view.frame = CGRect(origin: CGPoint(x: textFrame.minX + textPositioningInsets.left - textClippingContainerFrame.minX, y: textFrame.minY + textPositioningInsets.top - textClippingContainerFrame.minY), size: CGSize(width: maxTextWidth, height: textHeight))
self.updateTextContents()
if let effectIcon = self.effectIcon, let effectIconSize {
if let effectIconView = effectIcon.view {
var animateIn = false
if effectIconView.superview == nil {
animateIn = true
self.addSubview(effectIconView)
}
let effectIconFrame = CGRect(origin: CGPoint(x: backgroundSize.width - textInsets.right + 2.0 - effectIconSize.width, y: backgroundSize.height - textInsets.bottom - 2.0 - effectIconSize.height), size: effectIconSize)
if animateIn {
if let previousSize {
let previousEffectIconFrame = CGRect(origin: CGPoint(x: previousSize.width - textInsets.right + 2.0 - effectIconSize.width, y: previousSize.height - textInsets.bottom - 2.0 - effectIconSize.height), size: effectIconSize)
effectIconView.frame = previousEffectIconFrame
} else {
effectIconView.frame = effectIconFrame
}
transition.animateAlpha(view: effectIconView, from: 0.0, to: 1.0)
if !transition.animation.isImmediate {
effectIconView.layer.animateSpring(from: 0.001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
}
}
transition.setFrame(view: effectIconView, frame: effectIconFrame)
}
} else {
if let effectIcon = self.effectIcon {
self.effectIcon = nil
if let effectIconView = effectIcon.view {
let effectIconSize = effectIconView.bounds.size
let effectIconFrame = CGRect(origin: CGPoint(x: backgroundSize.width - textInsets.right - effectIconSize.width, y: backgroundSize.height - textInsets.bottom - effectIconSize.height), size: effectIconSize)
transition.setFrame(view: effectIconView, frame: effectIconFrame)
transition.setScale(view: effectIconView, scale: 0.001)
transition.setAlpha(view: effectIconView, alpha: 0.0, completion: { [weak effectIconView] _ in
effectIconView?.removeFromSuperview()
})
}
}
}
let backgroundAlpha: CGFloat
if explicitBackgroundSize != nil {
backgroundAlpha = 0.0
} else {
backgroundAlpha = 1.0
}
transition.setFrame(view: self.backgroundWallpaperNode.view, frame: CGRect(origin: CGPoint(), size: backgroundSize))
transition.setAlpha(view: self.backgroundWallpaperNode.view, alpha: backgroundAlpha)
self.backgroundWallpaperNode.updateFrame(CGRect(origin: CGPoint(), size: backgroundSize), transition: transition.containedViewLayoutTransition)
transition.setFrame(view: self.backgroundNode.view, frame: CGRect(origin: CGPoint(), size: backgroundSize))
transition.setAlpha(view: self.backgroundNode.view, alpha: backgroundAlpha)
self.backgroundNode.updateLayout(size: backgroundSize, transition: transition.containedViewLayoutTransition)
return backgroundSize
}
}
func updateClippingRect(
sourceMediaPreview: ChatSendMessageContextScreenMediaPreview?,
isAnimatedIn: Bool,
localFrame: CGRect,
containerSize: CGSize,
transition: Transition
) {
if let mediaPreviewClippingView = self.mediaPreviewClippingView, let sourceMediaPreview {
let clippingFrame: CGRect
if !isAnimatedIn, let globalClippingRect = sourceMediaPreview.globalClippingRect {
clippingFrame = self.convert(globalClippingRect, from: nil)
} else {
clippingFrame = CGRect(origin: CGPoint(x: -localFrame.minX, y: -localFrame.minY), size: containerSize)
}
transition.setPosition(view: mediaPreviewClippingView, position: clippingFrame.origin)
transition.setBounds(view: mediaPreviewClippingView, bounds: CGRect(origin: CGPoint(x: clippingFrame.minX, y: clippingFrame.minY), size: clippingFrame.size))
}
}
private func updateTextContents() {
guard let textInputNode = self.textNode else {
return
}
transition.setFrame(view: self.backgroundWallpaperNode.view, frame: CGRect(origin: CGPoint(), size: backgroundSize))
transition.setAlpha(view: self.backgroundWallpaperNode.view, alpha: backgroundAlpha)
self.backgroundWallpaperNode.updateFrame(CGRect(origin: CGPoint(), size: backgroundSize), transition: transition.containedViewLayoutTransition)
transition.setFrame(view: self.backgroundNode.view, frame: CGRect(origin: CGPoint(), size: backgroundSize))
transition.setAlpha(view: self.backgroundNode.view, alpha: backgroundAlpha)
self.backgroundNode.updateLayout(size: backgroundSize, transition: transition.containedViewLayoutTransition)
var customEmojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)] = []
return backgroundSize
if let attributedText = textInputNode.attributedText {
let beginning = textInputNode.textView.beginningOfDocument
attributedText.enumerateAttributes(in: NSMakeRange(0, attributedText.length), options: [], using: { attributes, range, _ in
if let value = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute {
if let start = textInputNode.textView.position(from: beginning, offset: range.location), let end = textInputNode.textView.position(from: start, offset: range.length), let textRange = textInputNode.textView.textRange(from: start, to: end) {
let textRects = textInputNode.textView.selectionRects(for: textRange)
for textRect in textRects {
customEmojiRects.append((textRect.rect, value))
break
}
}
}
})
}
if !customEmojiRects.isEmpty {
let customEmojiContainerView: CustomEmojiContainerView
if let current = self.customEmojiContainerView {
customEmojiContainerView = current
} else {
customEmojiContainerView = CustomEmojiContainerView(emojiViewProvider: { [weak self] emoji in
guard let self, let emojiViewProvider = self.emojiViewProvider else {
return nil
}
return emojiViewProvider(emoji)
})
customEmojiContainerView.isUserInteractionEnabled = false
textInputNode.textView.addSubview(customEmojiContainerView)
self.customEmojiContainerView = customEmojiContainerView
}
customEmojiContainerView.update(emojiRects: customEmojiRects)
} else {
if let customEmojiContainerView = self.customEmojiContainerView {
customEmojiContainerView.removeFromSuperview()
self.customEmojiContainerView = nil
}
}
}
}

View File

@ -24,6 +24,9 @@ final class SendButton: HighlightTrackingButton {
private let iconView: UIImageView
private var activityIndicator: ActivityIndicator?
private var didProcessSourceCustomContent: Bool = false
private var sourceCustomContentView: UIView?
override init(frame: CGRect) {
self.containerView = UIView()
self.containerView.isUserInteractionEnabled = false
@ -50,12 +53,14 @@ final class SendButton: HighlightTrackingButton {
context: AccountContext,
presentationData: PresentationData,
backgroundNode: WallpaperBackgroundNode?,
sourceSendButton: ASDisplayNode,
isAnimatedIn: Bool,
isLoadingEffectAnimation: Bool,
size: CGSize,
transition: Transition
) {
let innerSize = CGSize(width: 33.0, height: 33.0)
transition.setFrame(view: self.containerView, frame: CGRect(origin: CGPoint(x: floor((size.width - innerSize.width) * 0.5), y: floor((size.height - innerSize.height) * 0.5)), size: innerSize))
let innerSize = CGSize(width: size.width - 5.5 * 2.0, height: 33.0)
transition.setFrame(view: self.containerView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - innerSize.width) * 0.5), y: floorToScreenPixels((size.height - innerSize.height) * 0.5)), size: innerSize))
transition.setCornerRadius(layer: self.containerView.layer, cornerRadius: innerSize.height * 0.5)
if self.window != nil {
@ -71,7 +76,7 @@ final class SendButton: HighlightTrackingButton {
transition.setFrame(view: backgroundContent.view, frame: CGRect(origin: CGPoint(), size: innerSize))
}
if [.day, .night].contains(presentationData.theme.referenceTheme.baseTheme) && !presentationData.theme.chat.message.outgoing.bubble.withWallpaper.hasSingleFillColor {
if backgroundNode != nil && [.day, .night].contains(presentationData.theme.referenceTheme.baseTheme) && !presentationData.theme.chat.message.outgoing.bubble.withWallpaper.hasSingleFillColor {
self.backgroundContent?.isHidden = false
self.backgroundLayer.isHidden = true
} else {
@ -79,18 +84,44 @@ final class SendButton: HighlightTrackingButton {
self.backgroundLayer.isHidden = false
}
self.backgroundLayer.backgroundColor = presentationData.theme.list.itemAccentColor.cgColor
self.backgroundLayer.backgroundColor = presentationData.theme.chat.inputPanel.actionControlFillColor.cgColor
transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: CGPoint(), size: innerSize))
if !self.didProcessSourceCustomContent {
self.didProcessSourceCustomContent = true
if let sourceSendButton = sourceSendButton as? ChatSendMessageActionSheetControllerSourceSendButtonNode {
if let sourceCustomContentView = sourceSendButton.makeCustomContents() {
self.sourceCustomContentView = sourceCustomContentView
self.iconView.superview?.insertSubview(sourceCustomContentView, belowSubview: self.iconView)
}
}
}
if self.iconView.image == nil {
self.iconView.image = PresentationResourcesChat.chatInputPanelSendIconImage(presentationData.theme)
}
if let sourceCustomContentView = self.sourceCustomContentView {
let sourceCustomContentSize = sourceCustomContentView.bounds.size
let sourceCustomContentFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((innerSize.width - sourceCustomContentSize.width) * 0.5) + UIScreenPixel, y: floorToScreenPixels((innerSize.height - sourceCustomContentSize.height) * 0.5)), size: sourceCustomContentSize)
transition.setPosition(view: sourceCustomContentView, position: sourceCustomContentFrame.center)
transition.setBounds(view: sourceCustomContentView, bounds: CGRect(origin: CGPoint(), size: sourceCustomContentFrame.size))
transition.setAlpha(view: sourceCustomContentView, alpha: isAnimatedIn ? 0.0 : 1.0)
}
if let icon = self.iconView.image {
let iconFrame = CGRect(origin: CGPoint(x: floor((innerSize.width - icon.size.width) * 0.5), y: floor((innerSize.height - icon.size.height) * 0.5)), size: icon.size)
let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((innerSize.width - icon.size.width) * 0.5) - UIScreenPixel, y: floorToScreenPixels((innerSize.height - icon.size.height) * 0.5)), size: icon.size)
transition.setPosition(view: self.iconView, position: iconFrame.center)
transition.setBounds(view: self.iconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
transition.setAlpha(view: self.iconView, alpha: isLoadingEffectAnimation ? 0.0 : 1.0)
let iconViewAlpha: CGFloat
if (self.sourceCustomContentView != nil && !isAnimatedIn) || isLoadingEffectAnimation {
iconViewAlpha = 0.0
} else {
iconViewAlpha = 1.0
}
transition.setAlpha(view: self.iconView, alpha: iconViewAlpha)
transition.setScale(view: self.iconView, scale: isLoadingEffectAnimation ? 0.001 : 1.0)
}

View File

@ -549,10 +549,10 @@ private final class CreatePollContext: AttachmentMediaPickerContext {
func setCaption(_ caption: NSAttributedString) {
}
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
}
func schedule() {
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
}
func mainButtonAction() {

View File

@ -404,10 +404,10 @@ private final class LocationPickerContext: AttachmentMediaPickerContext {
func setCaption(_ caption: NSAttributedString) {
}
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
}
func schedule() {
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
}
func mainButtonAction() {

View File

@ -47,6 +47,9 @@ swift_library(
"//submodules/RadialStatusNode",
"//submodules/Camera",
"//submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation",
"//submodules/ChatSendMessageActionUI",
"//submodules/ComponentFlow",
"//submodules/Components/ComponentDisplayAdapters",
],
visibility = [
"//visibility:public",

View File

@ -25,6 +25,7 @@ import Camera
import CameraScreen
import MediaEditor
import ImageObjectSeparation
import ChatSendMessageActionUI
final class MediaPickerInteraction {
let downloadManager: AssetDownloadManager
@ -32,14 +33,14 @@ final class MediaPickerInteraction {
let openSelectedMedia: (TGMediaSelectableItem, UIImage?) -> Void
let openDraft: (MediaEditorDraft, UIImage?) -> Void
let toggleSelection: (TGMediaSelectableItem, Bool, Bool) -> Bool
let sendSelected: (TGMediaSelectableItem?, Bool, Int32?, Bool, @escaping () -> Void) -> Void
let schedule: () -> Void
let sendSelected: (TGMediaSelectableItem?, Bool, Int32?, Bool, ChatSendMessageActionSheetController.MessageEffect?, @escaping () -> Void) -> Void
let schedule: (ChatSendMessageActionSheetController.MessageEffect?) -> Void
let dismissInput: () -> Void
let selectionState: TGMediaSelectionContext?
let editingState: TGMediaEditingContext
var hiddenMediaId: String?
init(downloadManager: AssetDownloadManager, openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, openDraft: @escaping (MediaEditorDraft, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Bool, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool, @escaping () -> Void) -> Void, schedule: @escaping () -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
init(downloadManager: AssetDownloadManager, openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, openDraft: @escaping (MediaEditorDraft, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Bool, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool, ChatSendMessageActionSheetController.MessageEffect?, @escaping () -> Void) -> Void, schedule: @escaping (ChatSendMessageActionSheetController.MessageEffect?) -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
self.downloadManager = downloadManager
self.openMedia = openMedia
self.openSelectedMedia = openSelectedMedia
@ -199,7 +200,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
public var presentFilePicker: () -> Void = {}
private var completed = false
public var legacyCompletion: (_ signals: [Any], _ silently: Bool, _ scheduleTime: Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void = { _, _, _, _, _ in }
public var legacyCompletion: (_ signals: [Any], _ silently: Bool, _ scheduleTime: Int32?, ChatSendMessageActionSheetController.MessageEffect?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void = { _, _, _, _, _, _ in }
public var requestAttachmentMenuExpansion: () -> Void = { }
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
@ -212,6 +213,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
public var isContainerPanning: () -> Bool = { return false }
public var isContainerExpanded: () -> Bool = { return false }
public var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? = nil
private let selectedCollection = Promise<PHAssetCollection?>(nil)
var dismissAll: () -> Void = { }
@ -433,6 +436,49 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}
})
}
controller.getCurrentSendMessageContextMediaPreview = { [weak self] () -> ChatSendMessageContextScreenMediaPreview? in
guard let self else {
return nil
}
guard let controller = self.controller else {
return nil
}
guard let (layout, navigationHeight) = self.validLayout else {
return nil
}
var persistentItems = false
if case .media = controller.subject {
persistentItems = true
}
let previewNode = MediaPickerSelectedListNode(context: controller.context, persistentItems: persistentItems, isExternalPreview: true)
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.interaction = self.controller?.interaction
previewNode.getTransitionView = { [weak self] identifier in
guard let self else {
return nil
}
var node: MediaPickerGridItemNode?
self.gridNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? MediaPickerGridItemNode, itemNode.identifier == identifier {
node = itemNode
}
}
if let node = node {
return (node.view, node.spoilerNode?.dustNode, { [weak node] animateCheckNode in
node?.animateFadeIn(animateCheckNode: animateCheckNode, animateSpoilerNode: false)
})
} else {
return nil
}
}
let selectedItems = controller.interaction?.selectionState?.selectedItems() as? [TGMediaSelectableItem] ?? []
previewNode.updateLayout(size: layout.size, insets: UIEdgeInsets(), items: selectedItems, grouped: self.controller?.groupedValue ?? true, theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper, bubbleCorners: self.presentationData.chatBubbleCorners, transition: .immediate)
return previewNode
}
}
deinit {
@ -945,7 +991,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
persistentItems = true
}
let selectionNode = MediaPickerSelectedListNode(context: controller.context, persistentItems: persistentItems)
let selectionNode = MediaPickerSelectedListNode(context: controller.context, persistentItems: persistentItems, isExternalPreview: false)
selectionNode.alpha = animated ? 0.0 : 1.0
selectionNode.layer.allowsGroupOpacity = true
selectionNode.isUserInteractionEnabled = false
@ -992,11 +1038,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
if animated {
switch displayMode {
case .selected:
self.selectionNode?.animateIn(initiated: { [weak self] in
self.selectionNode?.animateIn(transition: .animated(duration: 0.25, curve: .easeInOut), initiated: { [weak self] in
self?.updateNavigation(transition: .immediate)
}, completion: completion)
case .all:
self.selectionNode?.animateOut(completion: completion)
self.selectionNode?.animateOut(transition: .animated(duration: 0.25, curve: .easeInOut), completion: completion)
}
} else {
self.updateNavigation(transition: .immediate)
@ -1099,7 +1145,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
return self?.transitionView(for: identifier)
}, completed: { [weak self] result, silently, scheduleTime, completion in
if let strongSelf = self {
strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false, completion)
strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false, nil, completion)
}
}, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
self?.currentGalleryParentController = c
@ -1138,7 +1184,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
return self?.transitionView(for: identifier)
}, completed: { [weak self] result, silently, scheduleTime, completion in
if let strongSelf = self {
strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false, completion)
strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false, nil, completion)
}
}, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
self?.currentGalleryParentController = c
@ -1172,7 +1218,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}
}
fileprivate func send(asFile: Bool = false, silently: Bool, scheduleTime: Int32?, animated: Bool, completion: @escaping () -> Void) {
fileprivate func send(asFile: Bool = false, silently: Bool, scheduleTime: Int32?, animated: Bool, messageEffect: ChatSendMessageActionSheetController.MessageEffect?, completion: @escaping () -> Void) {
guard let controller = self.controller, !controller.completed else {
return
}
@ -1200,7 +1246,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
return
}
controller.completed = true
controller.legacyCompletion(signals, silently, scheduleTime, { [weak self] identifier in
controller.legacyCompletion(signals, silently, scheduleTime, messageEffect, { [weak self] identifier in
return !asFile ? self?.getItemSnapshot(identifier) : nil
}, { [weak self] in
completion()
@ -1913,18 +1959,18 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
} else {
return false
}
}, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated, completion in
}, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated, messageEffect, completion in
if let strongSelf = self, let selectionState = strongSelf.interaction?.selectionState, !strongSelf.isDismissing {
strongSelf.isDismissing = true
if let currentItem = currentItem {
selectionState.setItem(currentItem, selected: true)
}
strongSelf.controllerNode.send(silently: silently, scheduleTime: scheduleTime, animated: animated, completion: completion)
strongSelf.controllerNode.send(silently: silently, scheduleTime: scheduleTime, animated: animated, messageEffect: messageEffect, completion: completion)
}
}, schedule: { [weak self] in
}, schedule: { [weak self] messageEffect in
if let strongSelf = self {
strongSelf.presentSchedulePicker(false, { [weak self] time in
self?.interaction?.sendSelected(nil, false, time, true, {})
self?.interaction?.sendSelected(nil, false, time, true, messageEffect, {})
})
}
}, dismissInput: { [weak self] in
@ -2384,7 +2430,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}, action: { [weak self] _, f in
f(.default)
self?.controllerNode.send(asFile: true, silently: false, scheduleTime: nil, animated: true, completion: {})
self?.controllerNode.send(asFile: true, silently: false, scheduleTime: nil, animated: true, messageEffect: nil, completion: {})
})))
}
if selectionCount > 1 {
@ -2517,12 +2563,12 @@ final class MediaPickerContext: AttachmentMediaPickerContext {
self.controller?.interaction?.editingState.setForcedCaption(caption, skipUpdate: true)
}
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
self.controller?.interaction?.sendSelected(nil, mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil, true, {})
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
self.controller?.interaction?.sendSelected(nil, mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil, true, messageEffect, {})
}
func schedule() {
self.controller?.interaction?.schedule()
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
self.controller?.interaction?.schedule(messageEffect)
}
func mainButtonAction() {

View File

@ -13,11 +13,15 @@ import MosaicLayout
import WallpaperBackgroundNode
import AccountContext
import ChatMessageBackground
import ChatSendMessageActionUI
import ComponentFlow
import ComponentDisplayAdapters
private class MediaPickerSelectedItemNode: ASDisplayNode {
let asset: TGMediaEditableItem
private let interaction: MediaPickerInteraction?
private let enableAnimations: Bool
private let isExternalPreview: Bool
private let imageNode: ImageNode
private var checkNode: InteractiveCheckNode?
@ -57,10 +61,11 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
private var videoDuration: Double?
init(asset: TGMediaEditableItem, interaction: MediaPickerInteraction?, enableAnimations: Bool) {
init(asset: TGMediaEditableItem, interaction: MediaPickerInteraction?, enableAnimations: Bool, isExternalPreview: Bool) {
self.asset = asset
self.interaction = interaction
self.enableAnimations = enableAnimations
self.isExternalPreview = isExternalPreview
self.imageNode = ImageNode()
self.imageNode.contentMode = .scaleAspectFill
@ -279,7 +284,7 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
if let checkNode = self.checkNode {
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
transition.updateAlpha(node: checkNode, alpha: selectionState.count() < 2 ? 0.0 : 1.0)
transition.updateAlpha(node: checkNode, alpha: (self.isExternalPreview || selectionState.count() < 2) ? 0.0 : 1.0)
}
}
}
@ -382,7 +387,7 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
return view
}
func animateFrom(_ view: UIView) {
func animateFrom(_ view: UIView, transition: ContainedViewLayoutTransition) {
view.alpha = 0.0
let frame = view.convert(view.bounds, to: self.supernode?.view)
@ -392,13 +397,19 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
self.durationBackgroundNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.updateLayout(size: frame.size, transition: .immediate)
self.updateLayout(size: targetFrame.size, transition: .animated(duration: 0.25, curve: .spring))
self.layer.animateFrame(from: frame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak view] _ in
view?.alpha = 1.0
self.updateLayout(size: targetFrame.size, transition: transition)
transition.animateFrame(layer: self.layer, from: frame, to: targetFrame, completion: { [weak self, weak view] _ in
guard let self else {
view?.alpha = 1.0
return
}
if !self.isExternalPreview {
view?.alpha = 1.0
}
})
}
func animateTo(_ view: UIView, dustNode: ASDisplayNode?, completion: @escaping (Bool) -> Void) {
func animateTo(_ view: UIView, dustNode: ASDisplayNode?, transition: ContainedViewLayoutTransition, completion: @escaping (Bool) -> Void) {
view.alpha = 0.0
let frame = self.frame
@ -418,14 +429,13 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
self.addSubnode(dustNode)
dustNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
dustNode.layer.animatePosition(from: CGPoint(x: frame.width / 2.0, y: frame.height / 2.0), to: dustNode.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
transition.animatePositionAdditive(layer: dustNode.layer, offset: CGPoint(x: frame.width / 2.0, y: frame.height / 2.0), to: dustNode.position)
self.spoilerNode?.dustNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
}
self.corners = []
self.updateLayout(size: targetFrame.size, transition: .animated(duration: 0.25, curve: .spring))
self.layer.animateFrame(from: frame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak view, weak self] _ in
self.updateLayout(size: targetFrame.size, transition: transition)
transition.animateFrame(layer: self.layer, from: frame, to: targetFrame, removeOnCompletion: false, completion: { [weak view, weak self] _ in
view?.alpha = 1.0
self?.durationTextNode?.layer.removeAllAnimations()
@ -471,7 +481,7 @@ private class MessageBackgroundNode: ASDisplayNode {
private var absoluteRect: (CGRect, CGSize)?
func update(size: CGSize, theme: PresentationTheme, wallpaper: TelegramWallpaper, graphics: PrincipalThemeEssentialGraphics, wallpaperBackgroundNode: WallpaperBackgroundNode, transition: ContainedViewLayoutTransition) {
func update(size: CGSize, theme: PresentationTheme, wallpaper: TelegramWallpaper, graphics: PrincipalThemeEssentialGraphics, wallpaperBackgroundNode: WallpaperBackgroundNode?, transition: ContainedViewLayoutTransition) {
self.backgroundNode.setType(type: .outgoing(.Extracted), highlighted: false, graphics: graphics, maskMode: false, hasWallpaper: wallpaper.hasWallpaper, transition: transition, backgroundNode: wallpaperBackgroundNode)
self.backgroundWallpaperNode.setType(type: .outgoing(.Extracted), theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), essentialGraphics: graphics, maskMode: true, backgroundNode: wallpaperBackgroundNode)
self.shadowNode.setType(type: .outgoing(.Extracted), hasWallpaper: wallpaper.hasWallpaper, graphics: graphics)
@ -496,11 +506,13 @@ private class MessageBackgroundNode: ASDisplayNode {
}
}
final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDelegate {
final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDelegate, ChatSendMessageContextScreenMediaPreview {
private let context: AccountContext
private let persistentItems: Bool
private let isExternalPreview: Bool
var globalClippingRect: CGRect?
fileprivate let wallpaperBackgroundNode: WallpaperBackgroundNode
fileprivate var wallpaperBackgroundNode: WallpaperBackgroundNode?
private let scrollNode: ASScrollNode
private var backgroundNodes: [Int: MessageBackgroundNode] = [:]
private var itemNodes: [String: MediaPickerSelectedItemNode] = [:]
@ -518,17 +530,28 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
private var didSetReady = false
private var ready = Promise<Bool>()
init(context: AccountContext, persistentItems: Bool) {
private var contentSize: CGSize = CGSize()
var isReady: Signal<Bool, NoError> {
return self.ready.get()
}
init(context: AccountContext, persistentItems: Bool, isExternalPreview: Bool) {
self.context = context
self.persistentItems = persistentItems
self.isExternalPreview = isExternalPreview
self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false)
self.wallpaperBackgroundNode.backgroundColor = .black
self.scrollNode = ASScrollNode()
self.scrollNode.clipsToBounds = false
super.init()
self.addSubnode(self.wallpaperBackgroundNode)
if !self.isExternalPreview {
let wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false)
wallpaperBackgroundNode.backgroundColor = .black
self.wallpaperBackgroundNode = wallpaperBackgroundNode
self.addSubnode(wallpaperBackgroundNode)
}
self.addSubnode(self.scrollNode)
}
@ -578,7 +601,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
var getTransitionView: (String) -> (UIView, ASDisplayNode?, (Bool) -> Void)? = { _ in return nil }
func animateIn(initiated: @escaping () -> Void, completion: @escaping () -> Void = {}) {
func animateIn(transition: ContainedViewLayoutTransition, initiated: @escaping () -> Void, completion: @escaping () -> Void = {}) {
let _ = (self.ready.get()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] _ in
@ -589,79 +612,105 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
strongSelf.alpha = 1.0
initiated()
strongSelf.wallpaperBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in
if let wallpaperBackgroundNode = strongSelf.wallpaperBackgroundNode {
wallpaperBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in
completion()
})
wallpaperBackgroundNode.layer.animateScale(from: 1.2, to: 1.0, duration: 0.33, timingFunction: kCAMediaTimingFunctionSpring)
} else {
completion()
})
strongSelf.wallpaperBackgroundNode.layer.animateScale(from: 1.2, to: 1.0, duration: 0.33, timingFunction: kCAMediaTimingFunctionSpring)
}
for (_, backgroundNode) in strongSelf.backgroundNodes {
backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
if strongSelf.isExternalPreview {
Transition.immediate.setScale(layer: backgroundNode.layer, scale: 0.001)
transition.updateTransformScale(layer: backgroundNode.layer, scale: 1.0)
}
}
for (identifier, itemNode) in strongSelf.itemNodes {
if let (transitionView, _, _) = strongSelf.getTransitionView(identifier) {
itemNode.animateFrom(transitionView)
itemNode.animateFrom(transitionView, transition: transition)
} else {
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
if strongSelf.isExternalPreview {
itemNode.alpha = 0.0
transition.animateTransformScale(node: itemNode, from: 0.001)
transition.updateAlpha(node: itemNode, alpha: 1.0)
} else {
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
}
}
}
if let topNode = strongSelf.messageNodes?.first, !topNode.alpha.isZero {
topNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
topNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -30.0), to: CGPoint(), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
transition.animatePositionAdditive(layer: topNode.layer, offset: CGPoint(x: 0.0, y: -30.0))
}
if let bottomNode = strongSelf.messageNodes?.last, !bottomNode.alpha.isZero {
bottomNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
bottomNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 30.0), to: CGPoint(), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
transition.animatePositionAdditive(layer: bottomNode.layer, offset: CGPoint(x: 0.0, y: 30.0))
}
})
}
func animateOut(completion: @escaping () -> Void = {}) {
self.wallpaperBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak self] _ in
completion()
func animateOut(transition: ContainedViewLayoutTransition, completion: @escaping () -> Void = {}) {
if let wallpaperBackgroundNode = self.wallpaperBackgroundNode{
wallpaperBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak self] _ in
completion()
if let strongSelf = self {
Queue.mainQueue().after(0.01) {
for (_, backgroundNode) in strongSelf.backgroundNodes {
backgroundNode.layer.removeAllAnimations()
if let strongSelf = self {
Queue.mainQueue().after(0.01) {
for (_, backgroundNode) in strongSelf.backgroundNodes {
backgroundNode.layer.removeAllAnimations()
}
for (_, itemNode) in strongSelf.itemNodes {
itemNode.layer.removeAllAnimations()
}
strongSelf.messageNodes?.first?.layer.removeAllAnimations()
strongSelf.messageNodes?.last?.layer.removeAllAnimations()
strongSelf.wallpaperBackgroundNode?.layer.removeAllAnimations()
}
for (_, itemNode) in strongSelf.itemNodes {
itemNode.layer.removeAllAnimations()
}
strongSelf.messageNodes?.first?.layer.removeAllAnimations()
strongSelf.messageNodes?.last?.layer.removeAllAnimations()
strongSelf.wallpaperBackgroundNode.layer.removeAllAnimations()
}
}
})
})
self.wallpaperBackgroundNode.layer.animateScale(from: 1.0, to: 1.2, duration: 0.33, timingFunction: kCAMediaTimingFunctionSpring)
wallpaperBackgroundNode.layer.animateScale(from: 1.0, to: 1.2, duration: 0.33, timingFunction: kCAMediaTimingFunctionSpring)
} else {
completion()
}
for (_, backgroundNode) in self.backgroundNodes {
backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false)
if self.isExternalPreview {
transition.updateTransformScale(layer: backgroundNode.layer, scale: 0.001)
}
}
for (identifier, itemNode) in self.itemNodes {
if let (transitionView, maybeDustNode, completion) = self.getTransitionView(identifier) {
itemNode.animateTo(transitionView, dustNode: maybeDustNode, completion: completion)
itemNode.animateTo(transitionView, dustNode: maybeDustNode, transition: transition, completion: completion)
} else {
itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false)
if self.isExternalPreview {
transition.updateTransformScale(node: itemNode, scale: 0.001)
transition.updateAlpha(node: itemNode, alpha: 0.0)
} else {
itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false)
}
}
}
if let topNode = self.messageNodes?.first {
topNode.layer.animateAlpha(from: topNode.alpha, to: 0.0, duration: 0.15, removeOnCompletion: false)
topNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -30.0), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
transition.animatePositionAdditive(layer: topNode.layer, offset: CGPoint(), to: CGPoint(x: 0.0, y: -30.0), removeOnCompletion: false)
}
if let bottomNode = self.messageNodes?.last {
bottomNode.layer.animateAlpha(from: bottomNode.alpha, to: 0.0, duration: 0.15, removeOnCompletion: false)
bottomNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: 30.0), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
transition.animatePositionAdditive(layer: bottomNode.layer, offset: CGPoint(), to: CGPoint(x: 0.0, y: 30.0), removeOnCompletion: false)
}
}
@ -782,7 +831,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
if let current = self.itemNodes[identifier] {
itemNode = current
} else {
itemNode = MediaPickerSelectedItemNode(asset: asset, interaction: self.interaction, enableAnimations: self.context.sharedContext.energyUsageSettings.fullTranslucency)
itemNode = MediaPickerSelectedItemNode(asset: asset, interaction: self.interaction, enableAnimations: self.context.sharedContext.energyUsageSettings.fullTranslucency, isExternalPreview: self.isExternalPreview)
self.itemNodes[identifier] = itemNode
self.scrollNode.addSubnode(itemNode)
@ -852,55 +901,61 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
}
}
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1))
var peers = SimpleDictionary<PeerId, Peer>()
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil)
if !self.isExternalPreview {
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1))
var peers = SimpleDictionary<PeerId, Peer>()
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil)
let previewText = groupLayouts.count > 1 ? presentationData.strings.Attachment_MessagesPreview : presentationData.strings.Attachment_MessagePreview
let previewText = groupLayouts.count > 1 ? presentationData.strings.Attachment_MessagesPreview : presentationData.strings.Attachment_MessagePreview
let previewMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: previewText, entities: [], additionalAttributes: nil))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
let previewItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [previewMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: true, isPreview: true, isStandalone: false)
let previewMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: previewText, entities: [], additionalAttributes: nil))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
let previewItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [previewMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: true, isPreview: true, isStandalone: false)
let dragMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: presentationData.strings.Attachment_DragToReorder, entities: [], additionalAttributes: nil))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
let dragItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [dragMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: true, isPreview: true, isStandalone: false)
let dragMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: presentationData.strings.Attachment_DragToReorder, entities: [], additionalAttributes: nil))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
let dragItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [dragMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: true, isPreview: true, isStandalone: false)
let headerItems: [ListViewItem] = [previewItem, dragItem]
let headerItems: [ListViewItem] = [previewItem, dragItem]
let params = ListViewItemLayoutParams(width: size.width, leftInset: insets.left, rightInset: insets.right, availableHeight: size.height)
if let messageNodes = self.messageNodes {
for i in 0 ..< headerItems.count {
let itemNode = messageNodes[i]
headerItems[i].updateNode(async: { $0() }, node: {
return itemNode
}, params: params, previousItem: nil, nextItem: nil, animation: .None, completion: { (layout, apply) in
let nodeFrame = CGRect(origin: itemNode.frame.origin, size: CGSize(width: size.width, height: layout.size.height))
let params = ListViewItemLayoutParams(width: size.width, leftInset: insets.left, rightInset: insets.right, availableHeight: size.height)
if let messageNodes = self.messageNodes {
for i in 0 ..< headerItems.count {
let itemNode = messageNodes[i]
headerItems[i].updateNode(async: { $0() }, node: {
return itemNode
}, params: params, previousItem: nil, nextItem: nil, animation: .None, completion: { (layout, apply) in
let nodeFrame = CGRect(origin: itemNode.frame.origin, size: CGSize(width: size.width, height: layout.size.height))
itemNode.contentSize = layout.contentSize
itemNode.insets = layout.insets
itemNode.frame = nodeFrame
itemNode.isUserInteractionEnabled = false
itemNode.contentSize = layout.contentSize
itemNode.insets = layout.insets
itemNode.frame = nodeFrame
itemNode.isUserInteractionEnabled = false
apply(ListViewItemApply(isOnScreen: true))
})
apply(ListViewItemApply(isOnScreen: true))
})
}
} else {
var messageNodes: [ListViewItemNode] = []
for i in 0 ..< headerItems.count {
var itemNode: ListViewItemNode?
headerItems[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: nil, nextItem: nil, completion: { node, apply in
itemNode = node
apply().1(ListViewItemApply(isOnScreen: true))
})
itemNode!.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
itemNode!.isUserInteractionEnabled = false
messageNodes.append(itemNode!)
self.scrollNode.addSubnode(itemNode!)
}
self.messageNodes = messageNodes
}
} else {
var messageNodes: [ListViewItemNode] = []
for i in 0 ..< headerItems.count {
var itemNode: ListViewItemNode?
headerItems[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: nil, nextItem: nil, completion: { node, apply in
itemNode = node
apply().1(ListViewItemApply(isOnScreen: true))
})
itemNode!.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
itemNode!.isUserInteractionEnabled = false
messageNodes.append(itemNode!)
self.scrollNode.addSubnode(itemNode!)
}
self.messageNodes = messageNodes
}
let spacing: CGFloat = 8.0
var contentHeight: CGFloat = 60.0
var contentHeight: CGFloat = 0.0
var contentWidth: CGFloat = 0.0
if !self.isExternalPreview {
contentHeight += 60.0
}
if let previewNode = self.messageNodes?.first {
transition.updateFrame(node: previewNode, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top + 28.0), size: previewNode.frame.size))
@ -915,26 +970,35 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
var groupIndex = 0
for (items, groupSize) in groupLayouts {
let groupRect = CGRect(origin: CGPoint(x: insets.left + floorToScreenPixels((size.width - insets.left - insets.right - groupSize.width) / 2.0), y: insets.top + contentHeight), size: groupSize)
let groupBackgroundNode: MessageBackgroundNode
if let current = self.backgroundNodes[groupIndex] {
groupBackgroundNode = current
} else {
groupBackgroundNode = MessageBackgroundNode()
groupBackgroundNode.displaysAsynchronously = false
self.backgroundNodes[groupIndex] = groupBackgroundNode
self.scrollNode.insertSubnode(groupBackgroundNode, at: 0)
var groupRect = CGRect(origin: CGPoint(x: 0.0, y: insets.top + contentHeight), size: groupSize)
if !self.isExternalPreview {
groupRect.origin.x = insets.left + floorToScreenPixels((size.width - insets.left - insets.right - groupSize.width) / 2.0)
}
var itemTransition = transition
if groupBackgroundNode.frame.width.isZero {
itemTransition = .immediate
if !self.isExternalPreview {
let groupBackgroundNode: MessageBackgroundNode
if let current = self.backgroundNodes[groupIndex] {
groupBackgroundNode = current
} else {
groupBackgroundNode = MessageBackgroundNode()
groupBackgroundNode.displaysAsynchronously = false
self.backgroundNodes[groupIndex] = groupBackgroundNode
self.scrollNode.insertSubnode(groupBackgroundNode, at: 0)
}
if groupBackgroundNode.frame.width.isZero {
itemTransition = .immediate
}
let groupBackgroundFrame = groupRect.insetBy(dx: -5.0, dy: -2.0).offsetBy(dx: 3.0, dy: 0.0)
itemTransition.updatePosition(node: groupBackgroundNode, position: groupBackgroundFrame.center)
itemTransition.updateBounds(node: groupBackgroundNode, bounds: CGRect(origin: CGPoint(), size: groupBackgroundFrame.size))
groupBackgroundNode.update(size: groupBackgroundNode.frame.size, theme: theme, wallpaper: wallpaper, graphics: graphics, wallpaperBackgroundNode: self.wallpaperBackgroundNode, transition: itemTransition)
}
itemTransition.updateFrame(node: groupBackgroundNode, frame: groupRect.insetBy(dx: -5.0, dy: -2.0).offsetBy(dx: 3.0, dy: 0.0))
groupBackgroundNode.update(size: groupBackgroundNode.frame.size, theme: theme, wallpaper: wallpaper, graphics: graphics, wallpaperBackgroundNode: self.wallpaperBackgroundNode, transition: itemTransition)
var isFirstGroup = true
for (item, itemRect, itemPosition) in items {
if let identifier = item.uniqueIdentifier, let itemNode = self.itemNodes[identifier] {
var corners: CACornerMask = []
@ -968,7 +1032,13 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
}
}
contentHeight += groupSize.height + spacing
if isFirstGroup {
isFirstGroup = false
} else {
contentHeight += spacing
}
contentHeight += groupSize.height
contentWidth = max(contentWidth, groupSize.width)
groupIndex += 1
}
@ -1032,7 +1102,13 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
self.updateAbsoluteRects()
self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight)
if self.isExternalPreview {
self.scrollNode.view.contentSize = CGSize(width: contentWidth, height: contentHeight)
} else {
self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight)
}
self.contentSize = CGSize(width: contentWidth, height: contentHeight)
}
func updateSelectionState() {
@ -1047,6 +1123,25 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
}
}
func animateIn(transition: Transition) {
self.animateIn(transition: transition.containedViewLayoutTransition, initiated: {}, completion: {})
}
func animateOut(transition: Transition) {
self.animateOut(transition: transition.containedViewLayoutTransition, completion: {})
}
func update(containerSize: CGSize, transition: Transition) -> CGSize {
if var validLayout = self.validLayout {
validLayout.size = containerSize
self.validLayout = validLayout
}
self.updateItems(transition: transition.containedViewLayoutTransition)
return self.contentSize
}
func updateLayout(size: CGSize, insets: UIEdgeInsets, items: [TGMediaSelectableItem], grouped: Bool, theme: PresentationTheme, wallpaper: TelegramWallpaper, bubbleCorners: PresentationChatBubbleCorners, transition: ContainedViewLayoutTransition) {
let previous = self.validLayout
self.validLayout = (size, insets, items, grouped, theme, wallpaper, bubbleCorners)
@ -1068,10 +1163,12 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
}
let inset: CGFloat = insets.left == 70 ? insets.left : 0.0
self.wallpaperBackgroundNode.update(wallpaper: wallpaper, animated: false)
self.wallpaperBackgroundNode.updateBubbleTheme(bubbleTheme: theme, bubbleCorners: bubbleCorners)
transition.updateFrame(node: self.wallpaperBackgroundNode, frame: CGRect(origin: CGPoint(x: inset, y: 0.0), size: CGSize(width: size.width - inset * 2.0, height: size.height)))
self.wallpaperBackgroundNode.updateLayout(size: CGSize(width: size.width - inset * 2.0, height: size.height), displayMode: .aspectFill, transition: transition)
if let wallpaperBackgroundNode = self.wallpaperBackgroundNode {
wallpaperBackgroundNode.update(wallpaper: wallpaper, animated: false)
wallpaperBackgroundNode.updateBubbleTheme(bubbleTheme: theme, bubbleCorners: bubbleCorners)
transition.updateFrame(node: wallpaperBackgroundNode, frame: CGRect(origin: CGPoint(x: inset, y: 0.0), size: CGSize(width: size.width - inset * 2.0, height: size.height)))
wallpaperBackgroundNode.updateLayout(size: CGSize(width: size.width - inset * 2.0, height: size.height), displayMode: .aspectFill, transition: transition)
}
self.updateItems(transition: itemsTransition)

View File

@ -1134,7 +1134,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1035688326] = { return Api.auth.SentCodeType.parse_sentCodeTypeApp($0) }
dict[1398007207] = { return Api.auth.SentCodeType.parse_sentCodeTypeCall($0) }
dict[-196020837] = { return Api.auth.SentCodeType.parse_sentCodeTypeEmailCode($0) }
dict[-444918734] = { return Api.auth.SentCodeType.parse_sentCodeTypeFirebaseSms($0) }
dict[331943703] = { return Api.auth.SentCodeType.parse_sentCodeTypeFirebaseSms($0) }
dict[-1425815847] = { return Api.auth.SentCodeType.parse_sentCodeTypeFlashCall($0) }
dict[-648651719] = { return Api.auth.SentCodeType.parse_sentCodeTypeFragmentSms($0) }
dict[-2113903484] = { return Api.auth.SentCodeType.parse_sentCodeTypeMissedCall($0) }
@ -1365,7 +1365,7 @@ public extension Api {
return parser(reader)
}
else {
telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found")
telegramApiLog("Type constructor \(String(UInt32(bitPattern: signature), radix: 16, uppercase: false)) not found")
return nil
}
}

View File

@ -589,7 +589,7 @@ public extension Api.auth {
case sentCodeTypeApp(length: Int32)
case sentCodeTypeCall(length: Int32)
case sentCodeTypeEmailCode(flags: Int32, emailPattern: String, length: Int32, resetAvailablePeriod: Int32?, resetPendingDate: Int32?)
case sentCodeTypeFirebaseSms(flags: Int32, nonce: Buffer?, receipt: String?, pushTimeout: Int32?, length: Int32)
case sentCodeTypeFirebaseSms(flags: Int32, nonce: Buffer?, playIntegrityNonce: Buffer?, receipt: String?, pushTimeout: Int32?, length: Int32)
case sentCodeTypeFlashCall(pattern: String)
case sentCodeTypeFragmentSms(url: String, length: Int32)
case sentCodeTypeMissedCall(prefix: String, length: Int32)
@ -622,12 +622,13 @@ public extension Api.auth {
if Int(flags) & Int(1 << 3) != 0 {serializeInt32(resetAvailablePeriod!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 4) != 0 {serializeInt32(resetPendingDate!, buffer: buffer, boxed: false)}
break
case .sentCodeTypeFirebaseSms(let flags, let nonce, let receipt, let pushTimeout, let length):
case .sentCodeTypeFirebaseSms(let flags, let nonce, let playIntegrityNonce, let receipt, let pushTimeout, let length):
if boxed {
buffer.appendInt32(-444918734)
buffer.appendInt32(331943703)
}
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeBytes(nonce!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {serializeBytes(playIntegrityNonce!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {serializeString(receipt!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(pushTimeout!, buffer: buffer, boxed: false)}
serializeInt32(length, buffer: buffer, boxed: false)
@ -689,8 +690,8 @@ public extension Api.auth {
return ("sentCodeTypeCall", [("length", length as Any)])
case .sentCodeTypeEmailCode(let flags, let emailPattern, let length, let resetAvailablePeriod, let resetPendingDate):
return ("sentCodeTypeEmailCode", [("flags", flags as Any), ("emailPattern", emailPattern as Any), ("length", length as Any), ("resetAvailablePeriod", resetAvailablePeriod as Any), ("resetPendingDate", resetPendingDate as Any)])
case .sentCodeTypeFirebaseSms(let flags, let nonce, let receipt, let pushTimeout, let length):
return ("sentCodeTypeFirebaseSms", [("flags", flags as Any), ("nonce", nonce as Any), ("receipt", receipt as Any), ("pushTimeout", pushTimeout as Any), ("length", length as Any)])
case .sentCodeTypeFirebaseSms(let flags, let nonce, let playIntegrityNonce, let receipt, let pushTimeout, let length):
return ("sentCodeTypeFirebaseSms", [("flags", flags as Any), ("nonce", nonce as Any), ("playIntegrityNonce", playIntegrityNonce as Any), ("receipt", receipt as Any), ("pushTimeout", pushTimeout as Any), ("length", length as Any)])
case .sentCodeTypeFlashCall(let pattern):
return ("sentCodeTypeFlashCall", [("pattern", pattern as Any)])
case .sentCodeTypeFragmentSms(let url, let length):
@ -758,19 +759,22 @@ public extension Api.auth {
_1 = reader.readInt32()
var _2: Buffer?
if Int(_1!) & Int(1 << 0) != 0 {_2 = parseBytes(reader) }
var _3: String?
if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) }
var _4: Int32?
if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() }
var _3: Buffer?
if Int(_1!) & Int(1 << 2) != 0 {_3 = parseBytes(reader) }
var _4: String?
if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) }
var _5: Int32?
_5 = reader.readInt32()
if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() }
var _6: Int32?
_6 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.auth.SentCodeType.sentCodeTypeFirebaseSms(flags: _1!, nonce: _2, receipt: _3, pushTimeout: _4, length: _5!)
let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil
let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.auth.SentCodeType.sentCodeTypeFirebaseSms(flags: _1!, nonce: _2, playIntegrityNonce: _3, receipt: _4, pushTimeout: _5, length: _6!)
}
else {
return nil

View File

@ -2061,15 +2061,16 @@ public extension Api.functions.auth {
}
}
public extension Api.functions.auth {
static func requestFirebaseSms(flags: Int32, phoneNumber: String, phoneCodeHash: String, playIntegrityToken: String?, iosPushSecret: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
static func requestFirebaseSms(flags: Int32, phoneNumber: String, phoneCodeHash: String, safetyNetToken: String?, playIntegrityToken: String?, iosPushSecret: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(1940588736)
buffer.appendInt32(-1908857314)
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(phoneNumber, buffer: buffer, boxed: false)
serializeString(phoneCodeHash, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeString(playIntegrityToken!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 0) != 0 {serializeString(safetyNetToken!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {serializeString(playIntegrityToken!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {serializeString(iosPushSecret!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "auth.requestFirebaseSms", parameters: [("flags", String(describing: flags)), ("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("playIntegrityToken", String(describing: playIntegrityToken)), ("iosPushSecret", String(describing: iosPushSecret))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
return (FunctionDescription(name: "auth.requestFirebaseSms", parameters: [("flags", String(describing: flags)), ("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("safetyNetToken", String(describing: safetyNetToken)), ("playIntegrityToken", String(describing: playIntegrityToken)), ("iosPushSecret", String(describing: iosPushSecret))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
@ -2095,12 +2096,14 @@ public extension Api.functions.auth {
}
}
public extension Api.functions.auth {
static func resendCode(phoneNumber: String, phoneCodeHash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.SentCode>) {
static func resendCode(flags: Int32, phoneNumber: String, phoneCodeHash: String, reason: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.SentCode>) {
let buffer = Buffer()
buffer.appendInt32(1056025023)
buffer.appendInt32(-890997469)
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(phoneNumber, buffer: buffer, boxed: false)
serializeString(phoneCodeHash, buffer: buffer, boxed: false)
return (FunctionDescription(name: "auth.resendCode", parameters: [("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in
if Int(flags) & Int(1 << 0) != 0 {serializeString(reason!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "auth.resendCode", parameters: [("flags", String(describing: flags)), ("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("reason", String(describing: reason))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in
let reader = BufferReader(buffer)
var result: Api.auth.SentCode?
if let signature = reader.readInt32() {

View File

@ -133,7 +133,7 @@ private func sendFirebaseAuthorizationCode(accountManager: AccountManager<Telegr
//auth.requestFirebaseSms#89464b50 flags:# phone_number:string phone_code_hash:string safety_net_token:flags.0?string ios_push_secret:flags.1?string = Bool;
var flags: Int32 = 0
flags |= 1 << 1
return account.network.request(Api.functions.auth.requestFirebaseSms(flags: flags, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, playIntegrityToken: nil, iosPushSecret: firebaseSecret))
return account.network.request(Api.functions.auth.requestFirebaseSms(flags: flags, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, safetyNetToken: nil, playIntegrityToken: nil, iosPushSecret: firebaseSecret))
|> mapError { _ -> SendFirebaseAuthorizationCodeError in
return .generic
}
@ -275,7 +275,7 @@ public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccount
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
}
if case let .sentCodeTypeFirebaseSms(_, _, receipt, pushTimeout, _) = type {
if case let .sentCodeTypeFirebaseSms(_, _, _, receipt, pushTimeout, _) = type {
return firebaseSecretStream
|> map { mapping -> String? in
guard let receipt = receipt else {
@ -395,7 +395,7 @@ public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccount
}
private func internalResendAuthorizationCode(accountManager: AccountManager<TelegramAccountManagerTypes>, account: UnauthorizedAccount, number: String, apiId: Int32, apiHash: String, hash: String, syncContacts: Bool, firebaseSecretStream: Signal<[String: String], NoError>) -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> {
return account.network.request(Api.functions.auth.resendCode(phoneNumber: number, phoneCodeHash: hash), automaticFloodWait: false)
return account.network.request(Api.functions.auth.resendCode(flags: 0, phoneNumber: number, phoneCodeHash: hash, reason: nil), automaticFloodWait: false)
|> mapError { error -> AuthorizationCodeRequestError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
@ -421,7 +421,7 @@ private func internalResendAuthorizationCode(accountManager: AccountManager<Tele
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
}
if case let .sentCodeTypeFirebaseSms(_, _, receipt, pushTimeout, _) = type {
if case let .sentCodeTypeFirebaseSms(_, _, _, receipt, pushTimeout, _) = type {
return firebaseSecretStream
|> map { mapping -> String? in
guard let receipt = receipt else {
@ -520,7 +520,7 @@ public func resendAuthorizationCode(accountManager: AccountManager<TelegramAccou
switch state.contents {
case let .confirmationCodeEntry(number, type, hash, _, nextType, syncContacts, previousCodeEntryValue, _):
if nextType != nil {
return account.network.request(Api.functions.auth.resendCode(phoneNumber: number, phoneCodeHash: hash), automaticFloodWait: false)
return account.network.request(Api.functions.auth.resendCode(flags: 0, phoneNumber: number, phoneCodeHash: hash, reason: nil), automaticFloodWait: false)
|> mapError { error -> AuthorizationCodeRequestError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
@ -563,7 +563,7 @@ public func resendAuthorizationCode(accountManager: AccountManager<TelegramAccou
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
}
if case let .sentCodeTypeFirebaseSms(_, _, receipt, pushTimeout, _) = newType {
if case let .sentCodeTypeFirebaseSms(_, _, _, receipt, pushTimeout, _) = newType {
return firebaseSecretStream
|> map { mapping -> String? in
guard let receipt = receipt else {

View File

@ -36,7 +36,7 @@ extension SentAuthorizationCodeType {
self = .emailSetupRequired(appleSignInAllowed: (flags & (1 << 0)) != 0)
case let .sentCodeTypeFragmentSms(url, length):
self = .fragment(url: url, length: length)
case let .sentCodeTypeFirebaseSms(_, _, _, pushTimeout, length):
case let .sentCodeTypeFirebaseSms(_, _, _, _, pushTimeout, length):
self = .firebase(pushTimeout: pushTimeout, length: length)
case let .sentCodeTypeSmsWord(_, beginning):
self = .word(startsWith: beginning)

View File

@ -3,10 +3,6 @@ import TelegramApi
import Postbox
import SwiftSignalKit
/*
availableEffect flags:# premium_required:flags.3?true id:long emoticon:string static_icon_id:flags.0?long effect_sticker_id:flags.1?long effect_animation_id:flags.2?long = AvailableEffect;
*/
public final class AvailableMessageEffects: Equatable, Codable {
public final class MessageEffect: Equatable, Codable {
private enum CodingKeys: String, CodingKey {

View File

@ -79,7 +79,7 @@ func _internal_requestChangeAccountPhoneNumberVerification(account: Account, pho
}
func _internal_requestNextChangeAccountPhoneNumberVerification(account: Account, phoneNumber: String, phoneCodeHash: String) -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> {
return account.network.request(Api.functions.auth.resendCode(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), automaticFloodWait: false)
return account.network.request(Api.functions.auth.resendCode(flags: 0, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, reason: nil), automaticFloodWait: false)
|> mapError { error -> RequestChangeAccountPhoneNumberVerificationError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded

View File

@ -41,7 +41,7 @@ func _internal_requestCancelAccountResetData(network: Network, hash: String) ->
}
func _internal_requestNextCancelAccountResetOption(network: Network, phoneNumber: String, phoneCodeHash: String) -> Signal<CancelAccountResetData, RequestCancelAccountResetDataError> {
return network.request(Api.functions.auth.resendCode(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), automaticFloodWait: false)
return network.request(Api.functions.auth.resendCode(flags: 0, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, reason: nil), automaticFloodWait: false)
|> mapError { error -> RequestCancelAccountResetDataError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded

View File

@ -229,6 +229,15 @@ public:
CGRect bounds;
};
class RenderTreeNodeContentItem {
public:
RenderTreeNodeContentItem() {
}
public:
std::vector<std::shared_ptr<RenderTreeNodeContentItem>> subItems;
};
class RenderTreeNodeContent {
public:
enum class ShadingType {

View File

@ -1036,7 +1036,7 @@ public:
}
public:
void renderChildren(AnimationFrameTime frameTime, std::optional<TrimParams> parentTrim) {
void updateChildren(AnimationFrameTime frameTime, std::optional<TrimParams> parentTrim) {
CATransform3D containerTransform = CATransform3D::identity();
double containerOpacity = 1.0;
if (transform) {
@ -1111,7 +1111,7 @@ public:
}
}
subItems[i]->renderChildren(frameTime, childTrim);
subItems[i]->updateChildren(frameTime, childTrim);
}
}
}
@ -1284,14 +1284,14 @@ CompositionLayer(solidLayer, Vector2D::Zero()) {
void ShapeCompositionLayer::displayContentsWithFrame(double frame, bool forceUpdates) {
_frameTime = frame;
_frameTimeInitialized = true;
_contentTree->itemTree->renderChildren(_frameTime, std::nullopt);
_contentTree->itemTree->updateChildren(_frameTime, std::nullopt);
}
std::shared_ptr<RenderTreeNode> ShapeCompositionLayer::renderTreeNode() {
if (!_frameTimeInitialized) {
_frameTime = 0.0;
_frameTimeInitialized = true;
_contentTree->itemTree->renderChildren(_frameTime, std::nullopt);
_contentTree->itemTree->updateChildren(_frameTime, std::nullopt);
}
if (!_renderTreeNode) {

View File

@ -19,7 +19,6 @@ std::vector<std::shared_ptr<CompositionLayer>> initializeCompositionLayers(
std::vector<std::shared_ptr<CompositionLayer>> compositionLayers;
std::map<int, std::shared_ptr<CompositionLayer>> layerMap;
/// Organize the assets into a dictionary of [ID : ImageAsset]
std::vector<std::shared_ptr<LayerModel>> childLayers;
for (const auto &layer : layers) {

View File

@ -10212,7 +10212,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
func forwardMessages(messageIds: Set<MessageId>?) {
if let messageIds = messageIds ?? self.state.selectedMessageIds, !messageIds.isEmpty {
let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, filter: [.onlyWriteable, .excludeDisabled], hasFilters: true, multipleSelection: true, selectForumThreads: true))
peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions in
peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions, _ in
guard let strongSelf = self, let strongController = peerSelectionController else {
return
}

View File

@ -19,7 +19,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
private var customTitle: String?
public var peerSelected: ((EnginePeer, Int64?) -> Void)?
public var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?) -> Void)?
public var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?, ChatSendMessageActionSheetController.MessageEffect?) -> Void)?
private let filter: ChatListNodePeersFilter
private let forumPeerId: EnginePeer.Id?
private let selectForumThreads: Bool
@ -246,8 +246,8 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
self.peerSelectionNode.navigationBar = self.navigationBar
self.peerSelectionNode.requestSend = { [weak self] peers, peerMap, text, mode, forwardOptionsState in
self?.multiplePeersSelected?(peers, peerMap, text, mode, forwardOptionsState)
self.peerSelectionNode.requestSend = { [weak self] peers, peerMap, text, mode, forwardOptionsState, messageEffect in
self?.multiplePeersSelected?(peers, peerMap, text, mode, forwardOptionsState, messageEffect)
}
self.peerSelectionNode.requestDeactivateSearch = { [weak self] in

View File

@ -83,7 +83,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
var requestOpenDisabledPeer: ((EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void)?
var requestOpenPeerFromSearch: ((EnginePeer, Int64?) -> Void)?
var requestOpenMessageFromSearch: ((EnginePeer, Int64?, EngineMessage.Id) -> Void)?
var requestSend: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?) -> Void)?
var requestSend: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?, ChatSendMessageActionSheetController.MessageEffect?) -> Void)?
private var presentationData: PresentationData {
didSet {
@ -529,7 +529,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
forwardMessageIds = forwardMessageIds.filter { selectedMessageIds.contains($0) }
strongSelf.updateChatPresentationInterfaceState(animated: true, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageIds(forwardMessageIds) }) })
}
strongSelf.textInputPanelNode?.sendMessage(.generic)
strongSelf.textInputPanelNode?.sendMessage(.generic, nil)
f(.default)
})))
@ -692,17 +692,17 @@ final class PeerSelectionControllerNode: ASDisplayNode {
}
let controller = makeChatSendMessageActionSheetController(context: strongSelf.context, peerId: strongSelf.presentationInterfaceState.chatLocation.peerId, forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds, hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, sourceSendButton: node, textInputView: textInputNode.textView, emojiViewProvider: textInputPanelNode.emojiViewProvider, canSendWhenOnline: false, completion: {
}, sendMessage: { [weak textInputPanelNode] mode, _ in
}, sendMessage: { [weak textInputPanelNode] mode, messageEffect in
switch mode {
case .generic:
textInputPanelNode?.sendMessage(.generic)
textInputPanelNode?.sendMessage(.generic, messageEffect)
case .silently:
textInputPanelNode?.sendMessage(.silent)
textInputPanelNode?.sendMessage(.silent, messageEffect)
case .whenOnline:
textInputPanelNode?.sendMessage(.whenOnline)
textInputPanelNode?.sendMessage(.whenOnline, messageEffect)
}
}, schedule: { [weak textInputPanelNode] _ in
textInputPanelNode?.sendMessage(.schedule)
}, schedule: { [weak textInputPanelNode] messageEffect in
textInputPanelNode?.sendMessage(.schedule, messageEffect)
})
strongSelf.presentInGlobalOverlay(controller, nil)
}, openScheduledMessages: {
@ -825,7 +825,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
return nil
})
textInputPanelNode.interfaceInteraction = self.interfaceInteraction
textInputPanelNode.sendMessage = { [weak self] mode in
textInputPanelNode.sendMessage = { [weak self] mode, messageEffect in
guard let strongSelf = self else {
return
}
@ -835,7 +835,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
let (selectedPeers, selectedPeerMap) = strongSelf.selectedPeers
if !selectedPeers.isEmpty {
strongSelf.requestSend?(selectedPeers, selectedPeerMap, effectiveInputText, mode, forwardOptionsState)
strongSelf.requestSend?(selectedPeers, selectedPeerMap, effectiveInputText, mode, forwardOptionsState, messageEffect)
}
}
self.addSubnode(textInputPanelNode)

View File

@ -49,10 +49,10 @@ private final class PremiumGiftContext: AttachmentMediaPickerContext {
func setCaption(_ caption: NSAttributedString) {
}
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
}
func schedule() {
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
}
func mainButtonAction() {

View File

@ -377,10 +377,10 @@ private final class ThemeColorsGridContext: AttachmentMediaPickerContext {
func setCaption(_ caption: NSAttributedString) {
}
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
}
func schedule() {
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
}
func mainButtonAction() {

View File

@ -1556,14 +1556,14 @@ final class StoryItemSetContainerSendMessage {
completion(controller, mediaPickerContext)
}, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in
attachmentController?.mediaPickerContext = mediaPickerContext
}, completion: { [weak self, weak view] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in
}, completion: { [weak self, weak view] signals, silentPosting, scheduleTime, messageEffect, getAnimatedTransitionSource, completion in
guard let self, let view else {
return
}
if !inputText.string.isEmpty {
self.clearInputText(view: view)
}
self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, messageEffect: messageEffect, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
}
)
case .file:
@ -1875,7 +1875,7 @@ final class StoryItemSetContainerSendMessage {
bannedSendVideos: (Int32, Bool)?,
present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void,
updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void,
completion: @escaping ([Any], Bool, Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void
completion: @escaping ([Any], Bool, Int32?, ChatSendMessageActionSheetController.MessageEffect?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void
) {
guard let component = view.component else {
return
@ -1936,8 +1936,8 @@ final class StoryItemSetContainerSendMessage {
}
return self.getCaptionPanelView(view: view, peer: peer, mediaPicker: controller)
}
controller.legacyCompletion = { signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion in
completion(signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion)
controller.legacyCompletion = { signals, silently, scheduleTime, messageEffect, getAnimatedTransitionSource, sendCompletion in
completion(signals, silently, scheduleTime, messageEffect, getAnimatedTransitionSource, sendCompletion)
}
present(controller, mediaPickerContext)
}
@ -2240,14 +2240,14 @@ final class StoryItemSetContainerSendMessage {
present(controller, mediaPickerContext)
},
updateMediaPickerContext: { _ in },
completion: { [weak self, weak view] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in
completion: { [weak self, weak view] signals, silentPosting, scheduleTime, messageEffect, getAnimatedTransitionSource, completion in
guard let self, let view else {
return
}
if !inputText.string.isEmpty {
self.clearInputText(view: view)
}
self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, messageEffect: messageEffect, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
}
)
}
@ -2569,7 +2569,7 @@ final class StoryItemSetContainerSendMessage {
}
}
private func enqueueMediaMessages(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyToMessageId: EngineMessage.Id?, replyToStoryId: StoryId?, signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) {
private func enqueueMediaMessages(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyToMessageId: EngineMessage.Id?, replyToStoryId: StoryId?, signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, messageEffect: ChatSendMessageActionSheetController.MessageEffect? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) {
guard let component = view.component else {
return
}
@ -2620,6 +2620,13 @@ final class StoryItemSetContainerSendMessage {
}
}
}
if let messageEffect {
message = message.withUpdatedAttributes { attributes in
var attributes = attributes
attributes.append(EffectMessageAttribute(id: messageEffect.id))
return attributes
}
}
mappedMessages.append(message)
}

View File

@ -183,10 +183,10 @@ private final class AttachmentFileContext: AttachmentMediaPickerContext {
func setCaption(_ caption: NSAttributedString) {
}
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
}
func schedule() {
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
}
func mainButtonAction() {

View File

@ -27,8 +27,8 @@ extension ChatControllerImpl {
subjects: subjects,
presentMediaPicker: { [weak self] subject, saveEditedPhotos, bannedSendPhotos, bannedSendVideos, present in
if let strongSelf = self {
strongSelf.presentMediaPicker(subject: subject, saveEditedPhotos: saveEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, present: present, updateMediaPickerContext: { _ in }, completion: { [weak self] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in
self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
strongSelf.presentMediaPicker(subject: subject, saveEditedPhotos: saveEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, present: present, updateMediaPickerContext: { _ in }, completion: { [weak self] signals, silentPosting, scheduleTime, messageEffect, getAnimatedTransitionSource, completion in
self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, messageEffect: messageEffect, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
})
}
},

View File

@ -9058,7 +9058,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
func enqueueMediaMessages(signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) {
func enqueueMediaMessages(signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, messageEffect: ChatSendMessageActionSheetController.MessageEffect? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) {
self.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(context: self.context, account: self.context.account, signals: signals!)
|> deliverOnMainQueue).startStrict(next: { [weak self] items in
if let strongSelf = self {
@ -9112,6 +9112,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
usedCorrelationId = correlationId
completionImpl = nil
}
if let messageEffect {
message = message.withUpdatedAttributes { attributes in
var attributes = attributes
attributes.append(EffectMessageAttribute(id: messageEffect.id))
return attributes
}
}
mappedMessages.append(message)
}

View File

@ -90,7 +90,7 @@ extension ChatControllerImpl {
}), in: .current)
}
}
controller.multiplePeersSelected = { [weak self, weak controller] peers, peerMap, messageText, mode, forwardOptions in
controller.multiplePeersSelected = { [weak self, weak controller] peers, peerMap, messageText, mode, forwardOptions, _ in
guard let strongSelf = self, let strongController = controller else {
return
}

View File

@ -308,11 +308,11 @@ extension ChatControllerImpl {
completion(controller, mediaPickerContext)
}, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in
attachmentController?.mediaPickerContext = mediaPickerContext
}, completion: { [weak self] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in
}, completion: { [weak self] signals, silentPosting, scheduleTime, messageEffect, getAnimatedTransitionSource, completion in
if !inputText.string.isEmpty {
self?.clearInputText()
}
self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, messageEffect: messageEffect, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
})
case .file:
strongSelf.controllerNavigationDisposable.set(nil)
@ -1136,7 +1136,7 @@ extension ChatControllerImpl {
self.present(actionSheet, in: .window(.root))
}
func presentMediaPicker(subject: MediaPickerScreen.Subject = .assets(nil, .default), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) {
func presentMediaPicker(subject: MediaPickerScreen.Subject = .assets(nil, .default), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, ChatSendMessageActionSheetController.MessageEffect?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) {
var isScheduledMessages = false
if case .scheduledMessages = self.presentationInterfaceState.subject {
isScheduledMessages = true
@ -1210,8 +1210,8 @@ extension ChatControllerImpl {
controller.getCaptionPanelView = { [weak self] in
return self?.getCaptionPanelView(isFile: false)
}
controller.legacyCompletion = { signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion in
completion(signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion)
controller.legacyCompletion = { signals, silently, scheduleTime, messageEffect, getAnimatedTransitionSource, sendCompletion in
completion(signals, silently, scheduleTime, messageEffect, getAnimatedTransitionSource, sendCompletion)
}
present(controller, mediaPickerContext)
}

View File

@ -451,11 +451,11 @@ final class ContactsPickerContext: AttachmentMediaPickerContext {
self.controller?.caption = caption
}
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
self.controller?.contactsNode.requestMultipleAction?(mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil)
}
func schedule() {
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
self.controller?.presentScheduleTimePicker ({ time in
self.controller?.contactsNode.requestMultipleAction?(false, time)
})

View File

@ -35,14 +35,14 @@ final class WebSearchControllerInteraction {
let setSearchQuery: (String) -> Void
let deleteRecentQuery: (String) -> Void
let toggleSelection: (ChatContextResult, Bool) -> Bool
let sendSelected: (ChatContextResult?, Bool, Int32?) -> Void
let schedule: () -> Void
let sendSelected: (ChatContextResult?, Bool, Int32?, ChatSendMessageActionSheetController.MessageEffect?) -> Void
let schedule: (ChatSendMessageActionSheetController.MessageEffect?) -> Void
let avatarCompleted: (UIImage) -> Void
let selectionState: TGMediaSelectionContext?
let editingState: TGMediaEditingContext
var hiddenMediaId: String?
init(openResult: @escaping (ChatContextResult) -> Void, setSearchQuery: @escaping (String) -> Void, deleteRecentQuery: @escaping (String) -> Void, toggleSelection: @escaping (ChatContextResult, Bool) -> Bool, sendSelected: @escaping (ChatContextResult?, Bool, Int32?) -> Void, schedule: @escaping () -> Void, avatarCompleted: @escaping (UIImage) -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
init(openResult: @escaping (ChatContextResult) -> Void, setSearchQuery: @escaping (String) -> Void, deleteRecentQuery: @escaping (String) -> Void, toggleSelection: @escaping (ChatContextResult, Bool) -> Bool, sendSelected: @escaping (ChatContextResult?, Bool, Int32?, ChatSendMessageActionSheetController.MessageEffect?) -> Void, schedule: @escaping (ChatSendMessageActionSheetController.MessageEffect?) -> Void, avatarCompleted: @escaping (UIImage) -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
self.openResult = openResult
self.setSearchQuery = setSearchQuery
self.deleteRecentQuery = deleteRecentQuery
@ -254,7 +254,7 @@ public final class WebSearchController: ViewController {
} else {
return false
}
}, sendSelected: { [weak self] current, silently, scheduleTime in
}, sendSelected: { [weak self] current, silently, scheduleTime, messageEffect in
if let selectionState = selectionState, let results = self?.controllerNode.currentExternalResults {
if let current = current {
let currentItem = LegacyWebSearchItem(result: current)
@ -264,10 +264,10 @@ public final class WebSearchController: ViewController {
sendSelected(results, selectionState, editingState, false)
}
}
}, schedule: { [weak self] in
}, schedule: { [weak self] messageEffect in
if let strongSelf = self {
strongSelf.presentSchedulePicker(false, { [weak self] time in
self?.controllerInteraction?.sendSelected(nil, false, time)
self?.controllerInteraction?.sendSelected(nil, false, time, nil)
})
}
}, avatarCompleted: { result in
@ -606,12 +606,12 @@ public class WebSearchPickerContext: AttachmentMediaPickerContext {
self.interaction?.editingState.setForcedCaption(caption, skipUpdate: true)
}
public func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
self.interaction?.sendSelected(nil, mode == .silently, nil)
public func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
self.interaction?.sendSelected(nil, mode == .silently, nil, messageEffect)
}
public func schedule() {
self.interaction?.schedule()
public func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
self.interaction?.schedule(messageEffect)
}
public func mainButtonAction() {

View File

@ -717,7 +717,7 @@ class WebSearchControllerNode: ASDisplayNode {
}
@objc private func sendPressed() {
self.controllerInteraction.sendSelected(nil, false, nil)
self.controllerInteraction.sendSelected(nil, false, nil, nil)
self.cancel?()
}
@ -740,7 +740,7 @@ class WebSearchControllerNode: ASDisplayNode {
return self?.transitionNode(for: result)?.transitionView()
}, completed: { [weak self] result in
if let strongSelf = self {
strongSelf.controllerInteraction.sendSelected(result, false, nil)
strongSelf.controllerInteraction.sendSelected(result, false, nil, nil)
strongSelf.cancel?()
}
}, getCaptionPanelView: self.getCaptionPanelView, present: present)
@ -760,7 +760,7 @@ class WebSearchControllerNode: ASDisplayNode {
}, baseNavigationController: nil, sendCurrent: { [weak self] result in
if let strongSelf = self {
strongSelf.controllerInteraction.sendSelected(result, false, nil)
strongSelf.controllerInteraction.sendSelected(result, false, nil, nil)
strongSelf.cancel?()
}
})

View File

@ -2092,10 +2092,10 @@ final class WebAppPickerContext: AttachmentMediaPickerContext {
func setCaption(_ caption: NSAttributedString) {
}
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
}
func schedule() {
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
}
func mainButtonAction() {