UI Improvements

This commit is contained in:
Ali 2020-03-27 17:49:45 +04:00
parent 1979be1f85
commit b8c46f069f
27 changed files with 3360 additions and 3147 deletions

View File

@ -5471,3 +5471,5 @@ Any member of this group will be able to see messages in the channel.";
"MuteFor.Forever" = "Mute Forever"; "MuteFor.Forever" = "Mute Forever";
"Conversation.Dice" = "Send a 🎲 emoji to any chat to get a random number from Telegram."; "Conversation.Dice" = "Send a 🎲 emoji to any chat to get a random number from Telegram.";
"Conversation.ContextMenuDiscuss" = "Discuss";

View File

@ -157,7 +157,7 @@ private final class ItemNode: ASDisplayNode {
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.containerNode.activated = { [weak self] gesture in self.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }

View File

@ -135,7 +135,7 @@ private final class ItemNode: ASDisplayNode {
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.containerNode.activated = { [weak self] gesture in self.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }

View File

@ -529,7 +529,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
}) })
self.contextContainer.activated = { [weak self] gesture in self.contextContainer.activated = { [weak self] gesture, _ in
guard let strongSelf = self, let item = strongSelf.item else { guard let strongSelf = self, let item = strongSelf.item else {
return return
} }

View File

@ -366,7 +366,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
} }
}) })
self.containerNode.activated = { [weak self] gesture in self.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self, let item = strongSelf.item, let contextAction = item.contextAction else { guard let strongSelf = self, let item = strongSelf.item, let contextAction = item.contextAction else {
gesture.cancel() gesture.cancel()
return return

View File

@ -9,10 +9,11 @@ public final class ContextControllerSourceNode: ASDisplayNode {
self.contextGesture?.isEnabled = self.isGestureEnabled self.contextGesture?.isEnabled = self.isGestureEnabled
} }
} }
public var activated: ((ContextGesture) -> Void)? public var activated: ((ContextGesture, CGPoint) -> Void)?
public var shouldBegin: ((CGPoint) -> Bool)? public var shouldBegin: ((CGPoint) -> Bool)?
public var customActivationProgress: ((CGFloat, ContextGestureTransition) -> Void)? public var customActivationProgress: ((CGFloat, ContextGestureTransition) -> Void)?
public var targetNodeForActivationProgress: ASDisplayNode? public var targetNodeForActivationProgress: ASDisplayNode?
public var targetNodeForActivationProgressContentRect: CGRect?
public func cancelGesture() { public func cancelGesture() {
self.contextGesture?.cancel() self.contextGesture?.cancel()
@ -41,25 +42,53 @@ public final class ContextControllerSourceNode: ASDisplayNode {
if let customActivationProgress = strongSelf.customActivationProgress { if let customActivationProgress = strongSelf.customActivationProgress {
customActivationProgress(progress, update) customActivationProgress(progress, update)
} else { } else {
let targetNode = strongSelf.targetNodeForActivationProgress ?? strongSelf let targetNode: ASDisplayNode
let targetContentRect: CGRect
if let targetNodeForActivationProgress = strongSelf.targetNodeForActivationProgress {
targetNode = targetNodeForActivationProgress
if let targetNodeForActivationProgressContentRect = strongSelf.targetNodeForActivationProgressContentRect {
targetContentRect = targetNodeForActivationProgressContentRect
} else {
targetContentRect = CGRect(origin: CGPoint(), size: targetNode.bounds.size)
}
} else {
targetNode = strongSelf
targetContentRect = CGRect(origin: CGPoint(), size: targetNode.bounds.size)
}
let minScale: CGFloat = (strongSelf.bounds.width - 10.0) / strongSelf.bounds.width let minScale: CGFloat = (targetContentRect.width - 10.0) / targetContentRect.width
let currentScale = 1.0 * (1.0 - progress) + minScale * progress let currentScale = 1.0 * (1.0 - progress) + minScale * progress
let originalCenterOffsetX: CGFloat = targetNode.bounds.width / 2.0 - targetContentRect.midX
let scaledCaneterOffsetX: CGFloat = originalCenterOffsetX * currentScale
let originalCenterOffsetY: CGFloat = targetNode.bounds.height / 2.0 - targetContentRect.midY
let scaledCaneterOffsetY: CGFloat = originalCenterOffsetY * currentScale
let scaleMidX: CGFloat = scaledCaneterOffsetX - originalCenterOffsetX
let scaleMidY: CGFloat = scaledCaneterOffsetY - originalCenterOffsetY
switch update { switch update {
case .update: case .update:
targetNode.layer.sublayerTransform = CATransform3DMakeScale(currentScale, currentScale, 1.0) let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0)
targetNode.layer.sublayerTransform = sublayerTransform
case .begin: case .begin:
targetNode.layer.sublayerTransform = CATransform3DMakeScale(currentScale, currentScale, 1.0) let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0)
case let .ended(previousProgress): targetNode.layer.sublayerTransform = sublayerTransform
let previousScale = 1.0 * (1.0 - previousProgress) + minScale * previousProgress case .ended:
targetNode.layer.sublayerTransform = CATransform3DMakeScale(currentScale, currentScale, 1.0) let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0)
targetNode.layer.animateSpring(from: previousScale as NSNumber, to: currentScale as NSNumber, keyPath: "sublayerTransform.scale", duration: 0.5, delay: 0.0, initialVelocity: 0.0, damping: 90.0) let previousTransform = targetNode.layer.sublayerTransform
targetNode.layer.sublayerTransform = sublayerTransform
targetNode.layer.animate(from: NSValue(caTransform3D: previousTransform), to: NSValue(caTransform3D: sublayerTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2)
//targetNode.layer.animateSpring(from: previousScale as NSNumber, to: currentScale as NSNumber, keyPath: "sublayerTransform.scale", duration: 0.5, delay: 0.0, initialVelocity: 0.0, damping: 90.0)
} }
} }
} }
contextGesture.activated = { [weak self] gesture in contextGesture.activated = { [weak self] gesture, location in
if let activated = self?.activated { if let activated = self?.activated {
activated(gesture) activated(gesture, location)
} else { } else {
gesture.cancel() gesture.cancel()
} }

View File

@ -57,7 +57,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
public var shouldBegin: ((CGPoint) -> Bool)? public var shouldBegin: ((CGPoint) -> Bool)?
public var activationProgress: ((CGFloat, ContextGestureTransition) -> Void)? public var activationProgress: ((CGFloat, ContextGestureTransition) -> Void)?
public var activated: ((ContextGesture) -> Void)? public var activated: ((ContextGesture, CGPoint) -> Void)?
public var externalUpdated: ((UIView?, CGPoint) -> Void)? public var externalUpdated: ((UIView?, CGPoint) -> Void)?
public var externalEnded: (((UIView?, CGPoint)?) -> Void)? public var externalEnded: (((UIView?, CGPoint)?) -> Void)?
@ -95,9 +95,10 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
guard let touch = touches.first else { guard let touch = touches.first else {
return return
} }
let location = touch.location(in: self.view)
if let shouldBegin = self.shouldBegin { if let shouldBegin = self.shouldBegin {
if !shouldBegin(touch.location(in: self.view)) { if !shouldBegin(location) {
self.state = .failed self.state = .failed
return return
} }
@ -132,7 +133,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
case .possible: case .possible:
strongSelf.delayTimer?.invalidate() strongSelf.delayTimer?.invalidate()
strongSelf.animator?.invalidate() strongSelf.animator?.invalidate()
strongSelf.activated?(strongSelf) strongSelf.activated?(strongSelf, location)
if let view = strongSelf.view?.superview { if let view = strongSelf.view?.superview {
if let window = view.window { if let window = view.window {
cancelOtherGestures(gesture: strongSelf, view: window) cancelOtherGestures(gesture: strongSelf, view: window)
@ -167,7 +168,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
case .possible: case .possible:
self.delayTimer?.invalidate() self.delayTimer?.invalidate()
self.animator?.invalidate() self.animator?.invalidate()
self.activated?(self) self.activated?(self, touch.location(in: self.view))
if let view = self.view?.superview { if let view = self.view?.superview {
if let window = view.window { if let window = view.window {
cancelOtherGestures(gesture: self, view: window) cancelOtherGestures(gesture: self, view: window)

View File

@ -248,7 +248,7 @@ private final class TabBarNodeContainer {
updateSelectedImage(value) updateSelectedImage(value)
} }
imageNode.containerNode.activated = { [weak self] gesture in imageNode.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }

View File

@ -529,7 +529,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
} }
}) })
self.containerNode.activated = { [weak self] gesture in self.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self, let item = strongSelf.layoutParams?.0, let contextAction = item.contextAction else { guard let strongSelf = self, let item = strongSelf.layoutParams?.0, let contextAction = item.contextAction else {
gesture.cancel() gesture.cancel()
return return

View File

@ -127,7 +127,7 @@ public final class SelectablePeerNode: ASDisplayNode {
self.contextContainer.addSubnode(self.textNode) self.contextContainer.addSubnode(self.textNode)
self.contextContainer.addSubnode(self.onlineNode) self.contextContainer.addSubnode(self.onlineNode)
self.contextContainer.activated = { [weak self] gesture in self.contextContainer.activated = { [weak self] gesture, _ in
guard let strongSelf = self, let contextAction = strongSelf.contextAction else { guard let strongSelf = self, let contextAction = strongSelf.contextAction else {
gesture.cancel() gesture.cancel()
return return

View File

@ -313,7 +313,7 @@ private final class ThemeSettingsAccentColorIconItemNode : ListViewItemNode {
self.containerNode.addSubnode(self.dotsNode) self.containerNode.addSubnode(self.dotsNode)
self.containerNode.addSubnode(self.centerNode) self.containerNode.addSubnode(self.centerNode)
self.containerNode.activated = { [weak self] gesture in self.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self, let item = strongSelf.item else { guard let strongSelf = self, let item = strongSelf.item else {
gesture.cancel() gesture.cancel()
return return

View File

@ -224,7 +224,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
self.containerNode.addSubnode(self.overlayNode) self.containerNode.addSubnode(self.overlayNode)
self.containerNode.addSubnode(self.titleNode) self.containerNode.addSubnode(self.titleNode)
self.containerNode.activated = { [weak self] gesture in self.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self, let item = strongSelf.item else { guard let strongSelf = self, let item = strongSelf.item else {
gesture.cancel() gesture.cancel()
return return

View File

@ -63,7 +63,7 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
self.containerNode.addSubnode(self.avatarNode) self.containerNode.addSubnode(self.avatarNode)
self.addSubnode(self.containerNode) self.addSubnode(self.containerNode)
self.containerNode.activated = { [weak self] gesture in self.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }

View File

@ -545,13 +545,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self?.openPeer(peerId: id, navigation: navigation, fromMessage: fromMessage) self?.openPeer(peerId: id, navigation: navigation, fromMessage: fromMessage)
}, openPeerMention: { [weak self] name in }, openPeerMention: { [weak self] name in
self?.openPeerMention(name) self?.openPeerMention(name)
}, openMessageContextMenu: { [weak self] message, selectAll, node, frame, recognizer in }, openMessageContextMenu: { [weak self] message, selectAll, node, frame, anyRecognizer in
guard let strongSelf = self, strongSelf.isNodeLoaded else { guard let strongSelf = self, strongSelf.isNodeLoaded else {
return return
} }
if strongSelf.presentationInterfaceState.interfaceState.selectionState != nil { if strongSelf.presentationInterfaceState.interfaceState.selectionState != nil {
return return
} }
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer
let gesture: ContextGesture? = anyRecognizer as? ContextGesture
if let messages = strongSelf.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(message.id) { if let messages = strongSelf.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(message.id) {
(strongSelf.view.window as? WindowHost)?.cancelInteractiveKeyboardGestures() (strongSelf.view.window as? WindowHost)?.cancelInteractiveKeyboardGestures()
strongSelf.chatDisplayNode.cancelInteractiveKeyboardGestures() strongSelf.chatDisplayNode.cancelInteractiveKeyboardGestures()
@ -600,7 +602,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let _ = ApplicationSpecificNotice.incrementChatTextSelectionTips(accountManager: strongSelf.context.sharedContext.accountManager).start() let _ = ApplicationSpecificNotice.incrementChatTextSelectionTips(accountManager: strongSelf.context.sharedContext.accountManager).start()
} }
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, message: message)), items: .single(actions), reactionItems: reactionItems, recognizer: recognizer, displayTextSelectionTip: displayTextSelectionTip) let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, message: message)), items: .single(actions), reactionItems: reactionItems, recognizer: recognizer, gesture: gesture, displayTextSelectionTip: displayTextSelectionTip)
strongSelf.currentContextController = controller strongSelf.currentContextController = controller
controller.reactionSelected = { [weak controller] value in controller.reactionSelected = { [weak controller] value in
guard let strongSelf = self, let message = updatedMessages.first else { guard let strongSelf = self, let message = updatedMessages.first else {

View File

@ -53,7 +53,7 @@ public final class ChatControllerInteraction {
let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool
let openPeer: (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void let openPeer: (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void
let openPeerMention: (String) -> Void let openPeerMention: (String) -> Void
let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, TapLongTapOrDoubleTapGestureRecognizer?) -> Void let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void
let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void
let navigateToMessage: (MessageId, MessageId) -> Void let navigateToMessage: (MessageId, MessageId) -> Void
let tapMessage: ((Message) -> Void)? let tapMessage: ((Message) -> Void)?
@ -121,7 +121,7 @@ public final class ChatControllerInteraction {
var searchTextHighightState: (String, [MessageIndex])? var searchTextHighightState: (String, [MessageIndex])?
var seenOneTimeAnimatedMedia = Set<MessageId>() var seenOneTimeAnimatedMedia = Set<MessageId>()
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, TapLongTapOrDoubleTapGestureRecognizer?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, tapMessage: ((Message) -> Void)?, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openTheme: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOptions: @escaping (MessageId, [Data]) -> Void, requestOpenMessagePollResults: @escaping (MessageId, MediaId) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, String, TextSelectionAction) -> Void, updateMessageReaction: @escaping (MessageId, String?) -> Void, openMessageReactions: @escaping (MessageId) -> Void, displaySwipeToReplyHint: @escaping () -> Void, dismissReplyMarkupMessage: @escaping (Message) -> Void, openMessagePollResults: @escaping (MessageId, Data) -> Void, openPollCreation: @escaping (Bool?) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) { init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, tapMessage: ((Message) -> Void)?, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openTheme: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOptions: @escaping (MessageId, [Data]) -> Void, requestOpenMessagePollResults: @escaping (MessageId, MediaId) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, String, TextSelectionAction) -> Void, updateMessageReaction: @escaping (MessageId, String?) -> Void, openMessageReactions: @escaping (MessageId) -> Void, displaySwipeToReplyHint: @escaping () -> Void, dismissReplyMarkupMessage: @escaping (Message) -> Void, openMessagePollResults: @escaping (MessageId, Data) -> Void, openPollCreation: @escaping (Bool?) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) {
self.openMessage = openMessage self.openMessage = openMessage
self.openPeer = openPeer self.openPeer = openPeer
self.openPeerMention = openPeerMention self.openPeerMention = openPeerMention

View File

@ -257,6 +257,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
var loadCopyMediaResource: MediaResource? var loadCopyMediaResource: MediaResource?
var isAction = false var isAction = false
var isDice = false var isDice = false
var canDiscuss = false
if messages.count == 1 { if messages.count == 1 {
for media in messages[0].media { for media in messages[0].media {
if let file = media as? TelegramMediaFile { if let file = media as? TelegramMediaFile {
@ -292,6 +293,17 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel { if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel {
if !isAction { if !isAction {
canPin = channel.hasPermission(.pinMessages) canPin = channel.hasPermission(.pinMessages)
if messages[0].id.namespace == Namespaces.Message.Cloud {
switch channel.info {
case let .broadcast(info):
if info.flags.contains(.hasDiscussionGroup) {
canDiscuss = true
}
case .group:
break
}
}
} }
} else if let group = messages[0].peers[messages[0].id.peerId] as? TelegramGroup { } else if let group = messages[0].peers[messages[0].id.peerId] as? TelegramGroup {
if !isAction { if !isAction {
@ -693,6 +705,94 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
}))) })))
} }
if canDiscuss {
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuDiscuss, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Message"), color: theme.actionSheet.primaryTextColor)
}, action: { c, _ in
let timestamp = messages[0].timestamp
let channelMessageId = messages[0].id
enum DiscussMessageResult {
case message(MessageId)
case peer(PeerId)
}
let signal = context.account.postbox.transaction { transaction -> PeerId? in
if let cachedData = transaction.getPeerCachedData(peerId: messages[0].id.peerId) as? CachedChannelData {
return cachedData.linkedDiscussionPeerId
} else {
return nil
}
}
|> mapToSignal { peerId -> Signal<DiscussMessageResult?, NoError> in
guard let peerId = peerId else {
return .single(nil)
}
let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: 0, id: 0), timestamp: timestamp)), count: 30), id: 0), account: context.account, chatLocation: .peer(peerId), fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
return historyView
|> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in
switch historyView {
case .Loading:
return .single((nil, true))
case let .HistoryView(view, _, _, _, _, _, _):
for entry in view.entries {
for attribute in entry.message.attributes {
if let attribute = attribute as? SourceReferenceMessageAttribute {
if attribute.messageId == channelMessageId {
return .single((entry.message.index, false))
}
}
}
}
return .single((nil, false))
}
}
|> take(until: { index in
return SignalTakeAction(passthrough: true, complete: !index.1)
})
|> last
|> map { result -> DiscussMessageResult? in
if let index = result?.0 {
return .message(index.id)
} else {
return .peer(peerId)
}
}
}
let foundIndex = Promise<DiscussMessageResult?>()
foundIndex.set(signal)
c.dismiss(completion: { [weak interfaceInteraction] in
var cancelImpl: (() -> Void)?
let statusController = OverlayStatusController(theme: chatPresentationInterfaceState.theme, type: .loading(cancelled: {
cancelImpl?()
}))
controllerInteraction.presentController(statusController, nil)
let disposable = (foundIndex.get()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak statusController] result in
statusController?.dismiss()
if let result = result {
switch result {
case let .message(id):
interfaceInteraction?.navigateToMessage(id)
case let .peer(peerId):
interfaceInteraction?.navigateToChat(peerId)
}
}
})
cancelImpl = { [weak statusController] in
disposable.dispose()
statusController?.dismiss()
}
})
})))
}
if data.messageActions.options.contains(.report) { if data.messageActions.options.contains(.report) {
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuReport, icon: { theme in actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuReport, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Report"), color: theme.actionSheet.primaryTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Report"), color: theme.actionSheet.primaryTextColor)

View File

@ -23,6 +23,12 @@ import GridMessageSelectionNode
import AppBundle import AppBundle
import Markdown import Markdown
private enum InternalBubbleTapAction {
case action(() -> Void)
case optionalAction(() -> Void)
case openContextMenu(tapMessage: Message, selectAll: Bool, subFrame: CGRect)
}
private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(Message, AnyClass, ChatMessageEntryAttributes)] { private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(Message, AnyClass, ChatMessageEntryAttributes)] {
var result: [(Message, AnyClass, ChatMessageEntryAttributes)] = [] var result: [(Message, AnyClass, ChatMessageEntryAttributes)] = []
var skipText = false var skipText = false
@ -145,6 +151,7 @@ private enum ContentNodeOperation {
class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode { class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode {
private let contextSourceNode: ContextExtractedContentContainingNode private let contextSourceNode: ContextExtractedContentContainingNode
private let containerNode: ContextControllerSourceNode
private let backgroundWallpaperNode: ChatMessageBubbleBackdrop private let backgroundWallpaperNode: ChatMessageBubbleBackdrop
private let backgroundNode: ChatMessageBackground private let backgroundNode: ChatMessageBackground
private let shadowNode: ChatMessageShadowNode private let shadowNode: ChatMessageShadowNode
@ -201,6 +208,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
required init() { required init() {
self.contextSourceNode = ContextExtractedContentContainingNode() self.contextSourceNode = ContextExtractedContentContainingNode()
self.containerNode = ContextControllerSourceNode()
self.backgroundWallpaperNode = ChatMessageBubbleBackdrop() self.backgroundWallpaperNode = ChatMessageBubbleBackdrop()
self.backgroundNode = ChatMessageBackground() self.backgroundNode = ChatMessageBackground()
@ -209,7 +217,45 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
super.init(layerBacked: false) super.init(layerBacked: false)
self.addSubnode(self.contextSourceNode) self.containerNode.shouldBegin = { [weak self] location in
guard let strongSelf = self else {
return false
}
if let action = strongSelf.gestureRecognized(gesture: .tap, location: location, recognizer: nil) {
if case .action = action {
return false
}
}
if let action = strongSelf.gestureRecognized(gesture: .longTap, location: location, recognizer: nil) {
switch action {
case .action, .optionalAction:
return false
case .openContextMenu:
return true
}
}
return true
}
self.containerNode.activated = { [weak self] gesture, location in
guard let strongSelf = self, let item = strongSelf.item else {
return
}
if let action = strongSelf.gestureRecognized(gesture: .longTap, location: location, recognizer: nil) {
switch action {
case .action, .optionalAction:
break
case let .openContextMenu(tapMessage, selectAll, subFrame):
item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, strongSelf, subFrame, gesture)
}
}
}
self.containerNode.addSubnode(self.contextSourceNode)
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
self.addSubnode(self.containerNode)
self.contextSourceNode.contentNode.addSubnode(self.backgroundWallpaperNode) self.contextSourceNode.contentNode.addSubnode(self.backgroundWallpaperNode)
self.contextSourceNode.contentNode.addSubnode(self.backgroundNode) self.contextSourceNode.contentNode.addSubnode(self.backgroundNode)
self.addSubnode(self.messageAccessibilityArea) self.addSubnode(self.messageAccessibilityArea)
@ -385,8 +431,18 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
return return
} }
strongSelf.reactionRecognizer?.cancel() strongSelf.reactionRecognizer?.cancel()
if strongSelf.gestureRecognized(gesture: .longTap, location: point, recognizer: recognizer) { if let action = strongSelf.gestureRecognized(gesture: .longTap, location: point, recognizer: recognizer) {
recognizer.cancel() switch action {
case let .action(f):
f()
recognizer.cancel()
case let .optionalAction(f):
f()
recognizer.cancel()
case .openContextMenu:
//recognizer.cancel()
break
}
} }
} }
recognizer.highlight = { [weak self] point in recognizer.highlight = { [weak self] point in
@ -1688,7 +1744,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
guard let strongSelf = selfReference.value else { guard let strongSelf = selfReference.value else {
return return
} }
let previousContextFrame = strongSelf.contextSourceNode.frame let previousContextFrame = strongSelf.containerNode.frame
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize) strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize) strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
@ -2052,6 +2109,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
let previousContextContentFrame = strongSelf.contextSourceNode.contentRect let previousContextContentFrame = strongSelf.contextSourceNode.contentRect
strongSelf.contextSourceNode.contentRect = backgroundFrame.offsetBy(dx: incomingOffset, dy: 0.0) strongSelf.contextSourceNode.contentRect = backgroundFrame.offsetBy(dx: incomingOffset, dy: 0.0)
strongSelf.containerNode.targetNodeForActivationProgressContentRect = strongSelf.contextSourceNode.contentRect
if previousContextFrame.size != strongSelf.contextSourceNode.bounds.size || previousContextContentFrame != strongSelf.contextSourceNode.contentRect { if previousContextFrame.size != strongSelf.contextSourceNode.bounds.size || previousContextContentFrame != strongSelf.contextSourceNode.contentRect {
strongSelf.contextSourceNode.layoutUpdated?(strongSelf.contextSourceNode.bounds.size) strongSelf.contextSourceNode.layoutUpdated?(strongSelf.contextSourceNode.bounds.size)
@ -2212,6 +2270,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
break break
} }
self.contextSourceNode.contentRect = backgroundFrame.offsetBy(dx: incomingOffset, dy: 0.0) self.contextSourceNode.contentRect = backgroundFrame.offsetBy(dx: incomingOffset, dy: 0.0)
self.containerNode.targetNodeForActivationProgressContentRect = self.contextSourceNode.contentRect
if !self.contextSourceNode.isExtractedToContextPreview { if !self.contextSourceNode.isExtractedToContextPreview {
if let (rect, size) = self.absoluteRect { if let (rect, size) = self.absoluteRect {
self.updateAbsoluteRect(rect, within: size) self.updateAbsoluteRect(rect, within: size)
@ -2246,14 +2305,25 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
switch recognizer.state { switch recognizer.state {
case .ended: case .ended:
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
let _ = self.gestureRecognized(gesture: gesture, location: location, recognizer: nil) if let action = self.gestureRecognized(gesture: gesture, location: location, recognizer: nil) {
switch action {
case let .action(f):
f()
case let .optionalAction(f):
f()
case let .openContextMenu(tapMessage, selectAll, subFrame):
self.item?.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, self, subFrame, nil)
}
} else if case .tap = gesture {
self.item?.controllerInteraction.clickThroughMessage()
}
} }
default: default:
break break
} }
} }
private func gestureRecognized(gesture: TapLongTapOrDoubleTapGesture, location: CGPoint, recognizer: TapLongTapOrDoubleTapGestureRecognizer?) -> Bool { private func gestureRecognized(gesture: TapLongTapOrDoubleTapGesture, location: CGPoint, recognizer: TapLongTapOrDoubleTapGestureRecognizer?) -> InternalBubbleTapAction? {
var mediaMessage: Message? var mediaMessage: Message?
var forceOpen = false var forceOpen = false
if let item = self.item { if let item = self.item {
@ -2290,38 +2360,39 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
switch gesture { switch gesture {
case .tap: case .tap:
if let avatarNode = self.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(location) { if let avatarNode = self.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(location) {
if let item = self.item, let author = item.content.firstMessage.author { return .action({
var openPeerId = item.effectiveAuthorId ?? author.id if let item = self.item, let author = item.content.firstMessage.author {
var navigate: ChatControllerInteractionNavigateToPeer var openPeerId = item.effectiveAuthorId ?? author.id
var navigate: ChatControllerInteractionNavigateToPeer
if item.content.firstMessage.id.peerId == item.context.account.peerId {
navigate = .chat(textInputState: nil, subject: nil) if item.content.firstMessage.id.peerId == item.context.account.peerId {
} else { navigate = .chat(textInputState: nil, subject: nil)
navigate = .info } else {
} navigate = .info
for attribute in item.content.firstMessage.attributes {
if let attribute = attribute as? SourceReferenceMessageAttribute {
openPeerId = attribute.messageId.peerId
navigate = .chat(textInputState: nil, subject: .message(attribute.messageId))
} }
}
for attribute in item.content.firstMessage.attributes {
if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty { if let attribute = attribute as? SourceReferenceMessageAttribute {
item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame) openPeerId = attribute.messageId.peerId
} else { navigate = .chat(textInputState: nil, subject: .message(attribute.messageId))
if item.message.id.peerId == item.context.account.peerId, let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil {
if case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) {
} else if case .member = channel.participationStatus {
} else {
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame)
return true
} }
} }
item.controllerInteraction.openPeer(openPeerId, navigate, item.message)
if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty {
item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame)
} else {
if item.message.id.peerId == item.context.account.peerId, let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil {
if case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) {
} else if case .member = channel.participationStatus {
} else {
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame)
return
}
}
item.controllerInteraction.openPeer(openPeerId, navigate, item.message)
}
} }
} })
return true
} }
if let nameNode = self.nameNode, nameNode.frame.contains(location) { if let nameNode = self.nameNode, nameNode.frame.contains(location) {
@ -2335,15 +2406,16 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
botAddressName = attribute.title botAddressName = attribute.title
} }
if let botAddressName = botAddressName { return .optionalAction({
item.controllerInteraction.updateInputState { textInputState in if let botAddressName = botAddressName {
return ChatTextInputState(inputText: NSAttributedString(string: "@" + botAddressName + " ")) item.controllerInteraction.updateInputState { textInputState in
return ChatTextInputState(inputText: NSAttributedString(string: "@" + botAddressName + " "))
}
item.controllerInteraction.updateInputMode { _ in
return .text
}
} }
item.controllerInteraction.updateInputMode { _ in })
return .text
}
}
return true
} }
} }
} }
@ -2351,119 +2423,127 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
if let item = self.item { if let item = self.item {
for attribute in item.message.attributes { for attribute in item.message.attributes {
if let attribute = attribute as? ReplyMessageAttribute { if let attribute = attribute as? ReplyMessageAttribute {
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId) return .optionalAction({
return true item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId)
})
} }
} }
} }
} }
if let forwardInfoNode = self.forwardInfoNode, forwardInfoNode.frame.contains(location) { if let forwardInfoNode = self.forwardInfoNode, forwardInfoNode.frame.contains(location) {
if let item = self.item, let forwardInfo = item.message.forwardInfo { if let item = self.item, let forwardInfo = item.message.forwardInfo {
if let sourceMessageId = forwardInfo.sourceMessageId { return .optionalAction({
if let channel = forwardInfo.author as? TelegramChannel, channel.username == nil { if let sourceMessageId = forwardInfo.sourceMessageId {
if case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) { if let channel = forwardInfo.author as? TelegramChannel, channel.username == nil {
} else if case .member = channel.participationStatus { if case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) {
} else { } else if case .member = channel.participationStatus {
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, forwardInfoNode, nil) } else {
return true item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, forwardInfoNode, nil)
return
}
} }
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId)
} else if let id = forwardInfo.source?.id ?? forwardInfo.author?.id {
item.controllerInteraction.openPeer(id, .info, nil)
} else if let _ = forwardInfo.authorSignature {
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, forwardInfoNode, nil)
} }
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId) })
} else if let id = forwardInfo.source?.id ?? forwardInfo.author?.id {
item.controllerInteraction.openPeer(id, .info, nil)
} else if let _ = forwardInfo.authorSignature {
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, forwardInfoNode, nil)
}
return true
} }
} }
var foundTapAction = false
loop: for contentNode in self.contentNodes { loop: for contentNode in self.contentNodes {
let tapAction = contentNode.tapActionAtPoint(CGPoint(x: location.x - contentNode.frame.minX, y: location.y - contentNode.frame.minY), gesture: gesture, isEstimating: false) let tapAction = contentNode.tapActionAtPoint(CGPoint(x: location.x - contentNode.frame.minX, y: location.y - contentNode.frame.minY), gesture: gesture, isEstimating: false)
switch tapAction { switch tapAction {
case .none, .ignore: case .none:
if let item = self.item, self.backgroundNode.frame.contains(CGPoint(x: self.frame.width - location.x, y: location.y)), let tapMessage = self.item?.controllerInteraction.tapMessage { if let item = self.item, self.backgroundNode.frame.contains(CGPoint(x: self.frame.width - location.x, y: location.y)), let tapMessage = self.item?.controllerInteraction.tapMessage {
foundTapAction = true return .action({
tapMessage(item.message) tapMessage(item.message)
} })
break }
case let .url(url, concealed): case .ignore:
foundTapAction = true if let item = self.item, self.backgroundNode.frame.contains(CGPoint(x: self.frame.width - location.x, y: location.y)), let tapMessage = self.item?.controllerInteraction.tapMessage {
return .action({
tapMessage(item.message)
})
} else {
return .action({
})
}
case let .url(url, concealed):
return .action({
self.item?.controllerInteraction.openUrl(url, concealed, nil, self.item?.content.firstMessage) self.item?.controllerInteraction.openUrl(url, concealed, nil, self.item?.content.firstMessage)
break loop })
case let .peerMention(peerId, _): case let .peerMention(peerId, _):
foundTapAction = true return .action({
self.item?.controllerInteraction.openPeer(peerId, .chat(textInputState: nil, subject: nil), nil) self.item?.controllerInteraction.openPeer(peerId, .chat(textInputState: nil, subject: nil), nil)
break loop })
case let .textMention(name): case let .textMention(name):
foundTapAction = true return .action({
self.item?.controllerInteraction.openPeerMention(name) self.item?.controllerInteraction.openPeerMention(name)
break loop })
case let .botCommand(command): case let .botCommand(command):
foundTapAction = true if let item = self.item {
if let item = self.item { return .action({
item.controllerInteraction.sendBotCommand(item.message.id, command) item.controllerInteraction.sendBotCommand(item.message.id, command)
} })
break loop }
case let .hashtag(peerName, hashtag): case let .hashtag(peerName, hashtag):
foundTapAction = true return .action({
self.item?.controllerInteraction.openHashtag(peerName, hashtag) self.item?.controllerInteraction.openHashtag(peerName, hashtag)
break loop })
case .instantPage: case .instantPage:
foundTapAction = true if let item = self.item {
if let item = self.item { return .optionalAction({
item.controllerInteraction.openInstantPage(item.message, item.associatedData) item.controllerInteraction.openInstantPage(item.message, item.associatedData)
} })
break loop }
case .wallpaper: case .wallpaper:
foundTapAction = true if let item = self.item {
if let item = self.item { return .action({
item.controllerInteraction.openWallpaper(item.message) item.controllerInteraction.openWallpaper(item.message)
} })
break loop }
case .theme: case .theme:
foundTapAction = true if let item = self.item {
if let item = self.item { return .action({
item.controllerInteraction.openTheme(item.message) item.controllerInteraction.openTheme(item.message)
} })
break loop }
case let .call(peerId): case let .call(peerId):
foundTapAction = true return .optionalAction({
self.item?.controllerInteraction.callPeer(peerId) self.item?.controllerInteraction.callPeer(peerId)
break loop })
case .openMessage: case .openMessage:
foundTapAction = true if let item = self.item {
if let item = self.item { return .action({
let _ = item.controllerInteraction.openMessage(item.message, .default) let _ = item.controllerInteraction.openMessage(item.message, .default)
} })
break loop }
case let .timecode(timecode, _): case let .timecode(timecode, _):
foundTapAction = true if let item = self.item, let mediaMessage = mediaMessage {
if let item = self.item, let mediaMessage = mediaMessage { return .action({
item.controllerInteraction.seekToTimecode(mediaMessage, timecode, forceOpen) item.controllerInteraction.seekToTimecode(mediaMessage, timecode, forceOpen)
} })
break loop }
case let .bankCard(number): case let .bankCard(number):
foundTapAction = true if let item = self.item {
if let item = self.item { return .action({
item.controllerInteraction.longTap(.bankCard(number), item.message) item.controllerInteraction.longTap(.bankCard(number), item.message)
} })
case let .tooltip(text, node, rect): }
foundTapAction = true case let .tooltip(text, node, rect):
if let item = self.item { if let item = self.item {
return .action({
let _ = item.controllerInteraction.displayMessageTooltip(item.message.id, text, node, rect) let _ = item.controllerInteraction.displayMessageTooltip(item.message.id, text, node, rect)
} })
break loop }
} }
} }
if !foundTapAction { return nil
self.item?.controllerInteraction.clickThroughMessage()
}
case .longTap, .doubleTap: case .longTap, .doubleTap:
if let item = self.item, self.backgroundNode.frame.contains(location) { if let item = self.item, self.backgroundNode.frame.contains(location) {
let message = item.message let message = item.message
var foundTapAction = false
var tapMessage: Message? = item.content.firstMessage var tapMessage: Message? = item.content.firstMessage
var selectAll = true var selectAll = true
loop: for contentNode in self.contentNodes { loop: for contentNode in self.contentNodes {
@ -2475,53 +2555,53 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
tapMessage = contentNode.item?.message tapMessage = contentNode.item?.message
let tapAction = contentNode.tapActionAtPoint(CGPoint(x: location.x - contentNode.frame.minX, y: location.y - contentNode.frame.minY), gesture: gesture, isEstimating: false) let tapAction = contentNode.tapActionAtPoint(CGPoint(x: location.x - contentNode.frame.minX, y: location.y - contentNode.frame.minY), gesture: gesture, isEstimating: false)
switch tapAction { switch tapAction {
case .none, .ignore: case .none, .ignore:
break break
case let .url(url, _): case let .url(url, _):
foundTapAction = true return .action({
item.controllerInteraction.longTap(.url(url), message) item.controllerInteraction.longTap(.url(url), message)
break loop })
case let .peerMention(peerId, mention): case let .peerMention(peerId, mention):
foundTapAction = true return .action({
item.controllerInteraction.longTap(.peerMention(peerId, mention), message) item.controllerInteraction.longTap(.peerMention(peerId, mention), message)
break loop })
case let .textMention(name): case let .textMention(name):
foundTapAction = true return .action({
item.controllerInteraction.longTap(.mention(name), message) item.controllerInteraction.longTap(.mention(name), message)
break loop })
case let .botCommand(command): case let .botCommand(command):
foundTapAction = true return .action({
item.controllerInteraction.longTap(.command(command), message) item.controllerInteraction.longTap(.command(command), message)
break loop })
case let .hashtag(_, hashtag): case let .hashtag(_, hashtag):
foundTapAction = true return .action({
item.controllerInteraction.longTap(.hashtag(hashtag), message) item.controllerInteraction.longTap(.hashtag(hashtag), message)
break loop })
case .instantPage: case .instantPage:
break break
case .wallpaper: case .wallpaper:
break break
case .theme: case .theme:
break break
case .call: case .call:
break break
case .openMessage: case .openMessage:
foundTapAction = false break
break case let .timecode(timecode, text):
case let .timecode(timecode, text): if let mediaMessage = mediaMessage {
foundTapAction = true return .action({
if let mediaMessage = mediaMessage {
item.controllerInteraction.longTap(.timecode(timecode, text), mediaMessage) item.controllerInteraction.longTap(.timecode(timecode, text), mediaMessage)
} })
break loop }
case let .bankCard(number): case let .bankCard(number):
foundTapAction = true return .action({
item.controllerInteraction.longTap(.bankCard(number), message) item.controllerInteraction.longTap(.bankCard(number), message)
case .tooltip: })
break case .tooltip:
break
} }
} }
if !foundTapAction, let tapMessage = tapMessage { if let tapMessage = tapMessage {
var subFrame = self.backgroundNode.frame var subFrame = self.backgroundNode.frame
if case .group = item.content { if case .group = item.content {
for contentNode in self.contentNodes { for contentNode in self.contentNodes {
@ -2531,14 +2611,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
} }
} }
} }
item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, self, subFrame, recognizer) return .openContextMenu(tapMessage: tapMessage, selectAll: selectAll, subFrame: subFrame)
return false
} }
} }
default: default:
break break
} }
return true return nil
} }
private func traceSelectionNodes(parent: ASDisplayNode, point: CGPoint) -> ASDisplayNode? { private func traceSelectionNodes(parent: ASDisplayNode, point: CGPoint) -> ASDisplayNode? {

View File

@ -118,7 +118,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
let gestureRecognizer = ContextGesture(target: nil, action: nil) let gestureRecognizer = ContextGesture(target: nil, action: nil)
self.sendButton.view.addGestureRecognizer(gestureRecognizer) self.sendButton.view.addGestureRecognizer(gestureRecognizer)
self.gestureRecognizer = gestureRecognizer self.gestureRecognizer = gestureRecognizer
gestureRecognizer.activated = { [weak self] gesture in gestureRecognizer.activated = { [weak self] gesture, _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }

View File

@ -70,7 +70,7 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode {
let gestureRecognizer = ContextGesture(target: nil, action: nil) let gestureRecognizer = ContextGesture(target: nil, action: nil)
self.gestureRecognizer = gestureRecognizer self.gestureRecognizer = gestureRecognizer
self.sendButton.view.addGestureRecognizer(gestureRecognizer) self.sendButton.view.addGestureRecognizer(gestureRecognizer)
gestureRecognizer.activated = { [weak self] recognizer in gestureRecognizer.activated = { [weak self] recognizer, _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }

View File

@ -183,7 +183,7 @@ final class GridMessageItemNode: GridItemNode {
self.containerNode.addSubnode(self.imageNode) self.containerNode.addSubnode(self.imageNode)
self.containerNode.addSubnode(self.mediaBadgeNode) self.containerNode.addSubnode(self.mediaBadgeNode)
self.containerNode.activated = { [weak self] gesture in self.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self, let item = strongSelf.item, let controllerInteraction = strongSelf.controllerInteraction else { guard let strongSelf = self, let item = strongSelf.item, let controllerInteraction = strongSelf.controllerInteraction else {
gesture.cancel() gesture.cancel()
return return
@ -458,8 +458,7 @@ final class GridMessageItemNode: GridItemNode {
let _ = controllerInteraction.openMessage(message, .default) let _ = controllerInteraction.openMessage(message, .default)
} }
case .longTap: case .longTap:
break break
//controllerInteraction.openMessageContextMenu(message, false, self, self.bounds, nil)
default: default:
break break
} }

View File

@ -162,7 +162,7 @@ final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate {
return strongSelf.fileAt(point: point) != nil return strongSelf.fileAt(point: point) != nil
} }
self.contextContainerNode.activated = { [weak self] gesture in self.contextContainerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self, let gestureLocation = gestureLocation else { guard let strongSelf = self, let gestureLocation = gestureLocation else {
return return
} }

View File

@ -71,7 +71,7 @@ private final class VisualMediaItemNode: ASDisplayNode {
self.containerNode.addSubnode(self.imageNode) self.containerNode.addSubnode(self.imageNode)
self.containerNode.addSubnode(self.mediaBadgeNode) self.containerNode.addSubnode(self.mediaBadgeNode)
self.containerNode.activated = { [weak self] gesture in self.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self, let item = strongSelf.item else { guard let strongSelf = self, let item = strongSelf.item else {
return return
} }

View File

@ -142,7 +142,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
let items = (chatAvailableMessageActionsImpl(postbox: strongSelf.context.account.postbox, accountPeerId: strongSelf.context.account.peerId, messageIds: [message.id]) (chatAvailableMessageActionsImpl(postbox: strongSelf.context.account.postbox, accountPeerId: strongSelf.context.account.peerId, messageIds: [message.id])
|> deliverOnMainQueue).start(next: { actions in |> deliverOnMainQueue).start(next: { actions in
var messageIds = Set<MessageId>() var messageIds = Set<MessageId>()
messageIds.insert(message.id) messageIds.insert(message.id)

View File

@ -449,12 +449,12 @@ public final class WalletStrings: Equatable {
public var Wallet_SecureStorageReset_Title: String { return self._s[219]! } public var Wallet_SecureStorageReset_Title: String { return self._s[219]! }
public var Wallet_Receive_CommentHeader: String { return self._s[220]! } public var Wallet_Receive_CommentHeader: String { return self._s[220]! }
public var Wallet_Info_ReceiveGrams: String { return self._s[221]! } public var Wallet_Info_ReceiveGrams: String { return self._s[221]! }
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String { public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
let form = getPluralizationForm(self.lc, value) let form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator) let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue) return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue)
} }
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String { public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
let form = getPluralizationForm(self.lc, value) let form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator) let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue) return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue)