Various improvements

This commit is contained in:
Ilya Laktyushin 2025-06-19 01:26:50 +02:00
parent 1b888befa1
commit c5223959b2
23 changed files with 368 additions and 47 deletions

View File

@ -14449,3 +14449,7 @@ Sorry for the inconvenience.";
"SuggestPost.SetTimeFormat.Date" = "%@"; "SuggestPost.SetTimeFormat.Date" = "%@";
"SuggestPost.SetTimeFormat.TodayAt" = "Today at %@"; "SuggestPost.SetTimeFormat.TodayAt" = "Today at %@";
"SuggestPost.SetTimeFormat.TomorrowAt" = "Tomorrow at %@"; "SuggestPost.SetTimeFormat.TomorrowAt" = "Tomorrow at %@";
"Chat.TodoItemCompletionTimestamp.Date" = "completed %@";
"Chat.TodoItemCompletionTimestamp.TodayAt" = "completed today at %@";
"Chat.TodoItemCompletionTimestamp.YesterdayAt" = "completed yesterday at %@";

View File

@ -1272,7 +1272,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
}, openBoostToUnrestrict: { }, openBoostToUnrestrict: {
}, updateRecordingTrimRange: { _, _, _, _ in }, updateRecordingTrimRange: { _, _, _, _ in
}, dismissAllTooltips: { }, dismissAllTooltips: {
}, editTodoMessage: { _, _ in }, editTodoMessage: { _, _, _ in
}, updateHistoryFilter: { _ in }, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _, _ in }, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: { }, toggleChatSidebarMode: {

View File

@ -664,6 +664,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
let appleIdProvider = ASAuthorizationAppleIDProvider() let appleIdProvider = ASAuthorizationAppleIDProvider()
let request = appleIdProvider.createRequest() let request = appleIdProvider.createRequest()
request.requestedScopes = [.email]
request.user = number request.user = number
let authorizationController = ASAuthorizationController(authorizationRequests: [request]) let authorizationController = ASAuthorizationController(authorizationRequests: [request])

View File

@ -101,6 +101,7 @@ public final class BrowserBookmarksScreen: ViewController {
}, callPeer: { _, _ in }, callPeer: { _, _ in
}, openConferenceCall: { _ in }, openConferenceCall: { _ in
}, longTap: { _, _ in }, longTap: { _, _ in
}, todoItemLongTap: { _, _ in
}, openCheckoutOrReceipt: { _, _ in }, openCheckoutOrReceipt: { _, _ in
}, openSearch: { }, openSearch: {
}, setupReply: { _ in }, setupReply: { _ in

View File

@ -179,7 +179,7 @@ public final class ChatPanelInterfaceInteraction {
public let openBoostToUnrestrict: () -> Void public let openBoostToUnrestrict: () -> Void
public let updateRecordingTrimRange: (Double, Double, Bool, Bool) -> Void public let updateRecordingTrimRange: (Double, Double, Bool, Bool) -> Void
public let dismissAllTooltips: () -> Void public let dismissAllTooltips: () -> Void
public let editTodoMessage: (MessageId, Bool) -> Void public let editTodoMessage: (MessageId, Int32?, Bool) -> Void
public let requestLayout: (ContainedViewLayoutTransition) -> Void public let requestLayout: (ContainedViewLayoutTransition) -> Void
public let chatController: () -> ViewController? public let chatController: () -> ViewController?
public let statuses: ChatPanelInterfaceInteractionStatuses? public let statuses: ChatPanelInterfaceInteractionStatuses?
@ -298,7 +298,7 @@ public final class ChatPanelInterfaceInteraction {
openBoostToUnrestrict: @escaping () -> Void, openBoostToUnrestrict: @escaping () -> Void,
updateRecordingTrimRange: @escaping (Double, Double, Bool, Bool) -> Void, updateRecordingTrimRange: @escaping (Double, Double, Bool, Bool) -> Void,
dismissAllTooltips: @escaping () -> Void, dismissAllTooltips: @escaping () -> Void,
editTodoMessage: @escaping (MessageId, Bool) -> Void, editTodoMessage: @escaping (MessageId, Int32?, Bool) -> Void,
updateHistoryFilter: @escaping ((ChatPresentationInterfaceState.HistoryFilter?) -> ChatPresentationInterfaceState.HistoryFilter?) -> Void, updateHistoryFilter: @escaping ((ChatPresentationInterfaceState.HistoryFilter?) -> ChatPresentationInterfaceState.HistoryFilter?) -> Void,
updateChatLocationThread: @escaping (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void, updateChatLocationThread: @escaping (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void,
toggleChatSidebarMode: @escaping () -> Void, toggleChatSidebarMode: @escaping () -> Void,
@ -551,7 +551,7 @@ public final class ChatPanelInterfaceInteraction {
}, openBoostToUnrestrict: { }, openBoostToUnrestrict: {
}, updateRecordingTrimRange: { _, _, _, _ in }, updateRecordingTrimRange: { _, _, _, _ in
}, dismissAllTooltips: { }, dismissAllTooltips: {
}, editTodoMessage: { _, _ in }, editTodoMessage: { _, _, _ in
}, updateHistoryFilter: { _ in }, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _, _ in }, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: { }, toggleChatSidebarMode: {

View File

@ -132,7 +132,7 @@ public final class TelegramMediaTodo: Media, Equatable {
return true return true
} }
func withUpdated(items: [TelegramMediaTodo.Item]) -> TelegramMediaTodo { public func withUpdated(items: [TelegramMediaTodo.Item]) -> TelegramMediaTodo {
return TelegramMediaTodo( return TelegramMediaTodo(
flags: self.flags, flags: self.flags,
text: self.text, text: self.text,

View File

@ -28,6 +28,7 @@ swift_library(
"//submodules/TelegramUI/Components/Chat/MergedAvatarsNode", "//submodules/TelegramUI/Components/Chat/MergedAvatarsNode",
"//submodules/TelegramUI/Components/TextNodeWithEntities", "//submodules/TelegramUI/Components/TextNodeWithEntities",
"//submodules/TelegramUI/Components/Chat/ShimmeringLinkNode", "//submodules/TelegramUI/Components/Chat/ShimmeringLinkNode",
"//submodules/TelegramUI/Components/ChatControllerInteraction",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -17,6 +17,7 @@ import ChatMessageItemCommon
import PollBubbleTimerNode import PollBubbleTimerNode
import TextNodeWithEntities import TextNodeWithEntities
import ShimmeringLinkNode import ShimmeringLinkNode
import ChatControllerInteraction
private final class ChatMessageTaskOptionRadioNodeParameters: NSObject { private final class ChatMessageTaskOptionRadioNodeParameters: NSObject {
let timestamp: Double let timestamp: Double
@ -372,16 +373,20 @@ private func generatePercentageAnimationImages(presentationData: ChatPresentatio
} }
private final class ChatMessageTodoItemNode: ASDisplayNode { private final class ChatMessageTodoItemNode: ASDisplayNode {
private let highlightedBackgroundNode: ASDisplayNode fileprivate let highlightedBackgroundNode: ASDisplayNode
private var avatarNode: AvatarNode? private var avatarNode: AvatarNode?
private(set) var radioNode: ChatMessageTaskOptionRadioNode? private(set) var radioNode: ChatMessageTaskOptionRadioNode?
private var iconNode: ASImageNode? private var iconNode: ASImageNode?
fileprivate var titleNode: TextNodeWithEntities? fileprivate var titleNode: TextNodeWithEntities?
fileprivate var nameNode: TextNode?
private let buttonNode: HighlightTrackingButtonNode private let buttonNode: HighlightTrackingButtonNode
let separatorNode: ASDisplayNode let separatorNode: ASDisplayNode
var option: TelegramMediaTodo.Item? var option: TelegramMediaTodo.Item?
var pressed: (() -> Void)? var pressed: (() -> Void)?
var selectionUpdated: (() -> Void)? var selectionUpdated: (() -> Void)?
var longTapped: (() -> Void)?
private var theme: PresentationTheme? private var theme: PresentationTheme?
weak var previousOptionNode: ChatMessageTodoItemNode? weak var previousOptionNode: ChatMessageTodoItemNode?
@ -389,6 +394,8 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
private var canMark = false private var canMark = false
private var isPremium = false private var isPremium = false
private var ignoreNextTap = false
var visibilityRect: CGRect? { var visibilityRect: CGRect? {
didSet { didSet {
if self.visibilityRect != oldValue { if self.visibilityRect != oldValue {
@ -438,6 +445,13 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
strongSelf.previousOptionNode?.separatorNode.layer.removeAnimation(forKey: "opacity") strongSelf.previousOptionNode?.separatorNode.layer.removeAnimation(forKey: "opacity")
strongSelf.previousOptionNode?.separatorNode.alpha = 0.0 strongSelf.previousOptionNode?.separatorNode.alpha = 0.0
Queue.mainQueue().after(0.8) {
if strongSelf.highlightedBackgroundNode.alpha == 1.0 {
strongSelf.ignoreNextTap = true
strongSelf.longTapped?()
}
}
} else { } else {
strongSelf.highlightedBackgroundNode.alpha = 0.0 strongSelf.highlightedBackgroundNode.alpha = 0.0
strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { finished in strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { finished in
@ -459,6 +473,10 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
} }
@objc private func buttonPressed() { @objc private func buttonPressed() {
guard !self.ignoreNextTap else {
self.ignoreNextTap = false
return
}
if let radioNode = self.radioNode, let isChecked = radioNode.isChecked, self.canMark, self.isPremium { if let radioNode = self.radioNode, let isChecked = radioNode.isChecked, self.canMark, self.isPremium {
radioNode.updateIsChecked(!isChecked, animated: true) radioNode.updateIsChecked(!isChecked, animated: true)
self.selectionUpdated?() self.selectionUpdated?()
@ -469,6 +487,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
static func asyncLayout(_ maybeNode: ChatMessageTodoItemNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ todo: TelegramMediaTodo, _ option: TelegramMediaTodo.Item, _ completion: TelegramMediaTodo.Completion?, _ translation: TranslationMessageAttribute.Additional?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessageTodoItemNode))) { static func asyncLayout(_ maybeNode: ChatMessageTodoItemNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ todo: TelegramMediaTodo, _ option: TelegramMediaTodo.Item, _ completion: TelegramMediaTodo.Completion?, _ translation: TranslationMessageAttribute.Additional?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessageTodoItemNode))) {
let makeTitleLayout = TextNodeWithEntities.asyncLayout(maybeNode?.titleNode) let makeTitleLayout = TextNodeWithEntities.asyncLayout(maybeNode?.titleNode)
let makeNameLayout = TextNode.asyncLayout(maybeNode?.nameNode)
return { context, presentationData, message, todo, option, completion, translation, constrainedWidth in return { context, presentationData, message, todo, option, completion, translation, constrainedWidth in
var canMark = false var canMark = false
@ -480,6 +499,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
let rightInset: CGFloat = 12.0 let rightInset: CGFloat = 12.0
let incoming = message.effectivelyIncoming(context.account.peerId) let incoming = message.effectivelyIncoming(context.account.peerId)
let messageTheme = incoming ? presentationData.theme.theme.chat.message.incoming : presentationData.theme.theme.chat.message.outgoing
var optionText = option.text var optionText = option.text
var optionEntities = option.entities var optionEntities = option.entities
@ -492,7 +512,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
optionEntities.append(MessageTextEntity(range: 0 ..< (optionText as NSString).length, type: .Strikethrough)) optionEntities.append(MessageTextEntity(range: 0 ..< (optionText as NSString).length, type: .Strikethrough))
} }
let optionTextColor: UIColor = incoming ? presentationData.theme.theme.chat.message.incoming.primaryTextColor : presentationData.theme.theme.chat.message.outgoing.primaryTextColor let optionTextColor: UIColor = messageTheme.primaryTextColor
let optionAttributedText = stringWithAppliedEntities( let optionAttributedText = stringWithAppliedEntities(
optionText, optionText,
entities: optionEntities, entities: optionEntities,
@ -510,6 +530,13 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: optionAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: max(1.0, constrainedWidth - leftInset - rightInset), height: CGFloat.greatestFiniteMagnitude), alignment: .left, cutout: nil, insets: UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0))) let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: optionAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: max(1.0, constrainedWidth - leftInset - rightInset), height: CGFloat.greatestFiniteMagnitude), alignment: .left, cutout: nil, insets: UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0)))
let nameLayoutAndApply: (TextNodeLayout, () -> TextNode)?
if let completion, let peer = message.peers[completion.completedBy], todo.flags.contains(.othersCanComplete) {
nameLayoutAndApply = makeNameLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), font: Font.regular(11.0), textColor: messageTheme.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: max(1.0, constrainedWidth - leftInset - rightInset), height: CGFloat.greatestFiniteMagnitude), alignment: .left, cutout: nil, insets: UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0)))
} else {
nameLayoutAndApply = nil
}
let contentHeight: CGFloat = max(46.0, titleLayout.size.height + 22.0) let contentHeight: CGFloat = max(46.0, titleLayout.size.height + 22.0)
let isSelectable: Bool = true let isSelectable: Bool = true
@ -558,12 +585,16 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
placeholderColor: incoming ? presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor, placeholderColor: incoming ? presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor,
attemptSynchronous: attemptSynchronous attemptSynchronous: attemptSynchronous
)) ))
let titleNodeFrame: CGRect var titleNodeFrame: CGRect
if titleLayout.hasRTL { if titleLayout.hasRTL {
titleNodeFrame = CGRect(origin: CGPoint(x: width - rightInset - titleLayout.size.width, y: 12.0), size: titleLayout.size) titleNodeFrame = CGRect(origin: CGPoint(x: width - rightInset - titleLayout.size.width, y: 12.0), size: titleLayout.size)
} else { } else {
titleNodeFrame = CGRect(origin: CGPoint(x: leftInset, y: 12.0), size: titleLayout.size) titleNodeFrame = CGRect(origin: CGPoint(x: leftInset, y: 12.0), size: titleLayout.size)
} }
if let _ = completion, canMark && todo.flags.contains(.othersCanComplete) {
titleNodeFrame = titleNodeFrame.offsetBy(dx: 0.0, dy: -6.0)
}
if node.titleNode !== titleNode { if node.titleNode !== titleNode {
node.titleNode = titleNode node.titleNode = titleNode
node.addSubnode(titleNode.textNode) node.addSubnode(titleNode.textNode)
@ -573,8 +604,43 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
titleNode.visibilityRect = visibilityRect.offsetBy(dx: 0.0, dy: titleNodeFrame.minY) titleNode.visibilityRect = visibilityRect.offsetBy(dx: 0.0, dy: titleNodeFrame.minY)
} }
} }
let previousFrame = titleNode.textNode.frame
titleNode.textNode.frame = titleNodeFrame titleNode.textNode.frame = titleNodeFrame
if animated, previousFrame != titleNodeFrame {
titleNode.textNode.layer.animateFrame(from: previousFrame, to: titleNodeFrame, duration: 0.2)
}
if let (nameLayout, nameApply) = nameLayoutAndApply {
var nameNodeFrame: CGRect
if titleLayout.hasRTL {
nameNodeFrame = CGRect(origin: CGPoint(x: width - rightInset - nameLayout.size.width, y: 26.0), size: nameLayout.size)
} else {
nameNodeFrame = CGRect(origin: CGPoint(x: leftInset, y: 26.0), size: nameLayout.size)
}
let nameNode = nameApply()
if node.nameNode !== nameNode {
node.nameNode = nameNode
node.addSubnode(nameNode)
nameNode.isUserInteractionEnabled = false
if animated {
nameNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
nameNode.frame = nameNodeFrame
} else if let nameNode = node.nameNode {
node.nameNode = nil
if animated {
nameNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak nameNode] _ in
nameNode?.removeFromSupernode()
})
} else {
nameNode.removeFromSupernode()
}
}
if let completion, canMark && todo.flags.contains(.othersCanComplete) { if let completion, canMark && todo.flags.contains(.othersCanComplete) {
let avatarNode: AvatarNode let avatarNode: AvatarNode
if let current = node.avatarNode { if let current = node.avatarNode {
@ -882,9 +948,8 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
let messageTheme = incoming ? item.presentationData.theme.theme.chat.message.incoming : item.presentationData.theme.theme.chat.message.outgoing let messageTheme = incoming ? item.presentationData.theme.theme.chat.message.incoming : item.presentationData.theme.theme.chat.message.outgoing
var todoTitleText = todo?.text ?? ""
var pollTitleText = todo?.text ?? "" var todoTitleEntities = todo?.textEntities ?? []
var pollTitleEntities = todo?.textEntities ?? []
var pollOptions: [TranslationMessageAttribute.Additional] = [] var pollOptions: [TranslationMessageAttribute.Additional] = []
var isTranslating = false var isTranslating = false
@ -892,8 +957,8 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
isTranslating = true isTranslating = true
for attribute in item.message.attributes { for attribute in item.message.attributes {
if let attribute = attribute as? TranslationMessageAttribute, !attribute.text.isEmpty, attribute.toLang == translateToLanguage { if let attribute = attribute as? TranslationMessageAttribute, !attribute.text.isEmpty, attribute.toLang == translateToLanguage {
pollTitleText = attribute.text todoTitleText = attribute.text
pollTitleEntities = attribute.entities todoTitleEntities = attribute.entities
pollOptions = attribute.additional pollOptions = attribute.additional
isTranslating = false isTranslating = false
break break
@ -902,8 +967,8 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
} }
let attributedText = stringWithAppliedEntities( let attributedText = stringWithAppliedEntities(
pollTitleText, todoTitleText,
entities: pollTitleEntities, entities: todoTitleEntities,
baseColor: messageTheme.primaryTextColor, baseColor: messageTheme.primaryTextColor,
linkColor: messageTheme.linkTextColor, linkColor: messageTheme.linkTextColor,
baseFont: item.presentationData.messageBoldFont, baseFont: item.presentationData.messageBoldFont,
@ -1062,9 +1127,6 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
var isRequesting = false var isRequesting = false
if let todo, i < todo.items.count { if let todo, i < todo.items.count {
isRequesting = false isRequesting = false
// if let inProgressOpaqueIds = item.controllerInteraction.pollActionState.pollMessageIdsInProgress[item.message.id] {
// isRequesting = inProgressOpaqueIds.contains(poll.options[i].opaqueIdentifier)
// }
} }
let optionNode = apply(animation.isAnimated, isRequesting, synchronousLoad) let optionNode = apply(animation.isAnimated, isRequesting, synchronousLoad)
let optionNodeFrame = CGRect(origin: CGPoint(x: layoutConstants.bubble.borderInset, y: verticalOffset), size: size) let optionNodeFrame = CGRect(origin: CGPoint(x: layoutConstants.bubble.borderInset, y: verticalOffset), size: size)
@ -1083,6 +1145,12 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
} }
item.controllerInteraction.displayTodoToggleUnavailable(item.message.id) item.controllerInteraction.displayTodoToggleUnavailable(item.message.id)
} }
optionNode.longTapped = { [weak optionNode] in
guard let strongSelf = self, let item = strongSelf.item, let todoItem, let optionNode, let contentNode = strongSelf.contextContentNodeForItem(itemNode: optionNode) else {
return
}
item.controllerInteraction.todoItemLongTap(todoItem.id, ChatControllerInteraction.LongTapParams(message: message, contentNode: contentNode, messageNode: strongSelf, progress: nil))
}
optionNode.frame = optionNodeFrame optionNode.frame = optionNodeFrame
} else { } else {
animation.animator.updateFrame(layer: optionNode.layer, frame: optionNodeFrame, completion: nil) animation.animator.updateFrame(layer: optionNode.layer, frame: optionNodeFrame, completion: nil)
@ -1313,4 +1381,42 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
} }
return nil return nil
} }
private func contextContentNodeForItem(itemNode: ChatMessageTodoItemNode) -> ContextExtractedContentContainingNode? {
guard let item = self.item else {
return nil
}
let containingNode = ContextExtractedContentContainingNode()
let incoming = item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData)
itemNode.highlightedBackgroundNode.alpha = 0.0
guard let snapshotView = itemNode.view.snapshotContentTree() else {
return nil
}
let backgroundNode = ASDisplayNode()
backgroundNode.backgroundColor = (incoming ? item.presentationData.theme.theme.chat.message.incoming.bubble.withoutWallpaper.fill : item.presentationData.theme.theme.chat.message.outgoing.bubble.withoutWallpaper.fill).first ?? .black
backgroundNode.clipsToBounds = true
backgroundNode.cornerRadius = 10.0
let insets = UIEdgeInsets.zero
let backgroundSize = CGSize(width: snapshotView.frame.width + insets.left + insets.right, height: snapshotView.frame.height + insets.top + insets.bottom)
backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: backgroundSize)
snapshotView.frame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: snapshotView.frame.size)
backgroundNode.view.addSubview(snapshotView)
let origin = CGPoint(x: 3.0, y: 1.0) //self.backgroundNode.frame.minX + 3.0, y: 1.0)
containingNode.frame = CGRect(origin: origin, size: CGSize(width: backgroundSize.width, height: backgroundSize.height + 20.0))
containingNode.contentNode.frame = CGRect(origin: .zero, size: backgroundSize)
containingNode.contentRect = CGRect(origin: .zero, size: backgroundSize)
containingNode.contentNode.addSubnode(backgroundNode)
containingNode.contentNode.alpha = 0.0
self.addSubnode(containingNode)
return containingNode
}
} }

