Merge commit '5d25b32ac4f63a08b06eaa65bb4f8153f7c322fd'

# Conflicts:
#	Telegram/Telegram-iOS/en.lproj/Localizable.strings
This commit is contained in:
Ali 2022-11-22 19:52:35 +04:00
commit 9243444129
26 changed files with 499 additions and 172 deletions

View File

@ -8295,10 +8295,10 @@ Sorry for the inconvenience.";
"Notification.ForumTopicHidden" = "Topic hidden";
"Notification.ForumTopicUnhidden" = "Topic unhidden";
"Notification.ForumTopicHiddenAuthor" = "%1$@ hidden topic";
"Notification.ForumTopicUnhiddenAuthor" = "%1$@ unhidden topic";
"Notification.OverviewTopicHidden" = "%1$@ hidden %2$@ %3$@";
"Notification.OverviewTopicUnhidden" = "%1$@ unhidden %2$@ %3$@";
"Notification.ForumTopicHiddenAuthor" = "%1$@ hid topic";
"Notification.ForumTopicUnhiddenAuthor" = "%1$@ unhid topic";
"Notification.OverviewTopicHidden" = "%1$@ hid %2$@ %3$@";
"Notification.OverviewTopicUnhidden" = "%1$@ unhid %2$@ %3$@";
"CreateTopic.ShowGeneral" = "Show in Topics";
"CreateTopic.ShowGeneralInfo" = "If the 'General' topic is hidden, group members can pull down in the topic list to view it.";
@ -8309,3 +8309,6 @@ Sorry for the inconvenience.";
"ChatList.DeletedThreads_any" = "Deleted %@ threads";
"DialogList.SearchSectionMessagesIn" = "Messages in %@";
"Conversation.ContextMenuReportFalsePositive" = "Report False Positive";
"Group.AdminLog.AntiSpamFalsePositiveReportedText" = "Telegram moderators will review your report. Thank you!";

View File

