mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Add View in Chat for messages in channel stats
This commit is contained in:
parent
e673a3153f
commit
54a57205ee
@ -1,6 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import Display
|
import Display
|
||||||
|
import AsyncDisplayKit
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
@ -14,16 +15,19 @@ import AccountContext
|
|||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import AppBundle
|
import AppBundle
|
||||||
import GraphUI
|
import GraphUI
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
private final class ChannelStatsControllerArguments {
|
private final class ChannelStatsControllerArguments {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let loadDetailedGraph: (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>
|
let loadDetailedGraph: (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>
|
||||||
let openMessageStats: (MessageId) -> Void
|
let openMessageStats: (MessageId) -> Void
|
||||||
|
let contextAction: (MessageId, ASDisplayNode, ContextGesture?) -> Void
|
||||||
|
|
||||||
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openMessage: @escaping (MessageId) -> Void) {
|
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openMessage: @escaping (MessageId) -> Void, contextAction: @escaping (MessageId, ASDisplayNode, ContextGesture?) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.loadDetailedGraph = loadDetailedGraph
|
self.loadDetailedGraph = loadDetailedGraph
|
||||||
self.openMessageStats = openMessage
|
self.openMessageStats = openMessage
|
||||||
|
self.contextAction = contextAction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,6 +334,8 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
case let .post(_, _, _, _, message, interactions):
|
case let .post(_, _, _, _, message, interactions):
|
||||||
return StatsMessageItem(context: arguments.context, presentationData: presentationData, message: message, views: interactions.views, forwards: interactions.forwards, sectionId: self.section, style: .blocks, action: {
|
return StatsMessageItem(context: arguments.context, presentationData: presentationData, message: message, views: interactions.views, forwards: interactions.forwards, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.openMessageStats(message.id)
|
arguments.openMessageStats(message.id)
|
||||||
|
}, contextAction: { node, gesture in
|
||||||
|
arguments.contextAction(message.id, node, gesture)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -407,6 +413,7 @@ private func channelStatsControllerEntries(data: ChannelStats?, messages: [Messa
|
|||||||
|
|
||||||
public func channelStatsController(context: AccountContext, peerId: PeerId, cachedPeerData: CachedPeerData) -> ViewController {
|
public func channelStatsController(context: AccountContext, peerId: PeerId, cachedPeerData: CachedPeerData) -> ViewController {
|
||||||
var openMessageStatsImpl: ((MessageId) -> Void)?
|
var openMessageStatsImpl: ((MessageId) -> Void)?
|
||||||
|
var contextActionImpl: ((MessageId, ASDisplayNode, ContextGesture?) -> Void)?
|
||||||
|
|
||||||
let actionsDisposable = DisposableSet()
|
let actionsDisposable = DisposableSet()
|
||||||
let dataPromise = Promise<ChannelStats?>(nil)
|
let dataPromise = Promise<ChannelStats?>(nil)
|
||||||
@ -440,6 +447,8 @@ public func channelStatsController(context: AccountContext, peerId: PeerId, cach
|
|||||||
return statsContext.loadDetailedGraph(graph, x: x)
|
return statsContext.loadDetailedGraph(graph, x: x)
|
||||||
}, openMessage: { messageId in
|
}, openMessage: { messageId in
|
||||||
openMessageStatsImpl?(messageId)
|
openMessageStatsImpl?(messageId)
|
||||||
|
}, contextAction: { messageId, node, gesture in
|
||||||
|
contextActionImpl?(messageId, node, gesture)
|
||||||
})
|
})
|
||||||
|
|
||||||
let messageView = context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId), index: .upperBound, anchorIndex: .upperBound, count: 100, fixedCombinedReadStates: nil)
|
let messageView = context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId), index: .upperBound, anchorIndex: .upperBound, count: 100, fixedCombinedReadStates: nil)
|
||||||
@ -496,9 +505,49 @@ public func channelStatsController(context: AccountContext, peerId: PeerId, cach
|
|||||||
controller?.clearItemNodesHighlight(animated: true)
|
controller?.clearItemNodesHighlight(animated: true)
|
||||||
}
|
}
|
||||||
openMessageStatsImpl = { [weak controller] messageId in
|
openMessageStatsImpl = { [weak controller] messageId in
|
||||||
if let navigationController = controller?.navigationController as? NavigationController {
|
controller?.push(messageStatsController(context: context, messageId: messageId, cachedPeerData: cachedPeerData))
|
||||||
controller?.push(messageStatsController(context: context, messageId: messageId, cachedPeerData: cachedPeerData))
|
}
|
||||||
|
contextActionImpl = { [weak controller] messageId, sourceNode, gesture in
|
||||||
|
guard let controller = controller, let sourceNode = sourceNode as? ContextExtractedContentContainingNode else {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
var items: [ContextMenuItem] = []
|
||||||
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak controller] c, _ in
|
||||||
|
c.dismiss(completion: {
|
||||||
|
if let navigationController = controller?.navigationController as? NavigationController {
|
||||||
|
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), subject: .message(id: messageId, highlight: true)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})))
|
||||||
|
|
||||||
|
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(ChannelStatsContextExtractedContentSource(controller: controller, sourceNode: sourceNode, keepInPlace: false)), items: .single(items), reactionItems: [], gesture: gesture)
|
||||||
|
controller.presentInGlobalOverlay(contextController)
|
||||||
}
|
}
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class ChannelStatsContextExtractedContentSource: ContextExtractedContentSource {
|
||||||
|
var keepInPlace: Bool
|
||||||
|
let ignoreContentTouches: Bool = true
|
||||||
|
let blurBackground: Bool = true
|
||||||
|
|
||||||
|
private let controller: ViewController
|
||||||
|
private let sourceNode: ContextExtractedContentContainingNode
|
||||||
|
|
||||||
|
init(controller: ViewController, sourceNode: ContextExtractedContentContainingNode, keepInPlace: Bool) {
|
||||||
|
self.controller = controller
|
||||||
|
self.sourceNode = sourceNode
|
||||||
|
self.keepInPlace = keepInPlace
|
||||||
|
}
|
||||||
|
|
||||||
|
func takeView() -> ContextControllerTakeViewInfo? {
|
||||||
|
return ContextControllerTakeViewInfo(contentContainingNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func putBack() -> ContextControllerPutBackViewInfo? {
|
||||||
|
return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -22,8 +22,9 @@ public class StatsMessageItem: ListViewItem, ItemListItem {
|
|||||||
public let sectionId: ItemListSectionId
|
public let sectionId: ItemListSectionId
|
||||||
let style: ItemListStyle
|
let style: ItemListStyle
|
||||||
let action: (() -> Void)?
|
let action: (() -> Void)?
|
||||||
|
let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||||
|
|
||||||
init(context: AccountContext, presentationData: ItemListPresentationData, message: Message, views: Int32, forwards: Int32, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)?) {
|
init(context: AccountContext, presentationData: ItemListPresentationData, message: Message, views: Int32, forwards: Int32, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.message = message
|
self.message = message
|
||||||
@ -32,6 +33,7 @@ public class StatsMessageItem: ListViewItem, ItemListItem {
|
|||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.style = style
|
self.style = style
|
||||||
self.action = action
|
self.action = action
|
||||||
|
self.contextAction = contextAction
|
||||||
}
|
}
|
||||||
|
|
||||||
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||||
@ -88,6 +90,8 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
let contextSourceNode: ContextExtractedContentContainingNode
|
let contextSourceNode: ContextExtractedContentContainingNode
|
||||||
private let containerNode: ContextControllerSourceNode
|
private let containerNode: ContextControllerSourceNode
|
||||||
private let extractedBackgroundImageNode: ASImageNode
|
private let extractedBackgroundImageNode: ASImageNode
|
||||||
|
private let offsetContainerNode: ASDisplayNode
|
||||||
|
private let countersContainerNode: ASDisplayNode
|
||||||
|
|
||||||
private var extractedRect: CGRect?
|
private var extractedRect: CGRect?
|
||||||
private var nonExtractedRect: CGRect?
|
private var nonExtractedRect: CGRect?
|
||||||
@ -134,6 +138,9 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
self.contentImageNode = TransformImageNode()
|
self.contentImageNode = TransformImageNode()
|
||||||
self.contentImageNode.isLayerBacked = true
|
self.contentImageNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.offsetContainerNode = ASDisplayNode()
|
||||||
|
self.countersContainerNode = ASDisplayNode()
|
||||||
|
|
||||||
self.titleNode = TextNode()
|
self.titleNode = TextNode()
|
||||||
self.titleNode.isUserInteractionEnabled = false
|
self.titleNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
@ -153,13 +160,56 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
self.addSubnode(self.contentImageNode)
|
self.containerNode.addSubnode(self.contextSourceNode)
|
||||||
self.addSubnode(self.titleNode)
|
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
||||||
self.addSubnode(self.labelNode)
|
self.addSubnode(self.containerNode)
|
||||||
self.addSubnode(self.viewsNode)
|
|
||||||
self.addSubnode(self.forwardsNode)
|
self.contextSourceNode.contentNode.addSubnode(self.extractedBackgroundImageNode)
|
||||||
|
self.contextSourceNode.contentNode.addSubnode(self.offsetContainerNode)
|
||||||
|
self.contextSourceNode.contentNode.addSubnode(self.countersContainerNode)
|
||||||
|
|
||||||
|
self.offsetContainerNode.addSubnode(self.contentImageNode)
|
||||||
|
self.offsetContainerNode.addSubnode(self.titleNode)
|
||||||
|
self.offsetContainerNode.addSubnode(self.labelNode)
|
||||||
|
self.countersContainerNode.addSubnode(self.viewsNode)
|
||||||
|
self.countersContainerNode.addSubnode(self.forwardsNode)
|
||||||
|
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
||||||
|
|
||||||
self.addSubnode(self.activateArea)
|
self.addSubnode(self.activateArea)
|
||||||
|
|
||||||
|
self.containerNode.activated = { [weak self] gesture, _ in
|
||||||
|
guard let strongSelf = self, let item = strongSelf.item, let contextAction = item.contextAction else {
|
||||||
|
gesture.cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
contextAction(strongSelf.contextSourceNode, gesture)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in
|
||||||
|
guard let strongSelf = self, let item = strongSelf.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isExtracted {
|
||||||
|
strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: item.presentationData.theme.list.itemBlocksBackgroundColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let extractedRect = strongSelf.extractedRect, let nonExtractedRect = strongSelf.nonExtractedRect {
|
||||||
|
let rect = isExtracted ? extractedRect : nonExtractedRect
|
||||||
|
transition.updateFrame(node: strongSelf.extractedBackgroundImageNode, frame: rect)
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.updateAlpha(node: strongSelf.countersContainerNode, alpha: isExtracted ? 0.0 : 1.0)
|
||||||
|
|
||||||
|
transition.updateSublayerTransformOffset(layer: strongSelf.countersContainerNode.layer, offset: CGPoint(x: isExtracted ? -24.0 : 0.0, y: 0.0))
|
||||||
|
transition.updateSublayerTransformOffset(layer: strongSelf.offsetContainerNode.layer, offset: CGPoint(x: isExtracted ? 12.0 : 0.0, y: 0.0))
|
||||||
|
|
||||||
|
transition.updateAlpha(node: strongSelf.extractedBackgroundImageNode, alpha: isExtracted ? 1.0 : 0.0, completion: { _ in
|
||||||
|
if !isExtracted {
|
||||||
|
self?.extractedBackgroundImageNode.image = nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func asyncLayout() -> (_ item: StatsMessageItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
public func asyncLayout() -> (_ item: StatsMessageItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
@ -271,6 +321,25 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.item = item
|
strongSelf.item = item
|
||||||
|
|
||||||
|
let nonExtractedRect = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width - 16.0, height: layout.contentSize.height))
|
||||||
|
let extractedRect = CGRect(origin: CGPoint(), size: layout.contentSize).insetBy(dx: 16.0 + params.leftInset, dy: 0.0)
|
||||||
|
strongSelf.extractedRect = extractedRect
|
||||||
|
strongSelf.nonExtractedRect = nonExtractedRect
|
||||||
|
|
||||||
|
if strongSelf.contextSourceNode.isExtractedToContextPreview {
|
||||||
|
strongSelf.extractedBackgroundImageNode.frame = extractedRect
|
||||||
|
} else {
|
||||||
|
strongSelf.extractedBackgroundImageNode.frame = nonExtractedRect
|
||||||
|
}
|
||||||
|
strongSelf.contextSourceNode.contentRect = extractedRect
|
||||||
|
|
||||||
|
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||||
|
strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||||
|
strongSelf.offsetContainerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||||
|
strongSelf.countersContainerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||||
|
strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||||
|
strongSelf.containerNode.isGestureEnabled = item.contextAction != nil
|
||||||
|
|
||||||
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
|
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
|
||||||
strongSelf.activateArea.accessibilityLabel = text
|
strongSelf.activateArea.accessibilityLabel = text
|
||||||
strongSelf.activateArea.accessibilityValue = label
|
strongSelf.activateArea.accessibilityValue = label
|
||||||
|
Loading…
x
Reference in New Issue
Block a user