View File

@ -172,7 +172,7 @@ public final class ChatRecentActionsController: TelegramBaseController {
}, openBoostToUnrestrict: { }, openBoostToUnrestrict: {
}, updateRecordingTrimRange: { _, _, _, _ in }, updateRecordingTrimRange: { _, _, _, _ in
}, dismissAllTooltips: { }, dismissAllTooltips: {
}, editTodoMessage: { _, _ in }, editTodoMessage: { _, _, _ in
}, updateHistoryFilter: { _ in }, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _, _ in }, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: { }, toggleChatSidebarMode: {

View File

@ -569,6 +569,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
break break
} }
} }
}, todoItemLongTap: { _, _ in
}, openCheckoutOrReceipt: { _, _ in }, openCheckoutOrReceipt: { _, _ in
}, openSearch: { }, openSearch: {
}, setupReply: { _ in }, setupReply: { _ in

View File

@ -429,7 +429,7 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess
}, chatControllerNode: { }, chatControllerNode: {
return nil return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, openConferenceCall: { _ in }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, openConferenceCall: { _ in
}, longTap: { _, _ in }, openCheckoutOrReceipt: { _, _ in }, openSearch: { }, setupReply: { _ in }, longTap: { _, _ in }, todoItemLongTap: { _, _ in }, openCheckoutOrReceipt: { _, _ in }, openSearch: { }, setupReply: { _ in
}, canSetupReply: { _ in }, canSetupReply: { _ in
return .none return .none
}, canSendMessages: { }, canSendMessages: {

View File

@ -215,6 +215,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
public let callPeer: (PeerId, Bool) -> Void public let callPeer: (PeerId, Bool) -> Void
public let openConferenceCall: (Message) -> Void public let openConferenceCall: (Message) -> Void
public let longTap: (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void public let longTap: (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void
public let todoItemLongTap: (Int32, LongTapParams?) -> Void
public let openCheckoutOrReceipt: (MessageId, OpenMessageParams?) -> Void public let openCheckoutOrReceipt: (MessageId, OpenMessageParams?) -> Void
public let openSearch: () -> Void public let openSearch: () -> Void
public let setupReply: (MessageId) -> Void public let setupReply: (MessageId) -> Void
@ -379,6 +380,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
callPeer: @escaping (PeerId, Bool) -> Void, callPeer: @escaping (PeerId, Bool) -> Void,
openConferenceCall: @escaping (Message) -> Void, openConferenceCall: @escaping (Message) -> Void,
longTap: @escaping (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void,
todoItemLongTap: @escaping (Int32, LongTapParams?) -> Void,
openCheckoutOrReceipt: @escaping (MessageId, OpenMessageParams?) -> Void, openCheckoutOrReceipt: @escaping (MessageId, OpenMessageParams?) -> Void,
openSearch: @escaping () -> Void, openSearch: @escaping () -> Void,
setupReply: @escaping (MessageId) -> Void, setupReply: @escaping (MessageId) -> Void,
@ -499,6 +501,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
self.callPeer = callPeer self.callPeer = callPeer
self.openConferenceCall = openConferenceCall self.openConferenceCall = openConferenceCall
self.longTap = longTap self.longTap = longTap
self.todoItemLongTap = todoItemLongTap
self.openCheckoutOrReceipt = openCheckoutOrReceipt self.openCheckoutOrReceipt = openCheckoutOrReceipt
self.openSearch = openSearch self.openSearch = openSearch
self.setupReply = setupReply self.setupReply = setupReply

View File

@ -982,6 +982,11 @@ final class ComposeTodoScreenComponent: Component {
} }
} }
var focusedIndex: Int?
if isFirstTime, let focusedId = component.initialData.focusedId {
focusedIndex = self.todoItems.firstIndex(where: { $0.id == focusedId })
}
for i in 0 ..< todoItemsSectionReadyItems.count { for i in 0 ..< todoItemsSectionReadyItems.count {
var activate = false var activate = false
let placeholder: String let placeholder: String
@ -994,6 +999,10 @@ final class ComposeTodoScreenComponent: Component {
placeholder = "Task" placeholder = "Task"
} }
if let focusedIndex, i == focusedIndex {
activate = true
}
if let itemView = todoItemsSectionReadyItems[i].itemView.contents.view as? ListComposePollOptionComponent.View { if let itemView = todoItemsSectionReadyItems[i].itemView.contents.view as? ListComposePollOptionComponent.View {
itemView.updateCustomPlaceholder(value: placeholder, size: todoItemsSectionReadyItems[i].size, transition: todoItemsSectionReadyItems[i].transition) itemView.updateCustomPlaceholder(value: placeholder, size: todoItemsSectionReadyItems[i].size, transition: todoItemsSectionReadyItems[i].transition)
@ -1527,6 +1536,7 @@ public class ComposeTodoScreen: ViewControllerComponentContainer, AttachmentCont
fileprivate let maxTodoItemLength: Int fileprivate let maxTodoItemLength: Int
fileprivate let maxTodoItemsCount: Int fileprivate let maxTodoItemsCount: Int
fileprivate let existingTodo: TelegramMediaTodo? fileprivate let existingTodo: TelegramMediaTodo?
fileprivate let focusedId: Int32?
fileprivate let append: Bool fileprivate let append: Bool
fileprivate let canEdit: Bool fileprivate let canEdit: Bool
@ -1535,6 +1545,7 @@ public class ComposeTodoScreen: ViewControllerComponentContainer, AttachmentCont
maxTodoItemLength: Int, maxTodoItemLength: Int,
maxTodoItemsCount: Int, maxTodoItemsCount: Int,
existingTodo: TelegramMediaTodo?, existingTodo: TelegramMediaTodo?,
focusedId: Int32?,
append: Bool, append: Bool,
canEdit: Bool canEdit: Bool
) { ) {
@ -1542,6 +1553,7 @@ public class ComposeTodoScreen: ViewControllerComponentContainer, AttachmentCont
self.maxTodoItemLength = maxTodoItemLength self.maxTodoItemLength = maxTodoItemLength
self.maxTodoItemsCount = maxTodoItemsCount self.maxTodoItemsCount = maxTodoItemsCount
self.existingTodo = existingTodo self.existingTodo = existingTodo
self.focusedId = focusedId
self.append = append self.append = append
self.canEdit = canEdit self.canEdit = canEdit
} }
@ -1639,7 +1651,7 @@ public class ComposeTodoScreen: ViewControllerComponentContainer, AttachmentCont
deinit { deinit {
} }
public static func initialData(context: AccountContext, existingTodo: TelegramMediaTodo? = nil, append: Bool = false, canEdit: Bool = false) -> InitialData { public static func initialData(context: AccountContext, existingTodo: TelegramMediaTodo? = nil, focusedId: Int32? = nil, append: Bool = false, canEdit: Bool = false) -> InitialData {
var maxTodoTextLength: Int = 32 var maxTodoTextLength: Int = 32
var maxTodoItemLength: Int = 64 var maxTodoItemLength: Int = 64
var maxTodoItemsCount: Int = 30 var maxTodoItemsCount: Int = 30
@ -1659,6 +1671,7 @@ public class ComposeTodoScreen: ViewControllerComponentContainer, AttachmentCont
maxTodoItemLength: maxTodoItemLength, maxTodoItemLength: maxTodoItemLength,
maxTodoItemsCount: maxTodoItemsCount, maxTodoItemsCount: maxTodoItemsCount,
existingTodo: existingTodo, existingTodo: existingTodo,
focusedId: focusedId,
append: append, append: append,
canEdit: canEdit canEdit: canEdit
) )

View File

@ -437,7 +437,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
}, openBoostToUnrestrict: { }, openBoostToUnrestrict: {
}, updateRecordingTrimRange: { _, _, _, _ in }, updateRecordingTrimRange: { _, _, _, _ in
}, dismissAllTooltips: { }, dismissAllTooltips: {
}, editTodoMessage: { _, _ in }, editTodoMessage: { _, _, _ in
}, updateHistoryFilter: { _ in }, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _, _ in }, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: { }, toggleChatSidebarMode: {
@ -3788,6 +3788,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
default: default:
break break
} }
}, todoItemLongTap: { _, _ in
}, openCheckoutOrReceipt: { _, _ in }, openCheckoutOrReceipt: { _, _ in
}, openSearch: { }, openSearch: {
}, setupReply: { _ in }, setupReply: { _ in

View File

@ -825,7 +825,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
}, openBoostToUnrestrict: { }, openBoostToUnrestrict: {
}, updateRecordingTrimRange: { _, _, _, _ in }, updateRecordingTrimRange: { _, _, _, _ in
}, dismissAllTooltips: { }, dismissAllTooltips: {
}, editTodoMessage: { _, _ in }, editTodoMessage: { _, _, _ in
}, updateHistoryFilter: { _ in }, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _, _ in }, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: { }, toggleChatSidebarMode: {

View File

@ -4173,11 +4173,11 @@ extension ChatControllerImpl {
return return
} }
self.dismissAllTooltips() self.dismissAllTooltips()
}, editTodoMessage: { [weak self] messageId, append in }, editTodoMessage: { [weak self] messageId, itemId, append in
guard let self else { guard let self else {
return return
} }
self.openTodoEditing(messageId: messageId, append: append) self.openTodoEditing(messageId: messageId, itemId: itemId, append: append)
}, updateHistoryFilter: { [weak self] update in }, updateHistoryFilter: { [weak self] update in
guard let self else { guard let self else {
return return

View File

@ -0,0 +1,192 @@
import Foundation
import UIKit
import SwiftSignalKit
import Postbox
import TelegramCore
import AsyncDisplayKit
import Display
import ContextUI
import UndoUI
import AccountContext
import ChatMessageItemView
import ChatMessageItemCommon
import AvatarNode
import ChatControllerInteraction
import Pasteboard
import TelegramStringFormatting
import TelegramPresentationData
extension ChatControllerImpl {
func openTodoItemContextMenu(todoItemId: Int32, params: ChatControllerInteraction.LongTapParams) -> Void {
guard let message = params.message, let todo = message.media.first(where: { $0 is TelegramMediaTodo }) as? TelegramMediaTodo, let todoItem = todo.items.first(where: { $0.id == todoItemId }), let contentNode = params.contentNode else {
return
}
let completion = todo.completions.first(where: { $0.id == todoItemId })
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer
let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture
let source: ContextContentSource
// if let location = location {
// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y)))
// } else {
source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
// }
var canMark = false
if (todo.flags.contains(.othersCanComplete) || message.author?.id == context.account.peerId) {
canMark = true
}
let canEdit = canEditMessage(context: self.context, limitsConfiguration: self.context.currentLimitsConfiguration.with { EngineConfiguration.Limits($0) }, message: message)
var items: [ContextMenuItem] = []
if let completion {
let dateText = humanReadableStringForTimestamp(strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, timestamp: completion.date, alwaysShowTime: true, allowYesterday: true, format: HumanReadableStringFormat(
dateFormatString: { value in
return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_TodoItemCompletionTimestamp_Date(value).string, ranges: [])
},
tomorrowFormatString: { value in
return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_TodoItemCompletionTimestamp_TodayAt(value).string, ranges: [])
},
todayFormatString: { value in
return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_TodoItemCompletionTimestamp_TodayAt(value).string, ranges: [])
},
yesterdayFormatString: { value in
return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_TodoItemCompletionTimestamp_YesterdayAt(value).string, ranges: [])
}
)).string
let nop: ((ContextMenuActionItem.Action) -> Void)? = nil
items.append(.action(ContextMenuActionItem(text: dateText, textFont: .small, icon: { _ in return nil }, action: nop)))
items.append(.separator)
if canMark {
items.append(.action(ContextMenuActionItem(text: "Uncheck", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
guard let self else {
return
}
let _ = self.context.engine.messages.requestUpdateTodoMessageItems(messageId: message.id, completedIds: [], incompletedIds: [todoItemId]).start()
})))
}
} else {
if canMark {
items.append(.action(ContextMenuActionItem(text: "Check", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
guard let self else {
return
}
let _ = self.context.engine.messages.requestUpdateTodoMessageItems(messageId: message.id, completedIds: [todoItemId], incompletedIds: []).start()
})))
}
}
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Copy", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
guard let self else {
return
}
storeMessageTextInPasteboard(todoItem.text, entities: todoItem.entities)
self.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
})))
var isReplyThreadHead = false
if case let .replyThread(replyThreadMessage) = self.presentationInterfaceState.chatLocation {
isReplyThreadHead = message.id == replyThreadMessage.effectiveTopId
}
if message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, !channel.isMonoForum, !isReplyThreadHead {
items.append(.action(ContextMenuActionItem(text: "Copy Link", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
guard let self else {
return
}
var threadMessageId: MessageId?
if case let .replyThread(replyThreadMessage) = self.presentationInterfaceState.chatLocation {
threadMessageId = replyThreadMessage.effectiveMessageId
}
let _ = (self.context.engine.messages.exportMessageLink(peerId: message.id.peerId, messageId: message.id, isThread: threadMessageId != nil)
|> map { result -> String? in
return result
}
|> deliverOnMainQueue).startStandalone(next: { [weak self] link in
guard let self, let link else {
return
}
UIPasteboard.general.string = link + "?task=\(todoItemId)"
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
var warnAboutPrivate = false
if case .peer = self.presentationInterfaceState.chatLocation {
if channel.addressName == nil {
warnAboutPrivate = true
}
}
Queue.mainQueue().after(0.2, {
if warnAboutPrivate {
self.controllerInteraction?.displayUndo(.linkCopied(title: nil, text: presentationData.strings.Conversation_PrivateMessageLinkCopiedLong))
} else {
self.controllerInteraction?.displayUndo(.linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied))
}
})
})
f(.default)
})))
}
if canEdit {
items.append(.action(ContextMenuActionItem(text: "Edit Item", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
guard let self else {
return
}
self.interfaceInteraction?.editTodoMessage(message.id, todoItemId, false)
})))
if todo.items.count > 1 {
items.append(.separator)
items.append(.action(ContextMenuActionItem(text: "Delete Item", textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] _, f in
f(.default)
guard let self else {
return
}
let updatedItems = todo.items.filter { $0.id != todoItemId }
let updatedTodo = todo.withUpdated(items: updatedItems)
let _ = self.context.engine.messages.requestEditMessage(
messageId: message.id,
text: "",
media: .update(.standalone(media: updatedTodo)),
entities: nil,
inlineStickers: [:]
).start()
})))
}
}
self.canReadHistory.set(false)
let controller = ContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(items))), recognizer: recognizer, gesture: gesture, disableScreenshots: false)
controller.dismissed = { [weak self] in
self?.canReadHistory.set(true)
}
self.window?.presentInGlobalOverlay(controller)
}
}