@ -925,18 +925,20 @@ public struct PremiumConfiguration {
public struct AntiSpamBotConfiguration {
public static var defaultValue: AntiSpamBotConfiguration {
return AntiSpamBotConfiguration(antiSpamBotId: nil)
return AntiSpamBotConfiguration(antiSpamBotId: nil, minimumGroupParticipants: 100)
}
public let antiSpamBotId: EnginePeer.Id?
public let minimumGroupParticipants: Int32
fileprivate init(antiSpamBotId: EnginePeer.Id?) {
fileprivate init(antiSpamBotId: EnginePeer.Id?, minimumGroupParticipants: Int32) {
self.antiSpamBotId = antiSpamBotId
self.minimumGroupParticipants = minimumGroupParticipants
}
public static func with(appConfiguration: AppConfiguration) -> AntiSpamBotConfiguration {
if let data = appConfiguration.data, let string = data["telegram_antispam_user_id"] as? String, let value = Int64(string) {
return AntiSpamBotConfiguration(antiSpamBotId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(value)))
if let data = appConfiguration.data, let botIdString = data["telegram_antispam_user_id"] as? String, let botIdValue = Int64(botIdString), let groupSize = data["telegram_antispam_group_size_min"] as? Double {
return AntiSpamBotConfiguration(antiSpamBotId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(botIdValue)), minimumGroupParticipants: Int32(groupSize))
} else {
return .defaultValue
}

View File

@ -514,14 +514,14 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId:
var items: [ContextMenuItem] = []
if let isClosed = isClosed, isClosed {
if let isClosed = isClosed, isClosed && threadId != 1 {
} else {
if let isPinned = isPinned, channel.hasPermission(.manageTopics) {
items.append(.action(ContextMenuActionItem(text: isPinned ? presentationData.strings.ChatList_Context_Unpin : presentationData.strings.ChatList_Context_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin": "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default)
let _ = (context.engine.peers.toggleForumChannelTopicPinned(id: peerId, threadId: threadId)
|> deliverOnMainQueue).start(error: { error in
|> deliverOnMainQueue).start(error: { error in
switch error {
case let .limitReached(count):
if let chatListController = chatListController {

View File

@ -338,75 +338,75 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
case .medium, .full:
strongSelf.statusNodeContainer.isHidden = true
// Queue.concurrentDefaultQueue().async {
// if let message = strongSelf.message, !message.isCopyProtected() && !imageReference.media.flags.contains(.hasStickers) {
// strongSelf.recognitionDisposable.set((recognizedContent(engine: strongSelf.context.engine, image: { return generate(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))?.generateImage() }, messageId: message.id)
// |> deliverOnMainQueue).start(next: { [weak self] results in
// if let strongSelf = self {
// strongSelf.recognizedContentNode?.removeFromSupernode()
// if !results.isEmpty {
// let size = strongSelf.imageNode.bounds.size
// let recognizedContentNode = RecognizedContentContainer(size: size, recognitions: results, presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, present: { [weak self] c, a in
// if let strongSelf = self {
// strongSelf.galleryController()?.presentInGlobalOverlay(c, with: a)
// }
// }, performAction: { [weak self] string, action in
// guard let strongSelf = self else {
// return
// }
// switch action {
// case .copy:
// UIPasteboard.general.string = string
// if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController {
// let presentationData = strongSelf.context.sharedContext.currentPresentationData.with({ $0 })
// let tooltipController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false })
// controller.present(tooltipController, in: .window(.root))
// }
// case .share:
// if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController {
// let shareController = ShareController(context: strongSelf.context, subject: .text(string), externalShare: true, immediateExternalShare: false, updatedPresentationData: (strongSelf.context.sharedContext.currentPresentationData.with({ $0 }), strongSelf.context.sharedContext.presentationData))
// controller.present(shareController, in: .window(.root))
// }
// case .lookup:
// let controller = UIReferenceLibraryViewController(term: string)
// if let window = strongSelf.baseNavigationController()?.view.window {
// controller.popoverPresentationController?.sourceView = window
// controller.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
// window.rootViewController?.present(controller, animated: true)
// }
// case .speak:
// let _ = speakText(context: strongSelf.context, text: string)
// case .translate:
// if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController {
// let controller = TranslateScreen(context: strongSelf.context, text: string, fromLanguage: nil)
// controller.pushController = { [weak parentController] c in
// (parentController?.navigationController as? NavigationController)?._keepModalDismissProgress = true
// parentController?.push(c)
// }
// controller.presentController = { [weak parentController] c in
// parentController?.present(c, in: .window(.root))
// }
// parentController.present(controller, in: .window(.root))
// }
// }
// })
// recognizedContentNode.barcodeAction = { [weak self] payload, rect in
// guard let strongSelf = self, let message = strongSelf.message else {
// return
// }
// strongSelf.footerContentNode.openActionOptions?(.url(url: payload, concealed: true), message)
// }
// recognizedContentNode.alpha = 0.0
// recognizedContentNode.frame = CGRect(origin: CGPoint(), size: size)
// recognizedContentNode.update(size: strongSelf.imageNode.bounds.size, transition: .immediate)
// strongSelf.imageNode.addSubnode(recognizedContentNode)
// strongSelf.recognizedContentNode = recognizedContentNode
// strongSelf.recognitionOverlayContentNode.transitionIn()
// }
// }
// }))
// }
// }
Queue.concurrentDefaultQueue().async {
if let message = strongSelf.message, !message.isCopyProtected() && !imageReference.media.flags.contains(.hasStickers) {
strongSelf.recognitionDisposable.set((recognizedContent(engine: strongSelf.context.engine, image: { return generate(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))?.generateImage() }, messageId: message.id)
|> deliverOnMainQueue).start(next: { [weak self] results in
if let strongSelf = self {
strongSelf.recognizedContentNode?.removeFromSupernode()
if !results.isEmpty {
let size = strongSelf.imageNode.bounds.size
let recognizedContentNode = RecognizedContentContainer(size: size, recognitions: results, presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, present: { [weak self] c, a in
if let strongSelf = self {
strongSelf.galleryController()?.presentInGlobalOverlay(c, with: a)
}
}, performAction: { [weak self] string, action in
guard let strongSelf = self else {
return
}
switch action {
case .copy:
UIPasteboard.general.string = string
if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with({ $0 })
let tooltipController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false })
controller.present(tooltipController, in: .window(.root))
}
case .share:
if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController {
let shareController = ShareController(context: strongSelf.context, subject: .text(string), externalShare: true, immediateExternalShare: false, updatedPresentationData: (strongSelf.context.sharedContext.currentPresentationData.with({ $0 }), strongSelf.context.sharedContext.presentationData))
controller.present(shareController, in: .window(.root))
}
case .lookup:
let controller = UIReferenceLibraryViewController(term: string)
if let window = strongSelf.baseNavigationController()?.view.window {
controller.popoverPresentationController?.sourceView = window
controller.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
window.rootViewController?.present(controller, animated: true)
}
case .speak:
let _ = speakText(context: strongSelf.context, text: string)
case .translate:
if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController {
let controller = TranslateScreen(context: strongSelf.context, text: string, canCopy: true, fromLanguage: nil)
controller.pushController = { [weak parentController] c in
(parentController?.navigationController as? NavigationController)?._keepModalDismissProgress = true
parentController?.push(c)
}
controller.presentController = { [weak parentController] c in
parentController?.present(c, in: .window(.root))
}
parentController.present(controller, in: .window(.root))
}
}
})
recognizedContentNode.barcodeAction = { [weak self] payload, rect in
guard let strongSelf = self, let message = strongSelf.message else {
return
}
strongSelf.footerContentNode.openActionOptions?(.url(url: payload, concealed: true), message)
}
recognizedContentNode.alpha = 0.0
recognizedContentNode.frame = CGRect(origin: CGPoint(), size: size)
recognizedContentNode.update(size: strongSelf.imageNode.bounds.size, transition: .immediate)
strongSelf.imageNode.addSubnode(recognizedContentNode)
strongSelf.recognizedContentNode = recognizedContentNode
strongSelf.recognitionOverlayContentNode.transitionIn()
}
}
}))
}
}
case .none, .blurred:
strongSelf.statusNodeContainer.isHidden = false
@ -1353,6 +1353,9 @@ private class ImageRecognitionOverlayContentNode: GalleryOverlayContentNode {
var action: ((Bool) -> Void)?
private var appeared = false
private var validLayout: (CGSize, LayoutMetrics, UIEdgeInsets)?
private var interfaceIsHidden: Bool = false
init(theme: PresentationTheme) {
self.backgroundNode = ASImageNode()
self.backgroundNode.displaysAsynchronously = false
@ -1377,13 +1380,25 @@ private class ImageRecognitionOverlayContentNode: GalleryOverlayContentNode {
self.selectedIconNode.isHidden = true
super.init()
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.addSubnode(self.buttonNode)
self.buttonNode.addSubnode(self.backgroundNode)
self.buttonNode.addSubnode(self.selectedBackgroundNode)
self.buttonNode.addSubnode(self.iconNode)
self.buttonNode.addSubnode(self.selectedIconNode)
self.buttonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.iconNode.layer.removeAnimation(forKey: "opacity")
strongSelf.iconNode.alpha = 0.4
} else {
strongSelf.iconNode.alpha = 1.0
strongSelf.iconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
}
@objc private func buttonPressed() {
@ -1392,6 +1407,10 @@ private class ImageRecognitionOverlayContentNode: GalleryOverlayContentNode {
self.selectedBackgroundNode.isHidden = !newValue
self.selectedIconNode.isHidden = !newValue
if !newValue && !self.interfaceIsHidden, let (size, metrics, insets) = self.validLayout {
self.updateLayout(size: size, metrics: metrics, insets: insets, isHidden: self.interfaceIsHidden, transition: .animated(duration: 0.3, curve: .easeInOut))
}
self.action?(newValue)
if self.interfaceIsHidden && !newValue {
@ -1409,8 +1428,9 @@ private class ImageRecognitionOverlayContentNode: GalleryOverlayContentNode {
self.buttonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
private var interfaceIsHidden: Bool = false
override func updateLayout(size: CGSize, metrics: LayoutMetrics, insets: UIEdgeInsets, isHidden: Bool, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, metrics, insets)
self.interfaceIsHidden = isHidden
let buttonSize = CGSize(width: 32.0, height: 32.0)
@ -1427,7 +1447,14 @@ private class ImageRecognitionOverlayContentNode: GalleryOverlayContentNode {
}
}
transition.updateFrame(node: self.buttonNode, frame: CGRect(x: size.width - insets.right - buttonSize.width - 24.0, y: insets.top - 50.0, width: buttonSize.width + 24.0, height: buttonSize.height + 24.0))
var buttonPosition: CGPoint
if isHidden && !self.buttonNode.isSelected {
buttonPosition = CGPoint(x: size.width - insets.right - buttonSize.width - 59.0, y: -50.0)
} else {
buttonPosition = CGPoint(x: size.width - insets.right - buttonSize.width - (self.buttonNode.isSelected ? 24.0 : 59.0), y: insets.top - 50.0)
}
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: buttonPosition, size: CGSize(width: buttonSize.width + 24.0, height: buttonSize.height + 24.0)))
}
override func animateIn(previousContentNode: GalleryOverlayContentNode?, transition: ContainedViewLayoutTransition) {

View File

@ -338,7 +338,7 @@ private struct ChannelAdminsControllerState: Equatable {
}
}
private func channelAdminsControllerEntries(presentationData: PresentationData, accountPeerId: PeerId, view: PeerView, state: ChannelAdminsControllerState, participants: [RenderedChannelParticipant]?, antiSpamEnabled: Bool) -> [ChannelAdminsEntry] {
private func channelAdminsControllerEntries(presentationData: PresentationData, accountPeerId: PeerId, view: PeerView, state: ChannelAdminsControllerState, participants: [RenderedChannelParticipant]?, antiSpamAvailable: Bool, antiSpamEnabled: Bool) -> [ChannelAdminsEntry] {
if participants == nil || participants?.count == nil {
return []
}
@ -351,7 +351,7 @@ private func channelAdminsControllerEntries(presentationData: PresentationData,
}
entries.append(.recentActions(presentationData.theme, presentationData.strings.Group_Info_AdminLog))
if isGroup && peer.hasPermission(.deleteAllMessages) {
if isGroup && peer.hasPermission(.deleteAllMessages) && antiSpamAvailable {
entries.append(.antiSpam(presentationData.theme, presentationData.strings.Group_Management_AntiSpam, antiSpamEnabled))
entries.append(.antiSpamInfo(presentationData.theme, presentationData.strings.Group_Management_AntiSpamInfo))
}
@ -539,10 +539,10 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
let adminsPromise = Promise<[RenderedChannelParticipant]?>(nil)
let antiSpamBotConfiguration = AntiSpamBotConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
let antiSpamConfiguration = AntiSpamBotConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
let resolveAntiSpamPeerDisposable = MetaDisposable()
if let antiSpamBotId = antiSpamBotConfiguration.antiSpamBotId {
if let antiSpamBotId = antiSpamConfiguration.antiSpamBotId {
resolveAntiSpamPeerDisposable.set(
(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: antiSpamBotId))
|> mapToSignal { peer -> Signal<Never, NoError> in
@ -786,6 +786,11 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
|> map { presentationData, state, view, admins, antiSpamEnabled -> (ItemListControllerState, (ItemListNodeState, Any)) in
let peerId = view.peerId
var antiSpamAvailable = false
if let cachedData = view.cachedData as? CachedChannelData, let memberCount = cachedData.participantsSummary.memberCount, memberCount >= antiSpamConfiguration.minimumGroupParticipants {
antiSpamAvailable = true
}
var rightNavigationButton: ItemListNavigationButton?
var secondaryRightNavigationButton: ItemListNavigationButton?
if let admins = admins, admins.count > 1 {
@ -857,7 +862,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(isGroup ? presentationData.strings.ChatAdmins_Title : presentationData.strings.Channel_Management_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, secondaryRightNavigationButton: secondaryRightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelAdminsControllerEntries(presentationData: presentationData, accountPeerId: context.account.peerId, view: view, state: state, participants: admins, antiSpamEnabled: antiSpamEnabled), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: previous != nil && admins != nil && previous!.count >= admins!.count)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelAdminsControllerEntries(presentationData: presentationData, accountPeerId: context.account.peerId, view: view, state: state, participants: admins, antiSpamAvailable: antiSpamAvailable, antiSpamEnabled: antiSpamEnabled), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: previous != nil && admins != nil && previous!.count >= admins!.count)
return (controllerState, (listState, arguments))
} |> afterDisposed {

View File

@ -340,13 +340,7 @@ func _internal_setForumChannelTopicClosed(account: Account, id: EnginePeer.Id, t
}
var flags: Int32 = 0
flags |= (1 << 2)
var hidden: Api.Bool? = nil
if threadId == 1, !isClosed {
flags |= (1 << 3)
hidden = .boolFalse
}
return account.network.request(Api.functions.channels.editForumTopic(
flags: flags,
channel: inputChannel,
@ -354,7 +348,7 @@ func _internal_setForumChannelTopicClosed(account: Account, id: EnginePeer.Id, t
title: nil,
iconEmojiId: nil,
closed: isClosed ? .boolTrue : .boolFalse,
hidden: hidden
hidden: nil
))
|> mapError { _ -> EditForumChannelTopicError in
return .generic
@ -367,7 +361,7 @@ func _internal_setForumChannelTopicClosed(account: Account, id: EnginePeer.Id, t
var data = initialData
data.isClosed = isClosed
if let _ = hidden {
if !isClosed && threadId == 1 {
data.isHidden = false
}

View File

@ -3,6 +3,7 @@ import SwiftSignalKit
public struct ChannelAdminEventLogEntry: Comparable {
public let stableId: UInt32
public let headerStableId: UInt32
public let event: AdminLogEvent
public let peers: [PeerId: Peer]
@ -85,6 +86,7 @@ public final class ChannelAdminEventLogContext {
private var filter: ChannelAdminEventLogFilter = ChannelAdminEventLogFilter()
private var nextStableId: UInt32 = 1
private var headerStableIds: [AdminLogEventId: UInt32] = [:]
private var stableIds: [AdminLogEventId: UInt32] = [:]
private var entries: ([ChannelAdminEventLogEntry], ChannelAdminEventLogFilter) = ([], ChannelAdminEventLogFilter())
@ -182,13 +184,13 @@ public final class ChannelAdminEventLogContext {
}
var entries: [ChannelAdminEventLogEntry] = events.map { event in
return ChannelAdminEventLogEntry(stableId: strongSelf.stableIdForEventId(event.id), event: event, peers: result.peers)
return ChannelAdminEventLogEntry(stableId: strongSelf.stableIdForEventId(event.id), headerStableId: strongSelf.headerStableIdForEventId(event.id), event: event, peers: result.peers)
}
entries.append(contentsOf: strongSelf.entries.0)
strongSelf.entries = (entries, strongSelf.filter)
} else {
let entries: [ChannelAdminEventLogEntry] = events.map { event in
return ChannelAdminEventLogEntry(stableId: strongSelf.stableIdForEventId(event.id), event: event, peers: result.peers)
return ChannelAdminEventLogEntry(stableId: strongSelf.stableIdForEventId(event.id), headerStableId: strongSelf.headerStableIdForEventId(event.id), event: event, peers: result.peers)
}
strongSelf.entries = (entries, strongSelf.filter)
}
@ -214,4 +216,15 @@ public final class ChannelAdminEventLogContext {
return value
}
}
private func headerStableIdForEventId(_ id: AdminLogEventId) -> UInt32 {
if let value = self.headerStableIds[id] {
return value
} else {
let value = self.nextStableId
self.nextStableId += 1
self.headerStableIds[id] = value
return value
}
}
}

View File

@ -292,6 +292,10 @@ public extension TelegramEngine {
return _internal_toggleAntiSpamProtection(account: self.account, peerId: peerId, enabled: enabled)
}
public func reportAntiSpamFalsePositive(peerId: PeerId, messageId: MessageId) -> Signal<Bool, NoError> {
return _internal_reportAntiSpamFalsePositive(account: self.account, peerId: peerId, messageId: messageId)
}
public func requestPeerPhotos(peerId: PeerId) -> Signal<[TelegramPeerPhoto], NoError> {
return _internal_requestPeerPhotos(postbox: self.account.postbox, network: self.account.network, peerId: peerId)
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "reportfalse_24.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,135 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 4.834961 3.335003 cm
0.000000 0.000000 0.000000 scn
5.861066 15.587957 m
6.196899 15.711591 6.498980 15.822798 6.742460 15.912901 c
6.938185 15.972766 7.061687 15.999998 7.169050 15.999998 c
7.276096 15.999998 7.389237 15.973757 7.604651 15.910002 c
8.743544 15.512181 11.235205 14.579237 12.364463 14.129102 c
12.722498 13.984460 12.842218 13.892902 12.896966 13.823884 c
12.934976 13.775966 13.000000 13.665576 13.000000 13.307275 c
13.000000 7.185324 l
13.000000 6.553703 12.915850 6.062992 12.747086 5.640941 c
12.579326 5.221397 12.313388 4.833261 11.900326 4.426327 c
11.044427 3.583125 9.640606 2.731597 7.372818 1.412622 c
7.318006 1.381764 7.263755 1.358576 7.218547 1.344114 c
7.193927 1.336239 7.177481 1.332685 7.169050 1.331110 c
7.160619 1.332685 7.144172 1.336239 7.119552 1.344114 c
7.074736 1.358451 7.021033 1.381364 6.966707 1.411822 c
4.705894 2.743835 3.299533 3.594983 2.437976 4.437263 c
2.022268 4.843668 1.754397 5.229964 1.585310 5.647222 c
1.415345 6.066645 1.330000 6.554857 1.330000 7.185324 c
1.330000 13.307275 l
1.330000 13.662965 1.395993 13.779277 1.436774 13.830870 c
1.491529 13.900139 1.610740 13.992070 1.957460 14.125925 c
1.957469 14.125902 l
1.962718 14.127980 l
2.859330 14.482901 4.598611 15.123198 5.860996 15.587932 c
5.861066 15.587957 l
h
7.169050 17.329998 m
6.842114 17.329998 6.548457 17.244995 6.332937 17.178432 c
6.321291 17.174837 6.309747 17.170919 6.298316 17.166689 c
6.058186 17.077799 5.755209 16.966265 5.415772 16.841309 c
5.415587 16.841240 l
4.154459 16.376986 2.390144 15.727495 1.475762 15.365632 c
1.086233 15.215069 0.680177 15.018452 0.393381 14.655631 c
0.091951 14.274296 0.000000 13.813374 0.000000 13.307275 c
0.000000 7.185324 l
0.000000 6.433676 0.101960 5.766401 0.352673 5.147715 c
0.604264 4.526862 0.990442 3.992426 1.508224 3.486230 c
2.512969 2.503967 4.089810 1.563107 6.296909 0.262775 c
6.296864 0.262699 l
6.306323 0.257332 l
6.547542 0.120480 6.865578 0.000000 7.169050 0.000000 c
7.472521 0.000000 7.790557 0.120480 8.031776 0.257332 c
8.031796 0.257298 l
8.037958 0.260883 l
10.255331 1.550500 11.831186 2.491209 12.833723 3.478873 c
13.350101 3.987590 13.733150 4.524757 13.982018 5.147135 c
14.229882 5.767005 14.330000 6.434830 14.330000 7.185324 c
14.330000 13.307275 l
14.330000 13.810762 14.241129 14.269478 13.938952 14.650421 c
13.653583 15.010178 13.247056 15.207094 12.860974 15.362955 c
12.858298 15.364022 l
11.707717 15.822705 9.179629 16.769213 8.027948 17.170942 c
8.018206 17.174341 8.008386 17.177513 7.998495 17.180454 c
7.995981 17.181202 l
7.783853 17.244291 7.495684 17.329998 7.169050 17.329998 c
h
7.165000 12.829998 m
7.532269 12.829998 7.830000 12.532268 7.830000 12.164998 c
7.830000 7.664998 l
7.830000 7.297729 7.532269 6.999998 7.165000 6.999998 c
6.797730 6.999998 6.500000 7.297729 6.500000 7.664998 c
6.500000 12.164998 l
6.500000 12.532268 6.797730 12.829998 7.165000 12.829998 c
h
8.165000 5.164998 m
8.165000 4.612713 7.717285 4.164998 7.165000 4.164998 c
6.612715 4.164998 6.165000 4.612713 6.165000 5.164998 c
6.165000 5.717283 6.612715 6.164998 7.165000 6.164998 c
7.717285 6.164998 8.165000 5.717283 8.165000 5.164998 c
h
f*
n
Q
endstream
endobj
3 0 obj
3222
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000003312 00000 n
0000003335 00000 n
0000003508 00000 n
0000003582 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
3641
%%EOF

View File

@ -457,7 +457,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
let _ = (item.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
if let peer = peer {
self?.item?.controllerInteraction.openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil), nil, false)
self?.item?.controllerInteraction.openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
}
})
}

View File

@ -409,7 +409,7 @@ final class ChatButtonKeyboardInputNode: ChatInputNode {
peer = botPeer
}
if let peer = peer, let botPeer = botPeer, let addressName = botPeer.addressName {
self.controllerInteraction.openPeer(EnginePeer(peer), .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: "@\(addressName) \(query)")), subject: nil, peekData: nil), nil, false)
self.controllerInteraction.openPeer(EnginePeer(peer), .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: "@\(addressName) \(query)")), subject: nil, peekData: nil), nil, .default)
}
}
case .payment:
@ -426,7 +426,7 @@ final class ChatButtonKeyboardInputNode: ChatInputNode {
guard let self, let peer else {
return
}
self.controllerInteraction.openPeer(peer, .info, nil, false)
self.controllerInteraction.openPeer(peer, .info, nil, .default)
})
case let .openWebView(url, simple):
self.controllerInteraction.openWebView(markupButton.title, url, simple, false)

View File

@ -926,7 +926,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}, openPeer: { [weak self] peer in
if let strongSelf = self {
strongSelf.controllerInteraction?.openPeer(peer, .default, nil, false)
strongSelf.controllerInteraction?.openPeer(peer, .default, nil, .default)
}
}, openHashtag: { [weak self] peerName, hashtag in
if let strongSelf = self {
@ -995,8 +995,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
})
})))
}, openPeer: { [weak self] peer, navigation, fromMessage, isReaction in
self?.openPeer(peer: peer, navigation: navigation, fromMessage: fromMessage, fromReactionMessageId: isReaction ? fromMessage?.id : nil)
}, openPeer: { [weak self] peer, navigation, fromMessage, source in
self?.openPeer(peer: peer, navigation: navigation, fromMessage: fromMessage, fromReactionMessageId: source == .reaction ? fromMessage?.id : nil, expandAvatar: source == .groupParticipant)
}, openPeerMention: { [weak self] name in
self?.openPeerMention(name)
}, openMessageContextMenu: { [weak self] message, selectAll, node, frame, anyRecognizer, location in
@ -13224,7 +13224,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
if let strongSelf = self, let peer = peer {
strongSelf.controllerInteraction?.openPeer(peer, .default, nil, false)
strongSelf.controllerInteraction?.openPeer(peer, .default, nil, .default)
}
})
case .longTap:
@ -13317,7 +13317,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
if let strongSelf = self, let peer = peer {
strongSelf.controllerInteraction?.openPeer(peer, .default, nil, false)
strongSelf.controllerInteraction?.openPeer(peer, .default, nil, .default)
}
})
case .longTap:
@ -13431,7 +13431,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
if let strongSelf = self, let peer = peer {
strongSelf.controllerInteraction?.openPeer(peer, .default, nil, false)
strongSelf.controllerInteraction?.openPeer(peer, .default, nil, .default)
}
})
case .longTap:

View File

@ -60,8 +60,14 @@ struct UnreadMessageRangeKey: Hashable {
}
public final class ChatControllerInteraction {
enum OpenPeerSource {
case `default`
case reaction
case groupParticipant
}
let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool
let openPeer: (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, Bool) -> Void
let openPeer: (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, OpenPeerSource) -> Void
let openPeerMention: (String) -> Void
let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?, CGPoint?) -> Void
let updateMessageReaction: (Message, ChatControllerInteractionReaction) -> Void
@ -170,7 +176,7 @@ public final class ChatControllerInteraction {
init(
openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool,
openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, Bool) -> Void,
openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, OpenPeerSource) -> Void,
openPeerMention: @escaping (String) -> Void,
openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?, CGPoint?) -> Void,
openMessageReactionContextMenu: @escaping (Message, ContextExtractedContentContainingView, ContextGesture?, MessageReaction.Reaction) -> Void,

View File

@ -1689,7 +1689,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
actions.insert(.custom(ChatReadReportContextItem(context: context, message: message, hasReadReports: hasReadReports, stats: readStats, action: { c, f, stats, customReactionEmojiPacks, firstCustomEmojiReaction in
if reactionCount == 0, let stats = stats, stats.peers.count == 1 {
c.dismiss(completion: {
controllerInteraction.openPeer(stats.peers[0], .default, nil, false)
controllerInteraction.openPeer(stats.peers[0], .default, nil, .default)
})
} else if (stats != nil && !stats!.peers.isEmpty) || reactionCount != 0 {
var tip: ContextController.Tip?
@ -1733,7 +1733,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
},
openPeer: { [weak c] peer in
c?.dismiss(completion: {
controllerInteraction.openPeer(peer, .default, MessageReference(message), true)
controllerInteraction.openPeer(peer, .default, MessageReference(message), .reaction)
})
}
)), tip: tip)))

