mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
554 lines
29 KiB
Swift
554 lines
29 KiB
Swift
import Foundation
|
||
import UIKit
|
||
import Display
|
||
import AsyncDisplayKit
|
||
import SwiftSignalKit
|
||
import Postbox
|
||
import TelegramCore
|
||
import TelegramPresentationData
|
||
import TelegramUIPreferences
|
||
import TelegramStringFormatting
|
||
import ItemListUI
|
||
import PresentationDataUtils
|
||
import AccountContext
|
||
import PresentationDataUtils
|
||
import AppBundle
|
||
import GraphUI
|
||
import ContextUI
|
||
|
||
private final class ChannelStatsControllerArguments {
|
||
let context: AccountContext
|
||
let loadDetailedGraph: (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>
|
||
let openMessageStats: (MessageId) -> Void
|
||
let contextAction: (MessageId, ASDisplayNode, ContextGesture?) -> 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.loadDetailedGraph = loadDetailedGraph
|
||
self.openMessageStats = openMessage
|
||
self.contextAction = contextAction
|
||
}
|
||
}
|
||
|
||
private enum StatsSection: Int32 {
|
||
case overview
|
||
case growth
|
||
case followers
|
||
case notifications
|
||
case viewsByHour
|
||
case viewsBySource
|
||
case followersBySource
|
||
case languages
|
||
case postInteractions
|
||
case recentPosts
|
||
case instantPageInteractions
|
||
}
|
||
|
||
private enum StatsEntry: ItemListNodeEntry {
|
||
case overviewTitle(PresentationTheme, String, String)
|
||
case overview(PresentationTheme, ChannelStats)
|
||
|
||
case growthTitle(PresentationTheme, String)
|
||
case growthGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
|
||
|
||
case followersTitle(PresentationTheme, String)
|
||
case followersGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
|
||
|
||
case notificationsTitle(PresentationTheme, String)
|
||
case notificationsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
|
||
|
||
case viewsByHourTitle(PresentationTheme, String)
|
||
case viewsByHourGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
|
||
|
||
case viewsBySourceTitle(PresentationTheme, String)
|
||
case viewsBySourceGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
|
||
|
||
case followersBySourceTitle(PresentationTheme, String)
|
||
case followersBySourceGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
|
||
|
||
case languagesTitle(PresentationTheme, String)
|
||
case languagesGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
|
||
|
||
case postInteractionsTitle(PresentationTheme, String)
|
||
case postInteractionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
|
||
|
||
case postsTitle(PresentationTheme, String)
|
||
case post(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Message, ChannelStatsMessageInteractions)
|
||
|
||
case instantPageInteractionsTitle(PresentationTheme, String)
|
||
case instantPageInteractionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
|
||
|
||
var section: ItemListSectionId {
|
||
switch self {
|
||
case .overviewTitle, .overview:
|
||
return StatsSection.overview.rawValue
|
||
case .growthTitle, .growthGraph:
|
||
return StatsSection.growth.rawValue
|
||
case .followersTitle, .followersGraph:
|
||
return StatsSection.followers.rawValue
|
||
case .notificationsTitle, .notificationsGraph:
|
||
return StatsSection.notifications.rawValue
|
||
case .viewsByHourTitle, .viewsByHourGraph:
|
||
return StatsSection.viewsByHour.rawValue
|
||
case .viewsBySourceTitle, .viewsBySourceGraph:
|
||
return StatsSection.viewsBySource.rawValue
|
||
case .followersBySourceTitle, .followersBySourceGraph:
|
||
return StatsSection.followersBySource.rawValue
|
||
case .languagesTitle, .languagesGraph:
|
||
return StatsSection.languages.rawValue
|
||
case .postInteractionsTitle, .postInteractionsGraph:
|
||
return StatsSection.postInteractions.rawValue
|
||
case .postsTitle, .post:
|
||
return StatsSection.recentPosts.rawValue
|
||
case .instantPageInteractionsTitle, .instantPageInteractionsGraph:
|
||
return StatsSection.instantPageInteractions.rawValue
|
||
}
|
||
}
|
||
|
||
var stableId: Int32 {
|
||
switch self {
|
||
case .overviewTitle:
|
||
return 0
|
||
case .overview:
|
||
return 1
|
||
case .growthTitle:
|
||
return 2
|
||
case .growthGraph:
|
||
return 3
|
||
case .followersTitle:
|
||
return 4
|
||
case .followersGraph:
|
||
return 5
|
||
case .notificationsTitle:
|
||
return 6
|
||
case .notificationsGraph:
|
||
return 7
|
||
case .viewsByHourTitle:
|
||
return 8
|
||
case .viewsByHourGraph:
|
||
return 9
|
||
case .viewsBySourceTitle:
|
||
return 10
|
||
case .viewsBySourceGraph:
|
||
return 11
|
||
case .followersBySourceTitle:
|
||
return 12
|
||
case .followersBySourceGraph:
|
||
return 13
|
||
case .languagesTitle:
|
||
return 14
|
||
case .languagesGraph:
|
||
return 15
|
||
case .postInteractionsTitle:
|
||
return 16
|
||
case .postInteractionsGraph:
|
||
return 17
|
||
case .instantPageInteractionsTitle:
|
||
return 18
|
||
case .instantPageInteractionsGraph:
|
||
return 19
|
||
case .postsTitle:
|
||
return 20
|
||
case let .post(index, _, _, _, _, _):
|
||
return 21 + index
|
||
}
|
||
}
|
||
|
||
static func ==(lhs: StatsEntry, rhs: StatsEntry) -> Bool {
|
||
switch lhs {
|
||
case let .overviewTitle(lhsTheme, lhsText, lhsDates):
|
||
if case let .overviewTitle(rhsTheme, rhsText, rhsDates) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsDates == rhsDates {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
case let .overview(lhsTheme, lhsStats):
|
||
if case let .overview(rhsTheme, rhsStats) = rhs, lhsTheme === rhsTheme, lhsStats == rhsStats {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
case let .growthTitle(lhsTheme, lhsText):
|
||
if case let .growthTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
case let .growthGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
|
||
if case let .growthGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
case let .followersTitle(lhsTheme, lhsText):
|
||
if case let .followersTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
case let .followersGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
|
||
if case let .followersGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
case let .notificationsTitle(lhsTheme, lhsText):
|
||
if case let .notificationsTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
case let .notificationsGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
|
||
if case let .notificationsGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
case let .viewsByHourTitle(lhsTheme, lhsText):
|
||
if case let .viewsByHourTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
case let .viewsByHourGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
|
||
if case let .viewsByHourGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
case let .viewsBySourceTitle(lhsTheme, lhsText):
|
||
if case let .viewsBySourceTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
case let .viewsBySourceGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
|
||
if case let .viewsBySourceGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
case let .followersBySourceTitle(lhsTheme, lhsText):
|
||
if case let .followersBySourceTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
case let .followersBySourceGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
|
||
if case let .followersBySourceGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
case let .languagesTitle(lhsTheme, lhsText):
|
||
if case let .languagesTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
case let .languagesGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
|
||
if case let .languagesGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
case let .postInteractionsTitle(lhsTheme, lhsText):
|
||
if case let .postInteractionsTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
case let .postInteractionsGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
|
||
if case let .postInteractionsGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
case let .postsTitle(lhsTheme, lhsText):
|
||
if case let .postsTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
case let .post(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsMessage, lhsInteractions):
|
||
if case let .post(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsMessage, rhsInteractions) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsMessage.id == rhsMessage.id, lhsInteractions == rhsInteractions {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
case let .instantPageInteractionsTitle(lhsTheme, lhsText):
|
||
if case let .instantPageInteractionsTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
case let .instantPageInteractionsGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
|
||
if case let .instantPageInteractionsGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
}
|
||
}
|
||
|
||
static func <(lhs: StatsEntry, rhs: StatsEntry) -> Bool {
|
||
return lhs.stableId < rhs.stableId
|
||
}
|
||
|
||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||
let arguments = arguments as! ChannelStatsControllerArguments
|
||
switch self {
|
||
case let .overviewTitle(_, text, dates):
|
||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: dates, color: .generic), sectionId: self.section)
|
||
case let .growthTitle(_, text),
|
||
let .followersTitle(_, text),
|
||
let .notificationsTitle(_, text),
|
||
let .viewsByHourTitle(_, text),
|
||
let .viewsBySourceTitle(_, text),
|
||
let .followersBySourceTitle(_, text),
|
||
let .languagesTitle(_, text),
|
||
let .postInteractionsTitle(_, text),
|
||
let .postsTitle(_, text),
|
||
let .instantPageInteractionsTitle(_, text):
|
||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||
case let .overview(_, stats):
|
||
return StatsOverviewItem(presentationData: presentationData, stats: stats, sectionId: self.section, style: .blocks)
|
||
case let .growthGraph(_, _, _, graph, type),
|
||
let .followersGraph(_, _, _, graph, type),
|
||
let .notificationsGraph(_, _, _, graph, type),
|
||
let .viewsByHourGraph(_, _, _, graph, type),
|
||
let .viewsBySourceGraph(_, _, _, graph, type),
|
||
let .followersBySourceGraph(_, _, _, graph, type),
|
||
let .languagesGraph(_, _, _, graph, type):
|
||
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks)
|
||
case let .postInteractionsGraph(_, _, _, graph, type),
|
||
let .instantPageInteractionsGraph(_, _, _, graph, type):
|
||
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, getDetailsData: { date, completion in
|
||
let _ = arguments.loadDetailedGraph(graph, Int64(date.timeIntervalSince1970) * 1000).start(next: { graph in
|
||
if let graph = graph, case let .Loaded(_, data) = graph {
|
||
completion(data)
|
||
}
|
||
})
|
||
}, sectionId: self.section, style: .blocks)
|
||
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: {
|
||
arguments.openMessageStats(message.id)
|
||
}, contextAction: { node, gesture in
|
||
arguments.contextAction(message.id, node, gesture)
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
private func channelStatsControllerEntries(data: ChannelStats?, messages: [Message]?, interactions: [MessageId: ChannelStatsMessageInteractions]?, presentationData: PresentationData) -> [StatsEntry] {
|
||
var entries: [StatsEntry] = []
|
||
|
||
if let data = data {
|
||
let minDate = stringForDate(timestamp: data.period.minDate, strings: presentationData.strings)
|
||
let maxDate = stringForDate(timestamp: data.period.maxDate, strings: presentationData.strings)
|
||
|
||
entries.append(.overviewTitle(presentationData.theme, presentationData.strings.Stats_Overview, "\(minDate) – \(maxDate)"))
|
||
entries.append(.overview(presentationData.theme, data))
|
||
|
||
if !data.growthGraph.isEmpty {
|
||
entries.append(.growthTitle(presentationData.theme, presentationData.strings.Stats_GrowthTitle))
|
||
entries.append(.growthGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.growthGraph, .lines))
|
||
}
|
||
|
||
if !data.followersGraph.isEmpty {
|
||
entries.append(.followersTitle(presentationData.theme, presentationData.strings.Stats_FollowersTitle))
|
||
entries.append(.followersGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.followersGraph, .lines))
|
||
}
|
||
|
||
if !data.muteGraph.isEmpty {
|
||
entries.append(.notificationsTitle(presentationData.theme, presentationData.strings.Stats_NotificationsTitle))
|
||
entries.append(.notificationsGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.muteGraph, .lines))
|
||
}
|
||
|
||
if !data.topHoursGraph.isEmpty {
|
||
entries.append(.viewsByHourTitle(presentationData.theme, presentationData.strings.Stats_ViewsByHoursTitle))
|
||
entries.append(.viewsByHourGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.topHoursGraph, .hourlyStep))
|
||
}
|
||
|
||
if !data.viewsBySourceGraph.isEmpty {
|
||
entries.append(.viewsBySourceTitle(presentationData.theme, presentationData.strings.Stats_ViewsBySourceTitle))
|
||
entries.append(.viewsBySourceGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.viewsBySourceGraph, .bars))
|
||
}
|
||
|
||
if !data.newFollowersBySourceGraph.isEmpty {
|
||
entries.append(.followersBySourceTitle(presentationData.theme, presentationData.strings.Stats_FollowersBySourceTitle))
|
||
entries.append(.followersBySourceGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.newFollowersBySourceGraph, .bars))
|
||
}
|
||
|
||
if !data.languagesGraph.isEmpty {
|
||
entries.append(.languagesTitle(presentationData.theme, presentationData.strings.Stats_LanguagesTitle))
|
||
entries.append(.languagesGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.languagesGraph, .pie))
|
||
}
|
||
|
||
if !data.interactionsGraph.isEmpty {
|
||
entries.append(.postInteractionsTitle(presentationData.theme, presentationData.strings.Stats_InteractionsTitle))
|
||
entries.append(.postInteractionsGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.interactionsGraph, .twoAxisStep))
|
||
}
|
||
|
||
if !data.instantPageInteractionsGraph.isEmpty {
|
||
entries.append(.instantPageInteractionsTitle(presentationData.theme, presentationData.strings.Stats_InstantViewInteractionsTitle))
|
||
entries.append(.instantPageInteractionsGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.instantPageInteractionsGraph, .twoAxisStep))
|
||
}
|
||
|
||
if let messages = messages, !messages.isEmpty, let interactions = interactions, !interactions.isEmpty {
|
||
entries.append(.postsTitle(presentationData.theme, presentationData.strings.Stats_PostsTitle))
|
||
var index: Int32 = 0
|
||
for message in messages {
|
||
if let interactions = interactions[message.id] {
|
||
entries.append(.post(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, message, interactions))
|
||
index += 1
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return entries
|
||
}
|
||
|
||
public func channelStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: PeerId, cachedPeerData: CachedPeerData) -> ViewController {
|
||
var openMessageStatsImpl: ((MessageId) -> Void)?
|
||
var contextActionImpl: ((MessageId, ASDisplayNode, ContextGesture?) -> Void)?
|
||
|
||
let actionsDisposable = DisposableSet()
|
||
let dataPromise = Promise<ChannelStats?>(nil)
|
||
let messagesPromise = Promise<MessageHistoryView?>(nil)
|
||
|
||
var datacenterId: Int32 = 0
|
||
if let cachedData = cachedPeerData as? CachedChannelData {
|
||
datacenterId = cachedData.statsDatacenterId
|
||
}
|
||
|
||
let statsContext = ChannelStatsContext(postbox: context.account.postbox, network: context.account.network, datacenterId: datacenterId, peerId: peerId)
|
||
let dataSignal: Signal<ChannelStats?, NoError> = statsContext.state
|
||
|> map { state in
|
||
return state.stats
|
||
} |> afterNext({ [weak statsContext] stats in
|
||
if let statsContext = statsContext, let stats = stats {
|
||
if case .OnDemand = stats.interactionsGraph {
|
||
statsContext.loadInteractionsGraph()
|
||
statsContext.loadMuteGraph()
|
||
statsContext.loadTopHoursGraph()
|
||
statsContext.loadNewFollowersBySourceGraph()
|
||
statsContext.loadViewsBySourceGraph()
|
||
statsContext.loadLanguagesGraph()
|
||
statsContext.loadInstantPageInteractionsGraph()
|
||
}
|
||
}
|
||
})
|
||
dataPromise.set(.single(nil) |> then(dataSignal))
|
||
|
||
let arguments = ChannelStatsControllerArguments(context: context, loadDetailedGraph: { graph, x -> Signal<StatsGraph?, NoError> in
|
||
return statsContext.loadDetailedGraph(graph, x: x)
|
||
}, openMessage: { messageId in
|
||
openMessageStatsImpl?(messageId)
|
||
}, contextAction: { messageId, node, gesture in
|
||
contextActionImpl?(messageId, node, gesture)
|
||
})
|
||
|
||
let messageView = context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId: peerId), index: .upperBound, anchorIndex: .upperBound, count: 100, fixedCombinedReadStates: nil)
|
||
|> map { messageHistoryView, _, _ -> MessageHistoryView? in
|
||
return messageHistoryView
|
||
}
|
||
messagesPromise.set(.single(nil) |> then(messageView))
|
||
|
||
let longLoadingSignal: Signal<Bool, NoError> = .single(false) |> then(.single(true) |> delay(2.0, queue: Queue.mainQueue()))
|
||
|
||
let previousData = Atomic<ChannelStats?>(value: nil)
|
||
|
||
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
|
||
let signal = combineLatest(presentationData, dataPromise.get(), messagesPromise.get(), longLoadingSignal)
|
||
|> deliverOnMainQueue
|
||
|> map { presentationData, data, messageView, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||
let previous = previousData.swap(data)
|
||
var emptyStateItem: ItemListControllerEmptyStateItem?
|
||
if data == nil {
|
||
if longLoading {
|
||
emptyStateItem = StatsEmptyStateItem(context: context, theme: presentationData.theme, strings: presentationData.strings)
|
||
} else {
|
||
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
|
||
}
|
||
}
|
||
|
||
let messages = messageView?.entries.map { $0.message }.sorted(by: { (lhsMessage, rhsMessage) -> Bool in
|
||
return lhsMessage.timestamp > rhsMessage.timestamp
|
||
})
|
||
let interactions = data?.messageInteractions.reduce([MessageId : ChannelStatsMessageInteractions]()) { (map, interactions) -> [MessageId : ChannelStatsMessageInteractions] in
|
||
var map = map
|
||
map[interactions.messageId] = interactions
|
||
return map
|
||
}
|
||
|
||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChannelInfo_Stats), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelStatsControllerEntries(data: data, messages: messages, interactions: interactions, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false)
|
||
|
||
return (controllerState, (listState, arguments))
|
||
}
|
||
|> afterDisposed {
|
||
actionsDisposable.dispose()
|
||
let _ = statsContext.state
|
||
}
|
||
|
||
let controller = ItemListController(context: context, state: signal)
|
||
controller.contentOffsetChanged = { [weak controller] _, _ in
|
||
controller?.forEachItemNode({ itemNode in
|
||
if let itemNode = itemNode as? StatsGraphItemNode {
|
||
itemNode.resetInteraction()
|
||
}
|
||
})
|
||
}
|
||
controller.didDisappear = { [weak controller] _ in
|
||
controller?.clearItemNodesHighlight(animated: true)
|
||
}
|
||
openMessageStatsImpl = { [weak controller] messageId in
|
||
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(id: peerId), subject: .message(id: .id(messageId), highlight: true, timecode: nil)))
|
||
}
|
||
})
|
||
})))
|
||
|
||
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(ChannelStatsContextExtractedContentSource(controller: controller, sourceNode: sourceNode, keepInPlace: false)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
||
controller.presentInGlobalOverlay(contextController)
|
||
}
|
||
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)
|
||
}
|
||
}
|