[WIP] Edit message preview

This commit is contained in:
Isaac 2024-05-26 18:59:44 +04:00
parent 6ff4cd3681
commit 667aa0349a
16 changed files with 487 additions and 1603 deletions

View File

@ -12298,3 +12298,13 @@ Sorry for the inconvenience.";
"Stars.Transfer.Balance" = "Balance"; "Stars.Transfer.Balance" = "Balance";
"Settings.Stars" = "Your Stars"; "Settings.Stars" = "Your Stars";
"Chat.MessageEffectMenu.TitleAddEffect" = "Add an animated effect";
"Chat.MessageEffectMenu.SectionMessageEffects" = "Message Effects";
"Chat.SendMessageMenu.MoveCaptionUp" = "Move Caption Up";
"Chat.SendMessageMenu.MoveCaptionDown" = "Move Caption Down";
"Chat.SendMessageMenu.ToastPremiumRequired.Text" = "Subscribe to [Telegram Premium]() to add this animated effect.";
"BusinessLink.AlertTextLimitText" = "The message text limit is 4096 characters";
"Chat.SendMessageMenu.EditMessage" = "Edit Message";

View File

@ -57,7 +57,7 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessage
self.sendButton.highligthedChanged = { [weak self] highlighted in self.sendButton.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self { if let strongSelf = self {
if strongSelf.sendButtonHasApplyIcon || !strongSelf.sendButtonLongPressEnabled { if !strongSelf.sendButtonLongPressEnabled {
if highlighted { if highlighted {
strongSelf.sendContainerNode.layer.removeAnimation(forKey: "opacity") strongSelf.sendContainerNode.layer.removeAnimation(forKey: "opacity")
strongSelf.sendContainerNode.alpha = 0.4 strongSelf.sendContainerNode.alpha = 0.4

View File

@ -1002,21 +1002,24 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
context: strongSelf.context, context: strongSelf.context,
updatedPresentationData: strongSelf.updatedPresentationData, updatedPresentationData: strongSelf.updatedPresentationData,
peerId: strongSelf.presentationInterfaceState.chatLocation.peerId, peerId: strongSelf.presentationInterfaceState.chatLocation.peerId,
forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds, params: .sendMessage(SendMessageActionSheetControllerParams.SendMessage(
isScheduledMessages: false,
mediaPreview: mediaPreview,
mediaCaptionIsAbove: (captionIsAboveMedia, { [weak strongSelf] value in
guard let strongSelf, let controller = strongSelf.controller, let mediaPickerContext = controller.mediaPickerContext else {
return
}
mediaPickerContext.setCaptionIsAboveMedia(value)
}),
attachment: true,
canSendWhenOnline: sendWhenOnlineAvailable,
forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds ?? []
)),
hasEntityKeyboard: hasEntityKeyboard, hasEntityKeyboard: hasEntityKeyboard,
gesture: gesture, gesture: gesture,
sourceSendButton: node, sourceSendButton: node,
textInputView: textInputNode.textView, textInputView: textInputNode.textView,
mediaPreview: mediaPreview,
mediaCaptionIsAbove: (captionIsAboveMedia, { [weak strongSelf] value in
guard let strongSelf, let controller = strongSelf.controller, let mediaPickerContext = controller.mediaPickerContext else {
return
}
mediaPickerContext.setCaptionIsAboveMedia(value)
}),
emojiViewProvider: textInputPanelNode.emojiViewProvider, emojiViewProvider: textInputPanelNode.emojiViewProvider,
attachment: true,
canSendWhenOnline: sendWhenOnlineAvailable,
completion: { completion: {
}, },
sendMessage: { [weak textInputPanelNode] mode, messageEffect in sendMessage: { [weak textInputPanelNode] mode, messageEffect in

View File

@ -11,169 +11,57 @@ import TextFormat
import ReactionSelectionNode import ReactionSelectionNode
import WallpaperBackgroundNode import WallpaperBackgroundNode
private final class ChatSendMessageActionSheetControllerImpl: ViewController, ChatSendMessageActionSheetController { public enum SendMessageActionSheetControllerParams {
private var controllerNode: ChatSendMessageActionSheetControllerNode { public final class SendMessage {
return self.displayNode as! ChatSendMessageActionSheetControllerNode public let isScheduledMessages: Bool
} public let mediaPreview: ChatSendMessageContextScreenMediaPreview?
public let mediaCaptionIsAbove: (Bool, (Bool) -> Void)?
private let context: AccountContext public let attachment: Bool
public let canSendWhenOnline: Bool
private let peerId: EnginePeer.Id? public let forwardMessageIds: [EngineMessage.Id]
private let isScheduledMessages: Bool
private let forwardMessageIds: [EngineMessage.Id]?
private let hasEntityKeyboard: Bool
private let gesture: ContextGesture
private let sourceSendButton: ASDisplayNode
private let textInputView: UITextView
private let attachment: Bool
private let canSendWhenOnline: Bool
private let completion: () -> Void
private let sendMessage: (SendMode, SendParameters?) -> Void
private let schedule: (SendParameters?) -> Void
private let reactionItems: [ReactionItem]?
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
private var didPlayPresentationAnimation = false
private var validLayout: ContainerViewLayout?
private let hapticFeedback = HapticFeedback()
private let emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: EnginePeer.Id?, isScheduledMessages: Bool = false, forwardMessageIds: [EngineMessage.Id]?, hasEntityKeyboard: Bool, gesture: ContextGesture, sourceSendButton: ASDisplayNode, textInputView: UITextView, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, attachment: Bool = false, canSendWhenOnline: Bool, completion: @escaping () -> Void, sendMessage: @escaping (SendMode, SendParameters?) -> Void, schedule: @escaping (SendParameters?) -> Void, reactionItems: [ReactionItem]? = nil) {
self.context = context
self.peerId = peerId
self.isScheduledMessages = isScheduledMessages
self.forwardMessageIds = forwardMessageIds
self.hasEntityKeyboard = hasEntityKeyboard
self.gesture = gesture
self.sourceSendButton = sourceSendButton
self.textInputView = textInputView
self.emojiViewProvider = emojiViewProvider
self.attachment = attachment
self.canSendWhenOnline = canSendWhenOnline
self.completion = completion
self.sendMessage = sendMessage
self.schedule = schedule
self.reactionItems = reactionItems
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } public init(
isScheduledMessages: Bool,
super.init(navigationBarPresentationData: nil) mediaPreview: ChatSendMessageContextScreenMediaPreview?,
mediaCaptionIsAbove: (Bool, (Bool) -> Void)?,
self.blocksBackgroundWhenInOverlay = true attachment: Bool,
canSendWhenOnline: Bool,
self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData) forwardMessageIds: [EngineMessage.Id]
|> deliverOnMainQueue).startStrict(next: { [weak self] presentationData in ) {
if let strongSelf = self { self.isScheduledMessages = isScheduledMessages
strongSelf.presentationData = presentationData self.mediaPreview = mediaPreview
if strongSelf.isNodeLoaded { self.mediaCaptionIsAbove = mediaCaptionIsAbove
strongSelf.controllerNode.updatePresentationData(presentationData) self.attachment = attachment
} self.canSendWhenOnline = canSendWhenOnline
} self.forwardMessageIds = forwardMessageIds
}).strict()
self.statusBar.statusBarStyle = .Hide
self.statusBar.ignoreInCall = true
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.presentationDataDisposable?.dispose()
}
override public func loadDisplayNode() {
var forwardedCount: Int?
if let forwardMessageIds = self.forwardMessageIds, forwardMessageIds.count > 0 {
forwardedCount = forwardMessageIds.count
}
var reminders = false
var isSecret = false
var canSchedule = false
if let peerId = self.peerId {
reminders = peerId == context.account.peerId
isSecret = peerId.namespace == Namespaces.Peer.SecretChat
canSchedule = !isSecret
}
if self.isScheduledMessages {
canSchedule = false
}
self.displayNode = ChatSendMessageActionSheetControllerNode(context: self.context, presentationData: self.presentationData, reminders: reminders, gesture: gesture, sourceSendButton: self.sourceSendButton, textInputView: self.textInputView, attachment: self.attachment, canSendWhenOnline: self.canSendWhenOnline, forwardedCount: forwardedCount, hasEntityKeyboard: self.hasEntityKeyboard, emojiViewProvider: self.emojiViewProvider, send: { [weak self] in
self?.sendMessage(.generic, nil)
self?.dismiss(cancel: false)
}, sendSilently: { [weak self] in
self?.sendMessage(.silently, nil)
self?.dismiss(cancel: false)
}, sendWhenOnline: { [weak self] in
self?.sendMessage(.whenOnline, nil)
self?.dismiss(cancel: false)
}, schedule: !canSchedule ? nil : { [weak self] in
self?.schedule(nil)
self?.dismiss(cancel: false)
}, cancel: { [weak self] in
self?.dismiss(cancel: true)
}, reactionItems: self.reactionItems)
self.displayNodeDidLoad()
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !self.didPlayPresentationAnimation {
self.didPlayPresentationAnimation = true
self.hapticFeedback.impact()
self.controllerNode.animateIn()
} }
} }
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { public final class EditMessage {
self.validLayout = layout public let messages: [EngineMessage]
public let mediaPreview: ChatSendMessageContextScreenMediaPreview?
super.containerLayoutUpdated(layout, transition: transition) public init(messages: [EngineMessage], mediaPreview: ChatSendMessageContextScreenMediaPreview?) {
self.messages = messages
self.controllerNode.containerLayoutUpdated(layout, transition: transition) self.mediaPreview = mediaPreview
}
} }
override public func dismiss(completion: (() -> Void)? = nil) { case sendMessage(SendMessage)
self.dismiss(cancel: true) case editMessage(EditMessage)
}
private func dismiss(cancel: Bool) {
self.statusBar.statusBarStyle = .Ignore
self.controllerNode.animateOut(cancel: cancel, completion: { [weak self] in
self?.completion()
self?.didPlayPresentationAnimation = false
self?.presentingViewController?.dismiss(animated: false, completion: nil)
})
}
} }
public func makeChatSendMessageActionSheetController( public func makeChatSendMessageActionSheetController(
context: AccountContext, context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
peerId: EnginePeer.Id?, peerId: EnginePeer.Id?,
isScheduledMessages: Bool = false, params: SendMessageActionSheetControllerParams,
forwardMessageIds: [EngineMessage.Id]?,
hasEntityKeyboard: Bool, hasEntityKeyboard: Bool,
gesture: ContextGesture, gesture: ContextGesture,
sourceSendButton: ASDisplayNode, sourceSendButton: ASDisplayNode,
textInputView: UITextView, textInputView: UITextView,
mediaPreview: ChatSendMessageContextScreenMediaPreview? = nil,
mediaCaptionIsAbove: (Bool, (Bool) -> Void)? = nil,
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
wallpaperBackgroundNode: WallpaperBackgroundNode? = nil, wallpaperBackgroundNode: WallpaperBackgroundNode? = nil,
attachment: Bool = false,
canSendWhenOnline: Bool,
completion: @escaping () -> Void, completion: @escaping () -> Void,
sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void, sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void,
schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void, schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void,
@ -182,43 +70,17 @@ public func makeChatSendMessageActionSheetController(
availableMessageEffects: AvailableMessageEffects? = nil, availableMessageEffects: AvailableMessageEffects? = nil,
isPremium: Bool = false isPremium: Bool = false
) -> ChatSendMessageActionSheetController { ) -> ChatSendMessageActionSheetController {
if textInputView.text.isEmpty && !"".isEmpty {
return ChatSendMessageActionSheetControllerImpl(
context: context,
updatedPresentationData: updatedPresentationData,
peerId: peerId,
isScheduledMessages: isScheduledMessages,
forwardMessageIds: forwardMessageIds,
hasEntityKeyboard: hasEntityKeyboard,
gesture: gesture,
sourceSendButton: sourceSendButton,
textInputView: textInputView,
emojiViewProvider: emojiViewProvider,
attachment: attachment,
canSendWhenOnline: canSendWhenOnline,
completion: completion,
sendMessage: sendMessage,
schedule: schedule,
reactionItems: nil
)
}
return ChatSendMessageContextScreen( return ChatSendMessageContextScreen(
context: context, context: context,
updatedPresentationData: updatedPresentationData, updatedPresentationData: updatedPresentationData,
peerId: peerId, peerId: peerId,
isScheduledMessages: isScheduledMessages, params: params,
forwardMessageIds: forwardMessageIds,
hasEntityKeyboard: hasEntityKeyboard, hasEntityKeyboard: hasEntityKeyboard,
gesture: gesture, gesture: gesture,
sourceSendButton: sourceSendButton, sourceSendButton: sourceSendButton,
textInputView: textInputView, textInputView: textInputView,
mediaPreview: mediaPreview,
mediaCaptionIsAbove: mediaCaptionIsAbove,
emojiViewProvider: emojiViewProvider, emojiViewProvider: emojiViewProvider,
wallpaperBackgroundNode: wallpaperBackgroundNode, wallpaperBackgroundNode: wallpaperBackgroundNode,
attachment: attachment,
canSendWhenOnline: canSendWhenOnline,
completion: completion, completion: completion,
sendMessage: sendMessage, sendMessage: sendMessage,
schedule: schedule, schedule: schedule,

View File

@ -56,18 +56,13 @@ final class ChatSendMessageContextScreenComponent: Component {
let context: AccountContext let context: AccountContext
let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
let peerId: EnginePeer.Id? let peerId: EnginePeer.Id?
let isScheduledMessages: Bool let params: SendMessageActionSheetControllerParams
let forwardMessageIds: [EngineMessage.Id]?
let hasEntityKeyboard: Bool let hasEntityKeyboard: Bool
let gesture: ContextGesture let gesture: ContextGesture
let sourceSendButton: ASDisplayNode let sourceSendButton: ASDisplayNode
let textInputView: UITextView let textInputView: UITextView
let mediaPreview: ChatSendMessageContextScreenMediaPreview?
let mediaCaptionIsAbove: (Bool, (Bool) -> Void)?
let emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)? let emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?
let wallpaperBackgroundNode: WallpaperBackgroundNode? let wallpaperBackgroundNode: WallpaperBackgroundNode?
let attachment: Bool
let canSendWhenOnline: Bool
let completion: () -> Void let completion: () -> Void
let sendMessage: (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void let sendMessage: (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void
let schedule: (ChatSendMessageActionSheetController.SendParameters?) -> Void let schedule: (ChatSendMessageActionSheetController.SendParameters?) -> Void
@ -80,18 +75,13 @@ final class ChatSendMessageContextScreenComponent: Component {
context: AccountContext, context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
peerId: EnginePeer.Id?, peerId: EnginePeer.Id?,
isScheduledMessages: Bool, params: SendMessageActionSheetControllerParams,
forwardMessageIds: [EngineMessage.Id]?,
hasEntityKeyboard: Bool, hasEntityKeyboard: Bool,
gesture: ContextGesture, gesture: ContextGesture,
sourceSendButton: ASDisplayNode, sourceSendButton: ASDisplayNode,
textInputView: UITextView, textInputView: UITextView,
mediaPreview: ChatSendMessageContextScreenMediaPreview?,
mediaCaptionIsAbove: (Bool, (Bool) -> Void)?,
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
wallpaperBackgroundNode: WallpaperBackgroundNode?, wallpaperBackgroundNode: WallpaperBackgroundNode?,
attachment: Bool,
canSendWhenOnline: Bool,
completion: @escaping () -> Void, completion: @escaping () -> Void,
sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void, sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void,
schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void, schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void,
@ -103,18 +93,13 @@ final class ChatSendMessageContextScreenComponent: Component {
self.context = context self.context = context
self.updatedPresentationData = updatedPresentationData self.updatedPresentationData = updatedPresentationData
self.peerId = peerId self.peerId = peerId
self.isScheduledMessages = isScheduledMessages self.params = params
self.forwardMessageIds = forwardMessageIds
self.hasEntityKeyboard = hasEntityKeyboard self.hasEntityKeyboard = hasEntityKeyboard
self.gesture = gesture self.gesture = gesture
self.sourceSendButton = sourceSendButton self.sourceSendButton = sourceSendButton
self.textInputView = textInputView self.textInputView = textInputView
self.mediaPreview = mediaPreview
self.mediaCaptionIsAbove = mediaCaptionIsAbove
self.emojiViewProvider = emojiViewProvider self.emojiViewProvider = emojiViewProvider
self.wallpaperBackgroundNode = wallpaperBackgroundNode self.wallpaperBackgroundNode = wallpaperBackgroundNode
self.attachment = attachment
self.canSendWhenOnline = canSendWhenOnline
self.completion = completion self.completion = completion
self.sendMessage = sendMessage self.sendMessage = sendMessage
self.schedule = schedule self.schedule = schedule
@ -331,7 +316,14 @@ final class ChatSendMessageContextScreenComponent: Component {
let themeUpdated = environment.theme !== self.environment?.theme let themeUpdated = environment.theme !== self.environment?.theme
if self.component == nil { if self.component == nil {
self.mediaCaptionIsAbove = component.mediaCaptionIsAbove?.0 ?? false switch component.params {
case let .sendMessage(sendMessage):
self.mediaCaptionIsAbove = sendMessage.mediaCaptionIsAbove?.0 ?? false
case let .editMessage(editMessage):
self.mediaCaptionIsAbove = editMessage.messages.contains(where: {
return $0.attributes.contains(where: { $0 is InvertMediaMessageAttribute })
})
}
component.gesture.externalUpdated = { [weak self] view, location in component.gesture.externalUpdated = { [weak self] view, location in
guard let self, let actionsStackNode = self.actionsStackNode else { guard let self, let actionsStackNode = self.actionsStackNode else {
@ -375,7 +367,15 @@ final class ChatSendMessageContextScreenComponent: Component {
) )
} }
var isMessageVisible = component.mediaPreview != nil var mediaPreview: ChatSendMessageContextScreenMediaPreview?
switch component.params {
case let .sendMessage(sendMessage):
mediaPreview = sendMessage.mediaPreview
case let .editMessage(editMessage):
mediaPreview = editMessage.mediaPreview
}
var isMessageVisible: Bool = mediaPreview != nil
let textString: NSAttributedString let textString: NSAttributedString
if let attributedText = component.textInputView.attributedText { if let attributedText = component.textInputView.attributedText {
@ -391,7 +391,14 @@ final class ChatSendMessageContextScreenComponent: Component {
if let current = self.sendButton { if let current = self.sendButton {
sendButton = current sendButton = current
} else { } else {
sendButton = SendButton() let sendButtonKind: SendButton.Kind
switch component.params {
case .sendMessage:
sendButtonKind = .send
case .editMessage:
sendButtonKind = .edit
}
sendButton = SendButton(kind: sendButtonKind)
sendButton.accessibilityLabel = environment.strings.MediaPicker_Send sendButton.accessibilityLabel = environment.strings.MediaPicker_Send
sendButton.addTarget(self, action: #selector(self.onSendButtonPressed), for: .touchUpInside) sendButton.addTarget(self, action: #selector(self.onSendButtonPressed), for: .touchUpInside)
/*if let snapshotView = component.sourceSendButton.view.snapshotView(afterScreenUpdates: false) { /*if let snapshotView = component.sourceSendButton.view.snapshotView(afterScreenUpdates: false) {
@ -427,22 +434,43 @@ final class ChatSendMessageContextScreenComponent: Component {
var reminders = false var reminders = false
var isSecret = false var isSecret = false
var canSchedule = false var canSchedule = false
if let peerId = component.peerId { switch component.params {
reminders = peerId == component.context.account.peerId case let .sendMessage(sendMessage):
isSecret = peerId.namespace == Namespaces.Peer.SecretChat if let peerId = component.peerId {
canSchedule = !isSecret reminders = peerId == component.context.account.peerId
} isSecret = peerId.namespace == Namespaces.Peer.SecretChat
if component.isScheduledMessages { canSchedule = !isSecret
canSchedule = false }
if sendMessage.isScheduledMessages {
canSchedule = false
}
case .editMessage:
break
} }
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
if component.mediaCaptionIsAbove != nil, textString.length != 0, case .media = component.mediaPreview?.layoutType {
//TODO:localize let canAdjustMediaCaptionPosition: Bool
switch component.params {
case let .sendMessage(sendMessage):
if case .media = mediaPreview?.layoutType {
canAdjustMediaCaptionPosition = sendMessage.mediaCaptionIsAbove != nil
} else {
canAdjustMediaCaptionPosition = false
}
case .editMessage:
if case .media = mediaPreview?.layoutType {
canAdjustMediaCaptionPosition = textString.length != 0
} else {
canAdjustMediaCaptionPosition = false
}
}
if canAdjustMediaCaptionPosition, textString.length != 0 {
let mediaCaptionIsAbove = self.mediaCaptionIsAbove let mediaCaptionIsAbove = self.mediaCaptionIsAbove
items.append(.action(ContextMenuActionItem( items.append(.action(ContextMenuActionItem(
id: AnyHashable("captionPosition"), id: AnyHashable("captionPosition"),
text: mediaCaptionIsAbove ? "Move Caption Down" : "Move Caption Up", text: mediaCaptionIsAbove ? presentationData.strings.Chat_SendMessageMenu_MoveCaptionDown : presentationData.strings.Chat_SendMessageMenu_MoveCaptionUp,
icon: { _ in icon: { _ in
return nil return nil
}, iconAnimation: ContextMenuActionItem.IconAnimation( }, iconAnimation: ContextMenuActionItem.IconAnimation(
@ -452,7 +480,12 @@ final class ChatSendMessageContextScreenComponent: Component {
return return
} }
self.mediaCaptionIsAbove = !self.mediaCaptionIsAbove self.mediaCaptionIsAbove = !self.mediaCaptionIsAbove
component.mediaCaptionIsAbove?.1(self.mediaCaptionIsAbove) switch component.params {
case let .sendMessage(sendMessage):
sendMessage.mediaCaptionIsAbove?.1(self.mediaCaptionIsAbove)
case .editMessage:
break
}
if !self.isUpdating { if !self.isUpdating {
self.state?.updated(transition: .spring(duration: 0.35)) self.state?.updated(transition: .spring(duration: 0.35))
} }
@ -461,34 +494,14 @@ final class ChatSendMessageContextScreenComponent: Component {
items.append(.separator) items.append(.separator)
} }
if !reminders { switch component.params {
items.append(.action(ContextMenuActionItem( case let.sendMessage(sendMessage):
id: AnyHashable("silent"), if !reminders {
text: environment.strings.Conversation_SendMessage_SendSilently,
icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, _ in
guard let self, let component = self.component else {
return
}
self.animateOutToEmpty = true
let sendParameters = ChatSendMessageActionSheetController.SendParameters(
effect: self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.SendParameters.Effect(id: $0.id) }),
textIsAboveMedia: self.mediaCaptionIsAbove
)
component.sendMessage(.silently, sendParameters)
self.environment?.controller()?.dismiss()
}
)))
if component.canSendWhenOnline && canSchedule {
items.append(.action(ContextMenuActionItem( items.append(.action(ContextMenuActionItem(
id: AnyHashable("whenOnline"), id: AnyHashable("silent"),
text: environment.strings.Conversation_SendMessage_SendWhenOnline, text: environment.strings.Conversation_SendMessage_SendSilently,
icon: { theme in icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/WhenOnlineIcon"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, _ in }, action: { [weak self] _, _ in
guard let self, let component = self.component else { guard let self, let component = self.component else {
return return
@ -500,18 +513,62 @@ final class ChatSendMessageContextScreenComponent: Component {
textIsAboveMedia: self.mediaCaptionIsAbove textIsAboveMedia: self.mediaCaptionIsAbove
) )
component.sendMessage(.whenOnline, sendParameters) component.sendMessage(.silently, sendParameters)
self.environment?.controller()?.dismiss()
}
)))
if sendMessage.canSendWhenOnline && canSchedule {
items.append(.action(ContextMenuActionItem(
id: AnyHashable("whenOnline"),
text: environment.strings.Conversation_SendMessage_SendWhenOnline,
icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/WhenOnlineIcon"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, _ in
guard let self, let component = self.component else {
return
}
self.animateOutToEmpty = true
let sendParameters = ChatSendMessageActionSheetController.SendParameters(
effect: self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.SendParameters.Effect(id: $0.id) }),
textIsAboveMedia: self.mediaCaptionIsAbove
)
component.sendMessage(.whenOnline, sendParameters)
self.environment?.controller()?.dismiss()
}
)))
}
}
if canSchedule {
items.append(.action(ContextMenuActionItem(
id: AnyHashable("schedule"),
text: reminders ? environment.strings.Conversation_SendMessage_SetReminder: environment.strings.Conversation_SendMessage_ScheduleMessage,
icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, _ in
guard let self, let component = self.component else {
return
}
self.animateOutToEmpty = true
let sendParameters = ChatSendMessageActionSheetController.SendParameters(
effect: self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.SendParameters.Effect(id: $0.id) }),
textIsAboveMedia: self.mediaCaptionIsAbove
)
component.schedule(sendParameters)
self.environment?.controller()?.dismiss() self.environment?.controller()?.dismiss()
} }
))) )))
} }
} case .editMessage:
if canSchedule {
items.append(.action(ContextMenuActionItem( items.append(.action(ContextMenuActionItem(
id: AnyHashable("schedule"), id: AnyHashable("silent"),
text: reminders ? environment.strings.Conversation_SendMessage_SetReminder: environment.strings.Conversation_SendMessage_ScheduleMessage, text: environment.strings.Chat_SendMessageMenu_EditMessage,
icon: { theme in icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, _ in }, action: { [weak self] _, _ in
guard let self, let component = self.component else { guard let self, let component = self.component else {
return return
@ -519,11 +576,11 @@ final class ChatSendMessageContextScreenComponent: Component {
self.animateOutToEmpty = true self.animateOutToEmpty = true
let sendParameters = ChatSendMessageActionSheetController.SendParameters( let sendParameters = ChatSendMessageActionSheetController.SendParameters(
effect: self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.SendParameters.Effect(id: $0.id) }), effect: nil,
textIsAboveMedia: self.mediaCaptionIsAbove textIsAboveMedia: self.mediaCaptionIsAbove
) )
component.schedule(sendParameters) component.sendMessage(.generic, sendParameters)
self.environment?.controller()?.dismiss() self.environment?.controller()?.dismiss()
} }
))) )))
@ -636,7 +693,7 @@ final class ChatSendMessageContextScreenComponent: Component {
let messageTextInsets = sourceMessageTextInsets let messageTextInsets = sourceMessageTextInsets
let messageItemViewContainerSize: CGSize let messageItemViewContainerSize: CGSize
if let mediaPreview = component.mediaPreview { if let mediaPreview {
switch mediaPreview.layoutType { switch mediaPreview.layoutType {
case .message, .media: case .message, .media:
messageItemViewContainerSize = CGSize(width: availableSize.width - 16.0 - 40.0, height: availableSize.height) messageItemViewContainerSize = CGSize(width: availableSize.width - 16.0 - 40.0, height: availableSize.height)
@ -654,7 +711,7 @@ final class ChatSendMessageContextScreenComponent: Component {
textString: textString, textString: textString,
sourceTextInputView: component.textInputView as? ChatInputTextView, sourceTextInputView: component.textInputView as? ChatInputTextView,
emojiViewProvider: component.emojiViewProvider, emojiViewProvider: component.emojiViewProvider,
sourceMediaPreview: component.mediaPreview, sourceMediaPreview: mediaPreview,
mediaCaptionIsAbove: self.mediaCaptionIsAbove, mediaCaptionIsAbove: self.mediaCaptionIsAbove,
textInsets: messageTextInsets, textInsets: messageTextInsets,
explicitBackgroundSize: explicitMessageBackgroundSize, explicitBackgroundSize: explicitMessageBackgroundSize,
@ -671,7 +728,6 @@ final class ChatSendMessageContextScreenComponent: Component {
if let current = self.reactionContextNode { if let current = self.reactionContextNode {
reactionContextNode = current reactionContextNode = current
} else { } else {
//TODO:localize
reactionContextNode = ReactionContextNode( reactionContextNode = ReactionContextNode(
context: component.context, context: component.context,
animationCache: component.context.animationCache, animationCache: component.context.animationCache,
@ -692,7 +748,7 @@ final class ChatSendMessageContextScreenComponent: Component {
return ReactionContextItem.reaction(item: item, icon: icon) return ReactionContextItem.reaction(item: item, icon: icon)
}, },
selectedItems: Set(), selectedItems: Set(),
title: "Add an animated effect", title: presentationData.strings.Chat_MessageEffectMenu_TitleAddEffect,
reactionsLocked: false, reactionsLocked: false,
alwaysAllowPremiumReactions: false, alwaysAllowPremiumReactions: false,
allPresetReactionsAreAvailable: true, allPresetReactionsAreAvailable: true,
@ -937,13 +993,12 @@ final class ChatSendMessageContextScreenComponent: Component {
} }
} }
//TODO:localize
let presentationData = component.updatedPresentationData?.initial ?? component.context.sharedContext.currentPresentationData.with({ $0 }) let presentationData = component.updatedPresentationData?.initial ?? component.context.sharedContext.currentPresentationData.with({ $0 })
self.environment?.controller()?.present(UndoOverlayController( self.environment?.controller()?.present(UndoOverlayController(
presentationData: presentationData, presentationData: presentationData,
content: .premiumPaywall( content: .premiumPaywall(
title: nil, title: nil,
text: "Subscribe to [Telegram Premium]() to add this animated effect.", text: presentationData.strings.Chat_SendMessageMenu_ToastPremiumRequired_Text,
customUndoText: nil, customUndoText: nil,
timeout: nil, timeout: nil,
linkAction: nil linkAction: nil
@ -984,7 +1039,7 @@ final class ChatSendMessageContextScreenComponent: Component {
} }
var readyMessageItemFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 8.0 - messageItemSize.width, y: readySendButtonFrame.maxY - 6.0 - messageItemSize.height), size: messageItemSize) var readyMessageItemFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 8.0 - messageItemSize.width, y: readySendButtonFrame.maxY - 6.0 - messageItemSize.height), size: messageItemSize)
if let mediaPreview = component.mediaPreview { if let mediaPreview {
switch mediaPreview.layoutType { switch mediaPreview.layoutType {
case .message, .media: case .message, .media:
break break
@ -1012,7 +1067,7 @@ final class ChatSendMessageContextScreenComponent: Component {
readySendButtonFrame.origin.y -= inputCoverOverflow readySendButtonFrame.origin.y -= inputCoverOverflow
} }
if let mediaPreview = component.mediaPreview { if let mediaPreview {
switch mediaPreview.layoutType { switch mediaPreview.layoutType {
case .message, .media: case .message, .media:
break break
@ -1034,7 +1089,7 @@ final class ChatSendMessageContextScreenComponent: Component {
let sendButtonFrame: CGRect let sendButtonFrame: CGRect
switch self.presentationAnimationState { switch self.presentationAnimationState {
case .initial: case .initial:
if component.mediaPreview != nil { if mediaPreview != nil {
messageItemFrame = readyMessageItemFrame messageItemFrame = readyMessageItemFrame
actionsStackFrame = readyActionsStackFrame actionsStackFrame = readyActionsStackFrame
} else { } else {
@ -1048,7 +1103,7 @@ final class ChatSendMessageContextScreenComponent: Component {
actionsStackFrame = readyActionsStackFrame actionsStackFrame = readyActionsStackFrame
sendButtonFrame = readySendButtonFrame sendButtonFrame = readySendButtonFrame
} else { } else {
if component.mediaPreview != nil { if mediaPreview != nil {
messageItemFrame = readyMessageItemFrame messageItemFrame = readyMessageItemFrame
actionsStackFrame = readyActionsStackFrame actionsStackFrame = readyActionsStackFrame
} else { } else {
@ -1066,7 +1121,7 @@ final class ChatSendMessageContextScreenComponent: Component {
transition.setFrame(view: messageItemView, frame: messageItemFrame) transition.setFrame(view: messageItemView, frame: messageItemFrame)
transition.setAlpha(view: messageItemView, alpha: isMessageVisible ? 1.0 : 0.0) transition.setAlpha(view: messageItemView, alpha: isMessageVisible ? 1.0 : 0.0)
messageItemView.updateClippingRect( messageItemView.updateClippingRect(
sourceMediaPreview: component.mediaPreview, sourceMediaPreview: mediaPreview,
isAnimatedIn: self.presentationAnimationState.key == .animatedIn, isAnimatedIn: self.presentationAnimationState.key == .animatedIn,
localFrame: messageItemFrame, localFrame: messageItemFrame,
containerSize: availableSize, containerSize: availableSize,
@ -1120,7 +1175,7 @@ final class ChatSendMessageContextScreenComponent: Component {
let reactionContextY = environment.statusBarHeight let reactionContextY = environment.statusBarHeight
let size = availableSize let size = availableSize
var reactionsAnchorRect = messageItemFrame var reactionsAnchorRect = messageItemFrame
if let mediaPreview = component.mediaPreview { if let mediaPreview {
switch mediaPreview.layoutType { switch mediaPreview.layoutType {
case .message, .media: case .message, .media:
reactionsAnchorRect.size.width += 100.0 reactionsAnchorRect.size.width += 100.0
@ -1193,14 +1248,14 @@ final class ChatSendMessageContextScreenComponent: Component {
guard let component = self.component else { guard let component = self.component else {
return return
} }
if component.mediaPreview == nil { if mediaPreview == nil {
component.textInputView.isHidden = true component.textInputView.isHidden = true
} }
component.sourceSendButton.isHidden = true component.sourceSendButton.isHidden = true
}) })
} }
} else { } else {
if component.mediaPreview == nil { if mediaPreview == nil {
component.textInputView.isHidden = true component.textInputView.isHidden = true
} }
component.sourceSendButton.isHidden = true component.sourceSendButton.isHidden = true
@ -1212,7 +1267,7 @@ final class ChatSendMessageContextScreenComponent: Component {
backgroundAlpha = 0.0 backgroundAlpha = 0.0
if self.animateOutToEmpty { if self.animateOutToEmpty {
if component.mediaPreview == nil { if mediaPreview == nil {
component.textInputView.isHidden = false component.textInputView.isHidden = false
} }
component.sourceSendButton.isHidden = false component.sourceSendButton.isHidden = false
@ -1234,7 +1289,7 @@ final class ChatSendMessageContextScreenComponent: Component {
if !self.performedActionsOnAnimateOut { if !self.performedActionsOnAnimateOut {
self.performedActionsOnAnimateOut = true self.performedActionsOnAnimateOut = true
if let component = self.component, !self.animateOutToEmpty { if let component = self.component, !self.animateOutToEmpty {
if component.mediaPreview == nil { if mediaPreview == nil {
component.textInputView.isHidden = false component.textInputView.isHidden = false
} }
component.sourceSendButton.isHidden = false component.sourceSendButton.isHidden = false
@ -1277,18 +1332,13 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
context: AccountContext, context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
peerId: EnginePeer.Id?, peerId: EnginePeer.Id?,
isScheduledMessages: Bool, params: SendMessageActionSheetControllerParams,
forwardMessageIds: [EngineMessage.Id]?,
hasEntityKeyboard: Bool, hasEntityKeyboard: Bool,
gesture: ContextGesture, gesture: ContextGesture,
sourceSendButton: ASDisplayNode, sourceSendButton: ASDisplayNode,
textInputView: UITextView, textInputView: UITextView,
mediaPreview: ChatSendMessageContextScreenMediaPreview?,
mediaCaptionIsAbove: (Bool, (Bool) -> Void)?,
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
wallpaperBackgroundNode: WallpaperBackgroundNode?, wallpaperBackgroundNode: WallpaperBackgroundNode?,
attachment: Bool,
canSendWhenOnline: Bool,
completion: @escaping () -> Void, completion: @escaping () -> Void,
sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void, sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void,
schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void, schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void,
@ -1305,18 +1355,13 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
context: context, context: context,
updatedPresentationData: updatedPresentationData, updatedPresentationData: updatedPresentationData,
peerId: peerId, peerId: peerId,
isScheduledMessages: isScheduledMessages, params: params,
forwardMessageIds: forwardMessageIds,
hasEntityKeyboard: hasEntityKeyboard, hasEntityKeyboard: hasEntityKeyboard,
gesture: gesture, gesture: gesture,
sourceSendButton: sourceSendButton, sourceSendButton: sourceSendButton,
textInputView: textInputView, textInputView: textInputView,
mediaPreview: mediaPreview,
mediaCaptionIsAbove: mediaCaptionIsAbove,
emojiViewProvider: emojiViewProvider, emojiViewProvider: emojiViewProvider,
wallpaperBackgroundNode: wallpaperBackgroundNode, wallpaperBackgroundNode: wallpaperBackgroundNode,
attachment: attachment,
canSendWhenOnline: canSendWhenOnline,
completion: completion, completion: completion,
sendMessage: sendMessage, sendMessage: sendMessage,
schedule: schedule, schedule: schedule,

View File

@ -18,6 +18,7 @@ import MultilineTextWithEntitiesComponent
import ReactionButtonListComponent import ReactionButtonListComponent
import MultilineTextComponent import MultilineTextComponent
import ChatInputTextNode import ChatInputTextNode
import EmojiTextAttachmentView
private final class EffectIcon: Component { private final class EffectIcon: Component {
enum Content: Equatable { enum Content: Equatable {
@ -134,6 +135,68 @@ private final class EffectIcon: Component {
} }
} }
final class CustomEmojiContainerView: UIView {
private let emojiViewProvider: (ChatTextInputTextCustomEmojiAttribute) -> UIView?
private var emojiLayers: [InlineStickerItemLayer.Key: UIView] = [:]
init(emojiViewProvider: @escaping (ChatTextInputTextCustomEmojiAttribute) -> UIView?) {
self.emojiViewProvider = emojiViewProvider
super.init(frame: CGRect())
}
required init(coder: NSCoder) {
preconditionFailure()
}
func update(emojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)]) {
var nextIndexById: [Int64: Int] = [:]
var validKeys = Set<InlineStickerItemLayer.Key>()
for (rect, emoji) in emojiRects {
let index: Int
if let nextIndex = nextIndexById[emoji.fileId] {
index = nextIndex
} else {
index = 0
}
nextIndexById[emoji.fileId] = index + 1
let key = InlineStickerItemLayer.Key(id: emoji.fileId, index: index)
let view: UIView
if let current = self.emojiLayers[key] {
view = current
} else if let newView = self.emojiViewProvider(emoji) {
view = newView
self.addSubview(newView)
self.emojiLayers[key] = view
} else {
continue
}
let size = CGSize(width: 24.0, height: 24.0)
view.frame = CGRect(origin: CGPoint(x: floor(rect.midX - size.width / 2.0), y: floor(rect.midY - size.height / 2.0)), size: size)
validKeys.insert(key)
}
var removeKeys: [InlineStickerItemLayer.Key] = []
for (key, view) in self.emojiLayers {
if !validKeys.contains(key) {
removeKeys.append(key)
view.removeFromSuperview()
}
}
for key in removeKeys {
self.emojiLayers.removeValue(forKey: key)
}
}
}
final class MessageItemView: UIView { final class MessageItemView: UIView {
private let backgroundWallpaperNode: ChatMessageBubbleBackdrop private let backgroundWallpaperNode: ChatMessageBubbleBackdrop
private let backgroundNode: ChatMessageBackground private let backgroundNode: ChatMessageBackground

View File

@ -19,6 +19,13 @@ import ActivityIndicator
import RadialStatusNode import RadialStatusNode
final class SendButton: HighlightTrackingButton { final class SendButton: HighlightTrackingButton {
enum Kind {
case send
case edit
}
private let kind: Kind
private let containerView: UIView private let containerView: UIView
private var backgroundContent: WallpaperBubbleBackgroundNode? private var backgroundContent: WallpaperBubbleBackgroundNode?
private let backgroundLayer: SimpleLayer private let backgroundLayer: SimpleLayer
@ -28,7 +35,9 @@ final class SendButton: HighlightTrackingButton {
private var didProcessSourceCustomContent: Bool = false private var didProcessSourceCustomContent: Bool = false
private var sourceCustomContentView: UIView? private var sourceCustomContentView: UIView?
override init(frame: CGRect) { init(kind: Kind) {
self.kind = kind
self.containerView = UIView() self.containerView = UIView()
self.containerView.isUserInteractionEnabled = false self.containerView.isUserInteractionEnabled = false
@ -37,7 +46,7 @@ final class SendButton: HighlightTrackingButton {
self.iconView = UIImageView() self.iconView = UIImageView()
self.iconView.isUserInteractionEnabled = false self.iconView.isUserInteractionEnabled = false
super.init(frame: frame) super.init(frame: CGRect())
self.containerView.clipsToBounds = true self.containerView.clipsToBounds = true
self.addSubview(self.containerView) self.addSubview(self.containerView)
@ -100,7 +109,12 @@ final class SendButton: HighlightTrackingButton {
} }
if self.iconView.image == nil { if self.iconView.image == nil {
self.iconView.image = PresentationResourcesChat.chatInputPanelSendIconImage(presentationData.theme) switch self.kind {
case .send:
self.iconView.image = PresentationResourcesChat.chatInputPanelSendIconImage(presentationData.theme)
case .edit:
self.iconView.image = PresentationResourcesChat.chatInputPanelApplyIconImage(presentationData.theme)
}
} }
if let sourceCustomContentView = self.sourceCustomContentView { if let sourceCustomContentView = self.sourceCustomContentView {

View File

@ -2502,8 +2502,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
mediaCaptionIsAbove = interaction.captionIsAboveMedia mediaCaptionIsAbove = interaction.captionIsAboveMedia
} }
//TODO:localize items.append(.action(ContextMenuActionItem(text: mediaCaptionIsAbove ? strings.Chat_SendMessageMenu_MoveCaptionDown : strings.Chat_SendMessageMenu_MoveCaptionUp, icon: { _ in return nil }, iconAnimation: ContextMenuActionItem.IconAnimation(
items.append(.action(ContextMenuActionItem(text: mediaCaptionIsAbove ? "Move Caption Down" : "Move Caption Up", icon: { _ in return nil }, iconAnimation: ContextMenuActionItem.IconAnimation(
name: !mediaCaptionIsAbove ? "message_preview_sort_above" : "message_preview_sort_below" name: !mediaCaptionIsAbove ? "message_preview_sort_above" : "message_preview_sort_below"
), action: { [weak self] _, f in ), action: { [weak self] _, f in
f(.default) f(.default)

View File

@ -1796,6 +1796,9 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
return return
} }
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 })
let strings = presentationData.strings
switch query { switch query {
case .none: case .none:
self.emojiSearchDisposable.set(nil) self.emojiSearchDisposable.set(nil)
@ -1929,7 +1932,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
} else { } else {
resultGroupIndexById[groupId] = resultGroups.count resultGroupIndexById[groupId] = resultGroups.count
//TODO:localize //TODO:localize
resultGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: i == 0 ? nil : "Message Effects", subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, hasEdit: false, headerItem: nil, items: [resultItem])) resultGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: i == 0 ? nil : strings.Chat_MessageEffectMenu_SectionMessageEffects, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, hasEdit: false, headerItem: nil, items: [resultItem]))
} }
} }
} }
@ -2281,7 +2284,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
} else { } else {
resultGroupIndexById[groupId] = resultGroups.count resultGroupIndexById[groupId] = resultGroups.count
//TODO:localize //TODO:localize
resultGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: i == 0 ? nil : "Message Effects", subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, hasEdit: false, headerItem: nil, items: [resultItem])) resultGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: i == 0 ? nil : strings.Chat_MessageEffectMenu_SectionMessageEffects, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, hasEdit: false, headerItem: nil, items: [resultItem]))
} }
} }
} }

View File

@ -2256,8 +2256,7 @@ public extension EmojiPagerContentComponent {
itemGroups[groupIndex].items.append(resultItem) itemGroups[groupIndex].items.append(resultItem)
} else { } else {
itemGroupIndexById[groupId] = itemGroups.count itemGroupIndexById[groupId] = itemGroups.count
//TODO:localize itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: i == 0 ? nil : strings.Chat_MessageEffectMenu_SectionMessageEffects, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, hasEdit: false, headerItem: nil, items: [resultItem]))
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: i == 0 ? nil : "Message Effects", subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, hasEdit: false, headerItem: nil, items: [resultItem]))
} }
} }
} }

View File

@ -691,24 +691,44 @@ final class PeerSelectionControllerNode: ASDisplayNode {
hasEntityKeyboard = true hasEntityKeyboard = true
} }
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: { let controller = makeChatSendMessageActionSheetController(
}, sendMessage: { [weak textInputPanelNode] mode, messageEffect in context: strongSelf.context,
switch mode { peerId: strongSelf.presentationInterfaceState.chatLocation.peerId,
case .generic: params: .sendMessage(SendMessageActionSheetControllerParams.SendMessage(
textInputPanelNode?.sendMessage(.generic, messageEffect) isScheduledMessages: false,
case .silently: mediaPreview: nil,
textInputPanelNode?.sendMessage(.silent, messageEffect) mediaCaptionIsAbove: nil,
case .whenOnline: attachment: false,
textInputPanelNode?.sendMessage(.whenOnline, messageEffect) canSendWhenOnline: false,
forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds ?? []
)),
hasEntityKeyboard: hasEntityKeyboard,
gesture: gesture,
sourceSendButton: node,
textInputView: textInputNode.textView,
emojiViewProvider: textInputPanelNode.emojiViewProvider,
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)
},
openPremiumPaywall: { [weak controller] c in
guard let controller else {
return
}
controller.push(c)
} }
}, schedule: { [weak textInputPanelNode] messageEffect in )
textInputPanelNode?.sendMessage(.schedule, messageEffect)
}, openPremiumPaywall: { [weak controller] c in
guard let controller else {
return
}
controller.push(c)
})
strongSelf.presentInGlobalOverlay(controller, nil) strongSelf.presentInGlobalOverlay(controller, nil)
}, openScheduledMessages: { }, openScheduledMessages: {
}, openPeersNearby: { }, openPeersNearby: {

View File

@ -2918,13 +2918,13 @@ final class StoryItemSetContainerSendMessage {
return return
} }
if !hashtag.isEmpty { if !hashtag.isEmpty {
if "".isEmpty { /*if !"".isEmpty {
let searchController = component.context.sharedContext.makeStorySearchController(context: component.context, query: hashtag) let searchController = component.context.sharedContext.makeStorySearchController(context: component.context, query: hashtag)
navigationController.pushViewController(searchController) navigationController.pushViewController(searchController)
} else { } else {*/
let searchController = component.context.sharedContext.makeHashtagSearchController(context: component.context, peer: peer.flatMap(EnginePeer.init), query: hashtag, all: true) let searchController = component.context.sharedContext.makeHashtagSearchController(context: component.context, peer: peer.flatMap(EnginePeer.init), query: hashtag, all: true)
navigationController.pushViewController(searchController) navigationController.pushViewController(searchController)
} //}
} }
})) }))
} }

View File

@ -1276,8 +1276,7 @@ extension ChatControllerImpl {
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
case let .businessLinkSetup(link): case let .businessLinkSetup(link):
if messages.count > 1 { if messages.count > 1 {
//TODO:localize strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.BusinessLink_AlertTextLimitText, actions: [
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: "The message text limit is 4096 characters", actions: [
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {}) TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})
]), in: .window(.root)) ]), in: .window(.root))

View File

@ -55,106 +55,178 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no
return user.isPremium return user.isPremium
} }
let editMessages: Signal<[EngineMessage], NoError>
if let editMessage = selfController.presentationInterfaceState.interfaceState.editMessage {
editMessages = selfController.context.engine.data.get(
TelegramEngine.EngineData.Item.Messages.Message(id: editMessage.messageId)
)
|> map { message -> [EngineMessage] in
if let message {
return [message]
} else {
return []
}
}
} else {
editMessages = .single([])
}
let _ = (combineLatest( let _ = (combineLatest(
selfController.context.account.viewTracker.peerView(peerId) |> take(1), selfController.context.account.viewTracker.peerView(peerId) |> take(1),
effectItems, effectItems,
availableMessageEffects, availableMessageEffects,
hasPremium hasPremium,
editMessages
) )
|> deliverOnMainQueue).startStandalone(next: { [weak selfController] peerView, effectItems, availableMessageEffects, hasPremium in |> deliverOnMainQueue).startStandalone(next: { [weak selfController] peerView, effectItems, availableMessageEffects, hasPremium, editMessages in
guard let selfController, let peer = peerViewMainPeer(peerView) else { guard let selfController, let peer = peerViewMainPeer(peerView) else {
return return
} }
var sendWhenOnlineAvailable = false
if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence, case let .present(until) = presence.status { if let _ = selfController.presentationInterfaceState.interfaceState.editMessage {
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) if editMessages.isEmpty {
if currentTime > until { return
sendWhenOnlineAvailable = true
} }
} let controller = makeChatSendMessageActionSheetController(
if peer.id.namespace == Namespaces.Peer.CloudUser && peer.id.id._internalGetInt64Value() == 777000 { context: selfController.context,
sendWhenOnlineAvailable = false updatedPresentationData: selfController.updatedPresentationData,
} peerId: selfController.presentationInterfaceState.chatLocation.peerId,
params: .editMessage(SendMessageActionSheetControllerParams.EditMessage(
if sendWhenOnlineAvailable { messages: editMessages,
let _ = ApplicationSpecificNotice.incrementSendWhenOnlineTip(accountManager: selfController.context.sharedContext.accountManager, count: 4).startStandalone() mediaPreview: nil
} )),
hasEntityKeyboard: hasEntityKeyboard,
var mediaPreview: ChatSendMessageContextScreenMediaPreview? gesture: gesture,
if let videoRecorderValue = selfController.videoRecorderValue { sourceSendButton: node,
mediaPreview = videoRecorderValue.makeSendMessageContextPreview() textInputView: textInputView,
} emojiViewProvider: selfController.chatDisplayNode.textInputPanelNode?.emojiViewProvider,
if let mediaDraftState = selfController.presentationInterfaceState.interfaceState.mediaDraftState { wallpaperBackgroundNode: selfController.chatDisplayNode.backgroundNode,
if case let .audio(audio) = mediaDraftState { completion: { [weak selfController] in
mediaPreview = ChatSendAudioMessageContextPreview( guard let selfController else {
context: selfController.context, return
presentationData: selfController.presentationData,
wallpaperBackgroundNode: selfController.chatDisplayNode.backgroundNode,
waveform: audio.waveform
)
}
}
let controller = makeChatSendMessageActionSheetController(
context: selfController.context,
updatedPresentationData: selfController.updatedPresentationData,
peerId: selfController.presentationInterfaceState.chatLocation.peerId,
forwardMessageIds: selfController.presentationInterfaceState.interfaceState.forwardMessageIds,
hasEntityKeyboard: hasEntityKeyboard,
gesture: gesture,
sourceSendButton: node,
textInputView: textInputView,
mediaPreview: mediaPreview,
emojiViewProvider: selfController.chatDisplayNode.textInputPanelNode?.emojiViewProvider,
wallpaperBackgroundNode: selfController.chatDisplayNode.backgroundNode,
canSendWhenOnline: sendWhenOnlineAvailable,
completion: { [weak selfController] in
guard let selfController else {
return
}
selfController.supportedOrientations = previousSupportedOrientations
},
sendMessage: { [weak selfController] mode, parameters in
guard let selfController else {
return
}
switch mode {
case .generic:
selfController.controllerInteraction?.sendCurrentMessage(false, parameters?.effect.flatMap(ChatSendMessageEffect.init))
case .silently:
selfController.controllerInteraction?.sendCurrentMessage(true, parameters?.effect.flatMap(ChatSendMessageEffect.init))
case .whenOnline:
selfController.chatDisplayNode.sendCurrentMessage(scheduleTime: scheduleWhenOnlineTimestamp, messageEffect: parameters?.effect.flatMap(ChatSendMessageEffect.init)) { [weak selfController] in
guard let selfController else {
return
}
selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, saveInterfaceState: selfController.presentationInterfaceState.subject != .scheduledMessages, {
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) }
})
selfController.openScheduledMessages()
} }
} selfController.supportedOrientations = previousSupportedOrientations
}, },
schedule: { [weak selfController] effect in sendMessage: { [weak selfController] mode, parameters in
guard let selfController else { guard let selfController else {
return return
} }
selfController.controllerInteraction?.scheduleCurrentMessage() selfController.interfaceInteraction?.editMessage()
}, openPremiumPaywall: { [weak selfController] c in },
guard let selfController else { schedule: { _ in
return }, openPremiumPaywall: { [weak selfController] c in
} guard let selfController else {
selfController.push(c) return
}, }
reactionItems: (!textInputView.text.isEmpty || mediaPreview != nil) ? effectItems : nil, selfController.push(c)
availableMessageEffects: availableMessageEffects, },
isPremium: hasPremium reactionItems: nil,
) availableMessageEffects: nil,
selfController.sendMessageActionsController = controller isPremium: hasPremium
if layout.isNonExclusive { )
selfController.present(controller, in: .window(.root)) selfController.sendMessageActionsController = controller
if layout.isNonExclusive {
selfController.present(controller, in: .window(.root))
} else {
selfController.presentInGlobalOverlay(controller, with: nil)
}
} else { } else {
selfController.presentInGlobalOverlay(controller, with: nil) var sendWhenOnlineAvailable = false
if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence, case let .present(until) = presence.status {
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
if currentTime > until {
sendWhenOnlineAvailable = true
}
}
if peer.id.namespace == Namespaces.Peer.CloudUser && peer.id.id._internalGetInt64Value() == 777000 {
sendWhenOnlineAvailable = false
}
if sendWhenOnlineAvailable {
let _ = ApplicationSpecificNotice.incrementSendWhenOnlineTip(accountManager: selfController.context.sharedContext.accountManager, count: 4).startStandalone()
}
var mediaPreview: ChatSendMessageContextScreenMediaPreview?
if let videoRecorderValue = selfController.videoRecorderValue {
mediaPreview = videoRecorderValue.makeSendMessageContextPreview()
}
if let mediaDraftState = selfController.presentationInterfaceState.interfaceState.mediaDraftState {
if case let .audio(audio) = mediaDraftState {
mediaPreview = ChatSendAudioMessageContextPreview(
context: selfController.context,
presentationData: selfController.presentationData,
wallpaperBackgroundNode: selfController.chatDisplayNode.backgroundNode,
waveform: audio.waveform
)
}
}
let controller = makeChatSendMessageActionSheetController(
context: selfController.context,
updatedPresentationData: selfController.updatedPresentationData,
peerId: selfController.presentationInterfaceState.chatLocation.peerId,
params: .sendMessage(SendMessageActionSheetControllerParams.SendMessage(
isScheduledMessages: false,
mediaPreview: mediaPreview,
mediaCaptionIsAbove: nil,
attachment: false,
canSendWhenOnline: sendWhenOnlineAvailable,
forwardMessageIds: selfController.presentationInterfaceState.interfaceState.forwardMessageIds ?? []
)),
hasEntityKeyboard: hasEntityKeyboard,
gesture: gesture,
sourceSendButton: node,
textInputView: textInputView,
emojiViewProvider: selfController.chatDisplayNode.textInputPanelNode?.emojiViewProvider,
wallpaperBackgroundNode: selfController.chatDisplayNode.backgroundNode,
completion: { [weak selfController] in
guard let selfController else {
return
}
selfController.supportedOrientations = previousSupportedOrientations
},
sendMessage: { [weak selfController] mode, parameters in
guard let selfController else {
return
}
switch mode {
case .generic:
selfController.controllerInteraction?.sendCurrentMessage(false, parameters?.effect.flatMap(ChatSendMessageEffect.init))
case .silently:
selfController.controllerInteraction?.sendCurrentMessage(true, parameters?.effect.flatMap(ChatSendMessageEffect.init))
case .whenOnline:
selfController.chatDisplayNode.sendCurrentMessage(scheduleTime: scheduleWhenOnlineTimestamp, messageEffect: parameters?.effect.flatMap(ChatSendMessageEffect.init)) { [weak selfController] in
guard let selfController else {
return
}
selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, saveInterfaceState: selfController.presentationInterfaceState.subject != .scheduledMessages, {
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) }
})
selfController.openScheduledMessages()
}
}
},
schedule: { [weak selfController] effect in
guard let selfController else {
return
}
selfController.controllerInteraction?.scheduleCurrentMessage()
}, openPremiumPaywall: { [weak selfController] c in
guard let selfController else {
return
}
selfController.push(c)
},
reactionItems: (!textInputView.text.isEmpty || mediaPreview != nil) ? effectItems : nil,
availableMessageEffects: availableMessageEffects,
isPremium: hasPremium
)
selfController.sendMessageActionsController = controller
if layout.isNonExclusive {
selfController.present(controller, in: .window(.root))
} else {
selfController.presentInGlobalOverlay(controller, with: nil)
}
} }
}) })
} }

View File

@ -67,7 +67,7 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode {
self.sendButton.highligthedChanged = { [weak self] highlighted in self.sendButton.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self { if let strongSelf = self {
if strongSelf.sendButtonHasApplyIcon || !strongSelf.sendButtonLongPressEnabled { if !strongSelf.sendButtonLongPressEnabled {
if highlighted { if highlighted {
strongSelf.sendContainerNode.layer.removeAnimation(forKey: "opacity") strongSelf.sendContainerNode.layer.removeAnimation(forKey: "opacity")
strongSelf.sendContainerNode.alpha = 0.4 strongSelf.sendContainerNode.alpha = 0.4
@ -109,9 +109,7 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
if !strongSelf.sendButtonHasApplyIcon { strongSelf.sendButtonLongPressed?(strongSelf, recognizer)
strongSelf.sendButtonLongPressed?(strongSelf, recognizer)
}
} }
self.micButtonPointerInteraction = PointerInteraction(view: self.micButton, style: .circle(36.0)) self.micButtonPointerInteraction = PointerInteraction(view: self.micButton, style: .circle(36.0))