View File

@ -2112,7 +2112,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId)
} else if let peer = forwardInfo.source ?? forwardInfo.author {
item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, false)
item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
} else if let _ = forwardInfo.authorSignature {
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, forwardInfoNode, nil)
}

View File

@ -3567,7 +3567,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} else {
return .optionalAction({
if let peer = item.message.peers[peerId] {
item.controllerInteraction.openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: nil, peekData: nil), nil, false)
item.controllerInteraction.openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
}
})
}
@ -3610,7 +3610,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
}
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId)
} else if let peer = forwardInfo.source ?? forwardInfo.author {
item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, false)
item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
} else if let _ = forwardInfo.authorSignature {
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, forwardInfoNode, nil)
}
@ -3653,7 +3653,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
let _ = (item.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
if let self = self, let item = self.item, let peer = peer {
item.controllerInteraction.openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil), nil, false)
item.controllerInteraction.openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
}
})
}

View File

@ -648,9 +648,9 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
self.controllerInteraction.activateAdAction(adMessageId)
} else {
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
self.controllerInteraction.openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: nil, peekData: nil), self.messageReference, false)
self.controllerInteraction.openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: nil, peekData: nil), self.messageReference, .default)
} else {
self.controllerInteraction.openPeer(EnginePeer(peer), .info, self.messageReference, false)
self.controllerInteraction.openPeer(EnginePeer(peer), .info, self.messageReference, .groupParticipant)
}
}
}