View File

@ -15,23 +15,10 @@ import ChatControllerInteraction
extension ChatControllerImpl { extension ChatControllerImpl {
func openMentionContextMenu(username: String, peerId: EnginePeer.Id?, params: ChatControllerInteraction.LongTapParams) -> Void { func openMentionContextMenu(username: String, peerId: EnginePeer.Id?, params: ChatControllerInteraction.LongTapParams) -> Void {
guard let message = params.message, let contentNode = params.contentNode else { guard let _ = params.message, let contentNode = params.contentNode else {
return return
} }
guard let messages = self.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(message.id) else {
return
}
var updatedMessages = messages
for i in 0 ..< updatedMessages.count {
if updatedMessages[i].id == message.id {
let message = updatedMessages.remove(at: i)
updatedMessages.insert(message, at: 0)
break
}
}
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer
let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture

View File

@ -2891,12 +2891,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let self else { guard let self else {
return return
} }
self.joinConferenceCall(message: EngineMessage(message)) self.joinConferenceCall(message: EngineMessage(message))
}, longTap: { [weak self] action, params in }, longTap: { [weak self] action, params in
if let self { guard let self else {
self.openLinkLongTap(action, params: params) return
} }
self.openLinkLongTap(action, params: params)
}, todoItemLongTap: { [weak self] todoItemId, params in
guard let self, let params else {
return
}
self.openTodoItemContextMenu(todoItemId: todoItemId, params: params)
}, openCheckoutOrReceipt: { [weak self] messageId, params in }, openCheckoutOrReceipt: { [weak self] messageId, params in
guard let strongSelf = self else { guard let strongSelf = self else {
return return

View File

@ -2094,7 +2094,7 @@ extension ChatControllerImpl {
) )
} }
func openTodoEditing(messageId: EngineMessage.Id, append: Bool) { func openTodoEditing(messageId: EngineMessage.Id, itemId: Int32?, append: Bool) {
guard let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId), let peer = self.presentationInterfaceState.renderedPeer?.peer else { guard let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId), let peer = self.presentationInterfaceState.renderedPeer?.peer else {
return return
} }
@ -2109,6 +2109,7 @@ extension ChatControllerImpl {
initialData: ComposeTodoScreen.initialData( initialData: ComposeTodoScreen.initialData(
context: self.context, context: self.context,
existingTodo: existingTodo, existingTodo: existingTodo,
focusedId: itemId,
append: append, append: append,
canEdit: canEdit canEdit: canEdit
), ),

View File

@ -1496,7 +1496,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.actionSheet.primaryTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.actionSheet.primaryTextColor)
}, action: { c, f in }, action: { c, f in
if let _ = activeTodo { if let _ = activeTodo {
interfaceInteraction.editTodoMessage(messages[0].id, false) interfaceInteraction.editTodoMessage(messages[0].id, nil, false)
f(.dismissWithoutContent) f(.dismissWithoutContent)
} else { } else {
interfaceInteraction.setupEditMessage(messages[0].id, { transition in interfaceInteraction.setupEditMessage(messages[0].id, { transition in
@ -1532,7 +1532,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
actions.append(.action(ContextMenuActionItem(text: "Add a Task", icon: { theme in actions.append(.action(ContextMenuActionItem(text: "Add a Task", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddCircle"), color: theme.actionSheet.primaryTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddCircle"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in }, action: { _, f in
interfaceInteraction.editTodoMessage(messages[0].id, true) interfaceInteraction.editTodoMessage(messages[0].id, nil, true)
f(.dismissWithoutContent) f(.dismissWithoutContent)
}))) })))
} }

View File

@ -119,6 +119,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
}, callPeer: { _, _ in }, callPeer: { _, _ in
}, openConferenceCall: { _ in }, openConferenceCall: { _ in
}, longTap: { _, _ in }, longTap: { _, _ in
}, todoItemLongTap: { _, _ in
}, openCheckoutOrReceipt: { _, _ in }, openCheckoutOrReceipt: { _, _ in
}, openSearch: { }, openSearch: {
}, setupReply: { _ in }, setupReply: { _ in

View File

@ -2327,7 +2327,11 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}, chatControllerNode: { }, chatControllerNode: {
return nil return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, openConferenceCall: { _ in }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, openConferenceCall: { _ in
}, longTap: { _, _ in }, openCheckoutOrReceipt: { _, _ in }, openSearch: { }, setupReply: { _ in }, longTap: { _, _ in
}, todoItemLongTap: { _, _ in
}, openCheckoutOrReceipt: { _, _ in
}, openSearch: {
}, setupReply: { _ in
}, canSetupReply: { _ in }, canSetupReply: { _ in
return .none return .none
}, canSendMessages: { }, canSendMessages: {