View File

@ -927,7 +927,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
}
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId)
} else if let peer = forwardInfo.source ?? forwardInfo.author {
item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, false)
item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
} else if let _ = forwardInfo.authorSignature {
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, forwardInfoNode, nil)
}

View File

@ -1247,7 +1247,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId)
return
} else if let peer = forwardInfo.source ?? forwardInfo.author {
item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, false)
item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
return
} else if let _ = forwardInfo.authorSignature {
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, forwardInfoNode, nil)

View File

@ -855,7 +855,7 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol
let _ = (item.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
if let peer = peer {
item.controllerInteraction.openPeer(peer, .info, nil, false)
item.controllerInteraction.openPeer(peer, .info, nil, .default)
}
})
case let .openWebView(url, simple):

View File

@ -854,7 +854,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
if let peer = peer {
controllerInteraction.openPeer(peer, .info, nil, false)
controllerInteraction.openPeer(peer, .info, nil, .default)
}
})
case let .openWebView(url, simple):

View File

@ -24,6 +24,8 @@ import UndoUI
import TelegramCallsUI
import WallpaperBackgroundNode
import BotPaymentsUI
import ContextUI
import Pasteboard
private final class ChatRecentActionsListOpaqueState {
let entries: [ChatRecentActionsEntry]
@ -56,13 +58,14 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
private var automaticMediaDownloadSettings: MediaAutoDownloadSettings
private var containerLayout: (ContainerViewLayout, CGFloat)?
private var visibleAreaInset = UIEdgeInsets()
private let backgroundNode: WallpaperBackgroundNode
private let panelBackgroundNode: NavigationBackgroundNode
private let panelSeparatorNode: ASDisplayNode
private let panelButtonNode: HighlightableButtonNode
private let listNode: ListView
fileprivate let listNode: ListView
private let loadingNode: ChatLoadingNode
private let emptyNode: ChatRecentActionsEmptyNode
@ -86,6 +89,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
private var adminsDisposable: Disposable?
private var adminsState: ChannelMemberListState?
private let banDisposables = DisposableDict<PeerId>()
private let reportFalsePositiveDisposables = DisposableDict<MessageId>()
private weak var antiSpamTooltipController: UndoOverlayController?
@ -258,8 +262,10 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}
}, openPeerMention: { [weak self] name in
self?.openPeerMention(name)
}, openMessageContextMenu: { [weak self] message, selectAll, node, frame, _, location in
self?.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame, location: location)
}, openMessageContextMenu: { [weak self] message, selectAll, node, frame, anyRecognizer, location in
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer
let gesture: ContextGesture? = anyRecognizer as? ContextGesture
self?.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame, recognizer: recognizer, gesture: gesture, location: location)
}, openMessageReactionContextMenu: { _, _, _, _ in
}, updateMessageReaction: { _, _ in
}, activateMessagePinch: { _ in
@ -628,6 +634,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
self.resolvePeerByNameDisposable.dispose()
self.adminsDisposable?.dispose()
self.banDisposables.dispose()
self.reportFalsePositiveDisposables.dispose()
}
func updatePresentationData(_ presentationData: PresentationData) {
@ -664,6 +671,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
transition.updateFrame(node: self.panelSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - panelHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
transition.updateFrame(node: self.panelButtonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - panelHeight), size: CGSize(width: layout.size.width, height: intrinsicPanelHeight)))
self.visibleAreaInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: panelHeight, right: 0.0)
transition.updateBounds(node: self.listNode, bounds: CGRect(origin: CGPoint(), size: layout.size))
transition.updatePosition(node: self.listNode, position: CGRect(origin: CGPoint(), size: layout.size).center)
@ -819,15 +828,51 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}))
}
private func openMessageContextMenu(message: Message, selectAll: Bool, node: ASDisplayNode, frame: CGRect, location: CGPoint?) {
var actions: [ContextMenuAction] = []
private func openMessageContextMenu(message: Message, selectAll: Bool, node: ASDisplayNode, frame: CGRect, recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil, gesture: ContextGesture? = nil, location: CGPoint? = nil) {
guard let controller = self.controller else {
return
}
self.dismissAllTooltips()
let context = self.context
let source: ContextContentSource
if let location = location {
source = .location(ChatMessageContextLocationContentSource(controller: controller, location: node.view.convert(node.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y)))
} else {
source = .extracted(ChatRecentActionsMessageContextExtractedContentSource(controllerNode: self, message: message, selectAll: selectAll))
}
var actions: [ContextMenuItem] = []
if !message.text.isEmpty {
actions.append(ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuCopy), action: {
UIPasteboard.general.string = message.text
let content: UndoOverlayContent = .copy(text: self.presentationData.strings.Conversation_TextCopied)
self.presentController(UndoOverlayController(presentationData: self.presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), .current, nil)
}))
actions.append(
.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuCopy, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
if let strongSelf = self {
var messageEntities: [MessageTextEntity]?
var restrictedText: String?
for attribute in message.attributes {
if let attribute = attribute as? TextEntitiesMessageAttribute {
messageEntities = attribute.entities
}
if let attribute = attribute as? RestrictedContentMessageAttribute {
restrictedText = attribute.platformText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) ?? ""
}
}
if let restrictedText = restrictedText {
storeMessageTextInPasteboard(restrictedText, entities: nil)
} else {
storeMessageTextInPasteboard(message.text, entities: messageEntities)
}
Queue.mainQueue().after(0.2, {
let content: UndoOverlayContent = .copy(text: strongSelf.presentationData.strings.Conversation_TextCopied)
strongSelf.presentController(UndoOverlayController(presentationData: strongSelf.presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), .current, nil)
})
}
}))
)
}
if let author = message.author, let adminsState = self.adminsState {
@ -847,42 +892,54 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}
if canBan {
actions.append(ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuBan, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuBan), action: { [weak self] in
if let strongSelf = self {
strongSelf.banDisposables.set((strongSelf.context.engine.peers.fetchChannelParticipant(peerId: strongSelf.peer.id, participantId: author.id)
|> deliverOnMainQueue).start(next: { participant in
if let strongSelf = self {
strongSelf.presentController(channelBannedMemberController(context: strongSelf.context, peerId: strongSelf.peer.id, memberId: author.id, initialParticipant: participant, updated: { _ in }, upgradedToSupergroup: { _, f in f() }), .window(.root), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
}), forKey: author.id)
}
}))
actions.append(
.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuBan, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
if let strongSelf = self {
f(.default)
strongSelf.banDisposables.set((strongSelf.context.engine.peers.fetchChannelParticipant(peerId: strongSelf.peer.id, participantId: author.id)
|> deliverOnMainQueue).start(next: { participant in
if let strongSelf = self {
strongSelf.presentController(channelBannedMemberController(context: strongSelf.context, peerId: strongSelf.peer.id, memberId: author.id, initialParticipant: participant, updated: { _ in }, upgradedToSupergroup: { _, f in f() }), .window(.root), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
}), forKey: author.id)
}
}))
)
}
}
if !actions.isEmpty {
let contextMenuController = ContextMenuController(actions: actions)
self.controllerInteraction.highlightedState = ChatInterfaceHighlightedState(messageStableId: message.stableId)
self.updateItemNodesHighlightedStates(animated: true)
contextMenuController.dismissed = { [weak self] in
if let strongSelf = self {
if strongSelf.controllerInteraction.highlightedState?.messageStableId == message.stableId {
strongSelf.controllerInteraction.highlightedState = nil
strongSelf.updateItemNodesHighlightedStates(animated: true)
}
let configuration = AntiSpamBotConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
for peer in message.peers {
if peer.0 == configuration.antiSpamBotId {
if !actions.isEmpty {
actions.insert(.separator, at: 0)
}
actions.insert(
.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuReportFalsePositive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AntiSpam"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
if let strongSelf = self {
strongSelf.reportFalsePositiveDisposables.set((strongSelf.context.engine.peers.reportAntiSpamFalsePositive(peerId: message.id.peerId, messageId: message.id)
|> deliverOnMainQueue).start(), forKey: message.id)
Queue.mainQueue().after(0.2, {
let content: UndoOverlayContent = .image(image: UIImage(bundleImageName: "Chat/AntiSpamTooltipIcon")!, title: nil, text: strongSelf.presentationData.strings.Group_AdminLog_AntiSpamFalsePositiveReportedText, undo: false)
strongSelf.presentController(UndoOverlayController(presentationData: strongSelf.presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), .current, nil)
})
}
})), at: 0
)
break
}
self.presentController(contextMenuController, .window(.root), ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak node] in
if let strongSelf = self, let node = node {
return (node, frame, strongSelf, strongSelf.bounds)
} else {
return nil
}
}))
}
guard !actions.isEmpty else {
return
}
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(actions))), recognizer: recognizer, gesture: gesture)
controller.window?.presentInGlobalOverlay(contextController)
}
private func updateItemNodesHighlightedStates(animated: Bool) {
@ -1043,4 +1100,70 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
return true
})
}
func frameForVisibleArea() -> CGRect {
let rect = CGRect(origin: CGPoint(x: self.visibleAreaInset.left, y: self.visibleAreaInset.top), size: CGSize(width: self.bounds.size.width - self.visibleAreaInset.left - self.visibleAreaInset.right, height: self.bounds.size.height - self.visibleAreaInset.top - self.visibleAreaInset.bottom))
return rect
}
}
final class ChatRecentActionsMessageContextExtractedContentSource: ContextExtractedContentSource {
let keepInPlace: Bool = false
let ignoreContentTouches: Bool = false
let blurBackground: Bool = true
private weak var controllerNode: ChatRecentActionsControllerNode?
private let message: Message
private let selectAll: Bool
var shouldBeDismissed: Signal<Bool, NoError> {
return .single(false)
}
init(controllerNode: ChatRecentActionsControllerNode, message: Message, selectAll: Bool) {
self.controllerNode = controllerNode
self.message = message
self.selectAll = selectAll
}
func takeView() -> ContextControllerTakeViewInfo? {
guard let controllerNode = self.controllerNode else {
return nil
}
var result: ContextControllerTakeViewInfo?
controllerNode.listNode.forEachItemNode { itemNode in
guard let itemNode = itemNode as? ChatMessageItemView else {
return
}
guard let item = itemNode.item else {
return
}
if item.content.contains(where: { $0.0.stableId == self.message.stableId }), let contentNode = itemNode.getMessageContextSourceNode(stableId: self.selectAll ? nil : self.message.stableId) {
result = ContextControllerTakeViewInfo(containingItem: .node(contentNode), contentAreaInScreenSpace: controllerNode.convert(controllerNode.frameForVisibleArea(), to: nil))
}
}
return result
}
func putBack() -> ContextControllerPutBackViewInfo? {
guard let controllerNode = self.controllerNode else {
return nil
}
var result: ContextControllerPutBackViewInfo?
controllerNode.listNode.forEachItemNode { itemNode in
guard let itemNode = itemNode as? ChatMessageItemView else {
return
}
guard let item = itemNode.item else {
return
}
if item.content.contains(where: { $0.0.stableId == self.message.stableId }) {
result = ContextControllerPutBackViewInfo(contentAreaInScreenSpace: controllerNode.convert(controllerNode.frameForVisibleArea(), to: nil))
}
}
return result
}
}

View File

@ -144,7 +144,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}, to: &text, entities: &entities)
}
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case .content:
let peers = SimpleDictionary<PeerId, Peer>()
@ -182,7 +182,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}, to: &text, entities: &entities)
}
let action: TelegramMediaActionType = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case .content:
var previousAttributes: [MessageAttribute] = []
@ -232,7 +232,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}, to: &text, entities: &entities)
}
let action: TelegramMediaActionType = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case .content:
var previousAttributes: [MessageAttribute] = []
@ -374,7 +374,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}, to: &text, entities: &entities)
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case .content:
if let message = message {
@ -459,7 +459,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case .content:
var peers = SimpleDictionary<PeerId, Peer>()
@ -502,7 +502,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case .content:
var peers = SimpleDictionary<PeerId, Peer>()
@ -526,6 +526,9 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}
}
}
if let peer = self.entry.peers[self.entry.event.peerId] {
peers[peer.id] = peer
}
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
}
@ -1072,7 +1075,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case .content:
var peers = SimpleDictionary<PeerId, Peer>()
@ -1634,7 +1637,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}, to: &text, entities: &entities)
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case .content:
var peers = SimpleDictionary<PeerId, Peer>()

View File

@ -186,7 +186,7 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
}
}
let transaction = preparedTransition(from: self.currentEntries, to: entries, context: self.context, presentationData: presentationData, openPeer: { [weak self] peer in
self?.chatControllerInteraction.openPeer(EnginePeer(peer), .default, nil, false)
self?.chatControllerInteraction.openPeer(EnginePeer(peer), .default, nil, .default)
}, openPeerContextAction: { [weak self] peer, node, gesture in
self?.openPeerContextAction(peer, node, gesture)
})

View File

@ -2666,7 +2666,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
let items: [ContextMenuItem] = [
.action(ContextMenuActionItem(text: presentationData.strings.Conversation_LinkDialogOpen, icon: { _ in nil }, action: { _, f in
f(.dismissWithoutContent)
self?.chatInterfaceInteraction.openPeer(EnginePeer(peer), .default, nil, false)
self?.chatInterfaceInteraction.openPeer(EnginePeer(peer), .default, nil, .default)
}))
]
let contextController = ContextController(account: strongSelf.context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)