mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Statistics improvements
This commit is contained in:
parent
c52f8c108c
commit
266ff8370d
@ -32,7 +32,7 @@ class ChartVisibilityItemView: UIView {
|
||||
func setupView() {
|
||||
checkButton.frame = bounds
|
||||
checkButton.titleLabel?.font = ChartVisibilityItemView.textFont
|
||||
checkButton.layer.cornerRadius = 6
|
||||
checkButton.layer.cornerRadius = 15
|
||||
checkButton.layer.masksToBounds = true
|
||||
checkButton.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
|
||||
let pressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(didRecognizedLongPress(recognizer:)))
|
||||
|
@ -62,8 +62,11 @@ private enum StatsSection: Int32 {
|
||||
case followersBySource
|
||||
case languages
|
||||
case postInteractions
|
||||
case recentPosts
|
||||
case instantPageInteractions
|
||||
case reactionsByEmotion
|
||||
case storyInteractions
|
||||
case storyReactionsByEmotion
|
||||
case recentPosts
|
||||
case boostLevel
|
||||
case boostOverview
|
||||
case boostPrepaid
|
||||
@ -99,13 +102,22 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
|
||||
case postInteractionsTitle(PresentationTheme, String)
|
||||
case postInteractionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
|
||||
|
||||
case reactionsByEmotionTitle(PresentationTheme, String)
|
||||
case reactionsByEmotionGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
|
||||
|
||||
case postsTitle(PresentationTheme, String)
|
||||
case post(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Message, ChannelStatsMessageInteractions)
|
||||
case storyInteractionsTitle(PresentationTheme, String)
|
||||
case storyInteractionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
|
||||
|
||||
case storyReactionsByEmotionTitle(PresentationTheme, String)
|
||||
case storyReactionsByEmotionGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
|
||||
|
||||
case instantPageInteractionsTitle(PresentationTheme, String)
|
||||
case instantPageInteractionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
|
||||
|
||||
case postsTitle(PresentationTheme, String)
|
||||
case post(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Message, ChannelStatsMessageInteractions)
|
||||
|
||||
case boostLevel(PresentationTheme, Int32, Int32, CGFloat)
|
||||
|
||||
case boostOverviewTitle(PresentationTheme, String)
|
||||
@ -149,10 +161,16 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
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
|
||||
case .reactionsByEmotionTitle, .reactionsByEmotionGraph:
|
||||
return StatsSection.reactionsByEmotion.rawValue
|
||||
case .storyInteractionsTitle, .storyInteractionsGraph:
|
||||
return StatsSection.storyInteractions.rawValue
|
||||
case .storyReactionsByEmotionTitle, .storyReactionsByEmotionGraph:
|
||||
return StatsSection.storyReactionsByEmotion.rawValue
|
||||
case .postsTitle, .post:
|
||||
return StatsSection.recentPosts.rawValue
|
||||
case .boostLevel:
|
||||
return StatsSection.boostLevel.rawValue
|
||||
case .boostOverviewTitle, .boostOverview:
|
||||
@ -207,13 +225,25 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
case .postInteractionsGraph:
|
||||
return 17
|
||||
case .instantPageInteractionsTitle:
|
||||
return 18
|
||||
case .instantPageInteractionsGraph:
|
||||
return 19
|
||||
case .postsTitle:
|
||||
return 18
|
||||
case .instantPageInteractionsGraph:
|
||||
return 19
|
||||
case .reactionsByEmotionTitle:
|
||||
return 20
|
||||
case .reactionsByEmotionGraph:
|
||||
return 21
|
||||
case .storyInteractionsTitle:
|
||||
return 22
|
||||
case .storyInteractionsGraph:
|
||||
return 23
|
||||
case .storyReactionsByEmotionTitle:
|
||||
return 24
|
||||
case .storyReactionsByEmotionGraph:
|
||||
return 25
|
||||
case .postsTitle:
|
||||
return 26
|
||||
case let .post(index, _, _, _, _, _):
|
||||
return 21 + index
|
||||
return 27 + index
|
||||
case .boostLevel:
|
||||
return 2000
|
||||
case .boostOverviewTitle:
|
||||
@ -367,12 +397,6 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
} 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
|
||||
@ -385,6 +409,48 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .reactionsByEmotionTitle(lhsTheme, lhsText):
|
||||
if case let .reactionsByEmotionTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .reactionsByEmotionGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
|
||||
if case let .reactionsByEmotionGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .storyInteractionsTitle(lhsTheme, lhsText):
|
||||
if case let .storyInteractionsTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .storyInteractionsGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
|
||||
if case let .storyInteractionsGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .storyReactionsByEmotionTitle(lhsTheme, lhsText):
|
||||
if case let .storyReactionsByEmotionTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .storyReactionsByEmotionGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
|
||||
if case let .storyReactionsByEmotionGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
|
||||
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 .boostLevel(lhsTheme, lhsBoosts, lhsLevel, lhsPosition):
|
||||
if case let .boostLevel(rhsTheme, rhsBoosts, rhsLevel, rhsPosition) = rhs, lhsTheme === rhsTheme, lhsBoosts == rhsBoosts, lhsLevel == rhsLevel, lhsPosition == rhsPosition {
|
||||
return true
|
||||
@ -507,8 +573,11 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
let .followersBySourceTitle(_, text),
|
||||
let .languagesTitle(_, text),
|
||||
let .postInteractionsTitle(_, text),
|
||||
let .postsTitle(_, text),
|
||||
let .instantPageInteractionsTitle(_, text),
|
||||
let .reactionsByEmotionTitle(_, text),
|
||||
let .storyInteractionsTitle(_, text),
|
||||
let .storyReactionsByEmotionTitle(_, text),
|
||||
let .postsTitle(_, text),
|
||||
let .boostOverviewTitle(_, text),
|
||||
let .boostPrepaidTitle(_, text),
|
||||
let .boostersTitle(_, text),
|
||||
@ -527,10 +596,13 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
let .viewsByHourGraph(_, _, _, graph, type),
|
||||
let .viewsBySourceGraph(_, _, _, graph, type),
|
||||
let .followersBySourceGraph(_, _, _, graph, type),
|
||||
let .languagesGraph(_, _, _, graph, type):
|
||||
let .languagesGraph(_, _, _, graph, type),
|
||||
let .reactionsByEmotionGraph(_, _, _, graph, type),
|
||||
let .storyReactionsByEmotionGraph(_, _, _, graph, type):
|
||||
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks)
|
||||
case let .postInteractionsGraph(_, _, _, graph, type),
|
||||
let .instantPageInteractionsGraph(_, _, _, graph, type):
|
||||
let .instantPageInteractionsGraph(_, _, _, graph, type),
|
||||
let .storyInteractionsGraph(_, _, _, 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 {
|
||||
@ -760,6 +832,21 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
|
||||
entries.append(.instantPageInteractionsGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.instantPageInteractionsGraph, .twoAxisStep))
|
||||
}
|
||||
|
||||
if !data.reactionsByEmotionGraph.isEmpty {
|
||||
entries.append(.reactionsByEmotionTitle(presentationData.theme, presentationData.strings.Stats_ReactionsByEmotionTitle))
|
||||
entries.append(.reactionsByEmotionGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.reactionsByEmotionGraph, .bars))
|
||||
}
|
||||
|
||||
if !data.storyInteractionsGraph.isEmpty {
|
||||
entries.append(.storyInteractionsTitle(presentationData.theme, presentationData.strings.Stats_StoryInteractionsTitle))
|
||||
entries.append(.storyInteractionsGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.storyInteractionsGraph, .twoAxisStep))
|
||||
}
|
||||
|
||||
if !data.storyReactionsByEmotionGraph.isEmpty {
|
||||
entries.append(.storyReactionsByEmotionTitle(presentationData.theme, presentationData.strings.Stats_StoryReactionsByEmotionTitle))
|
||||
entries.append(.storyReactionsByEmotionGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.storyReactionsByEmotionGraph, .bars))
|
||||
}
|
||||
|
||||
if let messages = messages, !messages.isEmpty, let interactions = interactions, !interactions.isEmpty {
|
||||
entries.append(.postsTitle(presentationData.theme, presentationData.strings.Stats_PostsTitle))
|
||||
var index: Int32 = 0
|
||||
@ -915,6 +1002,9 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
statsContext.loadViewsBySourceGraph()
|
||||
statsContext.loadLanguagesGraph()
|
||||
statsContext.loadInstantPageInteractionsGraph()
|
||||
statsContext.loadReactionsByEmotionGraph()
|
||||
statsContext.loadStoryInteractionsGraph()
|
||||
statsContext.loadStoryReactionsByEmotionGraph()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -29,6 +29,7 @@ private final class MessageStatsControllerArguments {
|
||||
private enum StatsSection: Int32 {
|
||||
case overview
|
||||
case interactions
|
||||
case reactions
|
||||
case publicForwards
|
||||
}
|
||||
|
||||
@ -39,6 +40,9 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
case interactionsTitle(PresentationTheme, String)
|
||||
case interactionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
|
||||
|
||||
case reactionsTitle(PresentationTheme, String)
|
||||
case reactionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
|
||||
|
||||
case publicForwardsTitle(PresentationTheme, String)
|
||||
case publicForward(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, EngineMessage)
|
||||
|
||||
@ -48,6 +52,8 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
return StatsSection.overview.rawValue
|
||||
case .interactionsTitle, .interactionsGraph:
|
||||
return StatsSection.interactions.rawValue
|
||||
case .reactionsTitle, .reactionsGraph:
|
||||
return StatsSection.reactions.rawValue
|
||||
case .publicForwardsTitle, .publicForward:
|
||||
return StatsSection.publicForwards.rawValue
|
||||
}
|
||||
@ -63,10 +69,14 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
return 2
|
||||
case .interactionsGraph:
|
||||
return 3
|
||||
case .publicForwardsTitle:
|
||||
case .reactionsTitle:
|
||||
return 4
|
||||
case .reactionsGraph:
|
||||
return 5
|
||||
case .publicForwardsTitle:
|
||||
return 6
|
||||
case let .publicForward(index, _, _, _, _):
|
||||
return 5 + index
|
||||
return 7 + index
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,6 +106,18 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .reactionsTitle(lhsTheme, lhsText):
|
||||
if case let .reactionsTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .reactionsGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
|
||||
if case let .reactionsGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .publicForwardsTitle(lhsTheme, lhsText):
|
||||
if case let .publicForwardsTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
@ -120,11 +142,12 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
switch self {
|
||||
case let .overviewTitle(_, text),
|
||||
let .interactionsTitle(_, text),
|
||||
let .reactionsTitle(_, text),
|
||||
let .publicForwardsTitle(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .overview(_, stats, publicShares):
|
||||
return MessageStatsOverviewItem(presentationData: presentationData, stats: stats, publicShares: publicShares, sectionId: self.section, style: .blocks)
|
||||
case let .interactionsGraph(_, _, _, graph, type):
|
||||
case let .interactionsGraph(_, _, _, graph, type), let .reactionsGraph(_, _, _, 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 {
|
||||
@ -170,6 +193,11 @@ private func messageStatsControllerEntries(data: MessageStats?, messages: Search
|
||||
|
||||
entries.append(.interactionsGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.interactionsGraph, chartType))
|
||||
}
|
||||
|
||||
if !data.reactionsGraph.isEmpty {
|
||||
entries.append(.reactionsTitle(presentationData.theme, presentationData.strings.Stats_MessageReactionsTitle.uppercased()))
|
||||
entries.append(.reactionsGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.reactionsGraph, .bars))
|
||||
}
|
||||
|
||||
if let messages = messages, !messages.messages.isEmpty {
|
||||
entries.append(.publicForwardsTitle(presentationData.theme, presentationData.strings.Stats_MessagePublicForwardsTitle.uppercased()))
|
||||
|
@ -180,6 +180,7 @@ class StatsGraphItemNode: ListViewItemNode {
|
||||
if let visibilityHeight = visibilityHeight {
|
||||
contentSize.height += visibilityHeight
|
||||
}
|
||||
contentSize.height += 7.0
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
|
||||
|
@ -9,12 +9,14 @@ public struct MessageStats: Equatable {
|
||||
public let forwards: Int
|
||||
public let interactionsGraph: StatsGraph
|
||||
public let interactionsGraphDelta: Int64
|
||||
public let reactionsGraph: StatsGraph
|
||||
|
||||
init(views: Int, forwards: Int, interactionsGraph: StatsGraph, interactionsGraphDelta: Int64) {
|
||||
init(views: Int, forwards: Int, interactionsGraph: StatsGraph, interactionsGraphDelta: Int64, reactionsGraph: StatsGraph) {
|
||||
self.views = views
|
||||
self.forwards = forwards
|
||||
self.interactionsGraph = interactionsGraph
|
||||
self.interactionsGraphDelta = interactionsGraphDelta
|
||||
self.reactionsGraph = reactionsGraph
|
||||
}
|
||||
|
||||
public static func == (lhs: MessageStats, rhs: MessageStats) -> Bool {
|
||||
@ -30,11 +32,14 @@ public struct MessageStats: Equatable {
|
||||
if lhs.interactionsGraphDelta != rhs.interactionsGraphDelta {
|
||||
return false
|
||||
}
|
||||
if lhs.reactionsGraph != rhs.reactionsGraph {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public func withUpdatedInteractionsGraph(_ interactionsGraph: StatsGraph) -> MessageStats {
|
||||
return MessageStats(views: self.views, forwards: self.forwards, interactionsGraph: interactionsGraph, interactionsGraphDelta: self.interactionsGraphDelta)
|
||||
return MessageStats(views: self.views, forwards: self.forwards, interactionsGraph: interactionsGraph, interactionsGraphDelta: self.interactionsGraphDelta, reactionsGraph: self.reactionsGraph)
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,8 +88,8 @@ private func requestMessageStats(postbox: Postbox, network: Network, datacenterI
|
||||
|
||||
return signal
|
||||
|> mapToSignal { result -> Signal<MessageStats?, MTRpcError> in
|
||||
if case let .messageStats(apiViewsGraph, _) = result {
|
||||
let interactionsGraph = StatsGraph(apiStatsGraph: apiViewsGraph)
|
||||
if case let .messageStats(apiInteractionsGraph, apiReactionsGraph) = result {
|
||||
let interactionsGraph = StatsGraph(apiStatsGraph: apiInteractionsGraph)
|
||||
var interactionsGraphDelta: Int64 = 86400
|
||||
if case let .Loaded(_, data) = interactionsGraph {
|
||||
if let start = data.range(of: "[\"x\",") {
|
||||
@ -101,8 +106,14 @@ private func requestMessageStats(postbox: Postbox, network: Network, datacenterI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return .single(MessageStats(views: views, forwards: forwards, interactionsGraph: interactionsGraph, interactionsGraphDelta: interactionsGraphDelta))
|
||||
let reactionsGraph = StatsGraph(apiStatsGraph: apiReactionsGraph)
|
||||
return .single(MessageStats(
|
||||
views: views,
|
||||
forwards: forwards,
|
||||
interactionsGraph: interactionsGraph,
|
||||
interactionsGraphDelta: interactionsGraphDelta,
|
||||
reactionsGraph: reactionsGraph
|
||||
))
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
@ -468,6 +468,51 @@ private final class ChannelStatsContextImpl {
|
||||
}
|
||||
}
|
||||
|
||||
func loadReactionsByEmotionGraph() {
|
||||
guard let stats = self._state.stats else {
|
||||
return
|
||||
}
|
||||
if case let .OnDemand(token) = stats.reactionsByEmotionGraph {
|
||||
self.disposables.set((requestGraph(network: self.network, datacenterId: self.datacenterId, token: token)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] graph in
|
||||
if let strongSelf = self, let graph = graph {
|
||||
strongSelf._state = ChannelStatsContextState(stats: strongSelf._state.stats?.withUpdatedReactionsByEmotionGraph(graph))
|
||||
strongSelf._statePromise.set(.single(strongSelf._state))
|
||||
}
|
||||
}), forKey: token)
|
||||
}
|
||||
}
|
||||
|
||||
func loadStoryInteractionsGraph() {
|
||||
guard let stats = self._state.stats else {
|
||||
return
|
||||
}
|
||||
if case let .OnDemand(token) = stats.storyInteractionsGraph {
|
||||
self.disposables.set((requestGraph(network: self.network, datacenterId: self.datacenterId, token: token)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] graph in
|
||||
if let strongSelf = self, let graph = graph {
|
||||
strongSelf._state = ChannelStatsContextState(stats: strongSelf._state.stats?.withUpdatedStoryInteractionsGraph(graph))
|
||||
strongSelf._statePromise.set(.single(strongSelf._state))
|
||||
}
|
||||
}), forKey: token)
|
||||
}
|
||||
}
|
||||
|
||||
func loadStoryReactionsByEmotionGraph() {
|
||||
guard let stats = self._state.stats else {
|
||||
return
|
||||
}
|
||||
if case let .OnDemand(token) = stats.storyReactionsByEmotionGraph {
|
||||
self.disposables.set((requestGraph(network: self.network, datacenterId: self.datacenterId, token: token)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] graph in
|
||||
if let strongSelf = self, let graph = graph {
|
||||
strongSelf._state = ChannelStatsContextState(stats: strongSelf._state.stats?.withUpdatedStoryReactionsByEmotionGraph(graph))
|
||||
strongSelf._statePromise.set(.single(strongSelf._state))
|
||||
}
|
||||
}), forKey: token)
|
||||
}
|
||||
}
|
||||
|
||||
func loadDetailedGraph(_ graph: StatsGraph, x: Int64) -> Signal<StatsGraph?, NoError> {
|
||||
if let token = graph.token {
|
||||
return requestGraph(network: self.network, datacenterId: self.datacenterId, token: token, x: x)
|
||||
@ -551,6 +596,21 @@ public final class ChannelStatsContext {
|
||||
impl.loadLanguagesGraph()
|
||||
}
|
||||
}
|
||||
public func loadReactionsByEmotionGraph() {
|
||||
self.impl.with { impl in
|
||||
impl.loadReactionsByEmotionGraph()
|
||||
}
|
||||
}
|
||||
public func loadStoryInteractionsGraph() {
|
||||
self.impl.with { impl in
|
||||
impl.loadStoryInteractionsGraph()
|
||||
}
|
||||
}
|
||||
public func loadStoryReactionsByEmotionGraph() {
|
||||
self.impl.with { impl in
|
||||
impl.loadStoryReactionsByEmotionGraph()
|
||||
}
|
||||
}
|
||||
|
||||
public func loadDetailedGraph(_ graph: StatsGraph, x: Int64) -> Signal<StatsGraph?, NoError> {
|
||||
return Signal { subscriber in
|
222
submodules/TelegramCore/Sources/Statistics/StoryStatistics.swift
Normal file
222
submodules/TelegramCore/Sources/Statistics/StoryStatistics.swift
Normal file
@ -0,0 +1,222 @@
|
||||
import Foundation
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramApi
|
||||
import MtProtoKit
|
||||
|
||||
public struct StoryStats: Equatable {
|
||||
public let views: Int
|
||||
public let forwards: Int
|
||||
public let interactionsGraph: StatsGraph
|
||||
public let interactionsGraphDelta: Int64
|
||||
public let reactionsGraph: StatsGraph
|
||||
|
||||
init(views: Int, forwards: Int, interactionsGraph: StatsGraph, interactionsGraphDelta: Int64, reactionsGraph: StatsGraph) {
|
||||
self.views = views
|
||||
self.forwards = forwards
|
||||
self.interactionsGraph = interactionsGraph
|
||||
self.interactionsGraphDelta = interactionsGraphDelta
|
||||
self.reactionsGraph = reactionsGraph
|
||||
}
|
||||
|
||||
public static func == (lhs: StoryStats, rhs: StoryStats) -> Bool {
|
||||
if lhs.views != rhs.views {
|
||||
return false
|
||||
}
|
||||
if lhs.forwards != rhs.forwards {
|
||||
return false
|
||||
}
|
||||
if lhs.interactionsGraph != rhs.interactionsGraph {
|
||||
return false
|
||||
}
|
||||
if lhs.interactionsGraphDelta != rhs.interactionsGraphDelta {
|
||||
return false
|
||||
}
|
||||
if lhs.reactionsGraph != rhs.reactionsGraph {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public func withUpdatedInteractionsGraph(_ interactionsGraph: StatsGraph) -> StoryStats {
|
||||
return StoryStats(views: self.views, forwards: self.forwards, interactionsGraph: interactionsGraph, interactionsGraphDelta: self.interactionsGraphDelta, reactionsGraph: self.reactionsGraph)
|
||||
}
|
||||
}
|
||||
|
||||
public struct StoryStatsContextState: Equatable {
|
||||
public var stats: StoryStats?
|
||||
}
|
||||
|
||||
private func requestStoryStats(postbox: Postbox, network: Network, datacenterId: Int32, peerId: EnginePeer.Id, storyId: Int32, dark: Bool = false) -> Signal<StoryStats?, NoError> {
|
||||
return postbox.transaction { transaction -> Peer? in
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
return peer
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} |> mapToSignal { peer -> Signal<StoryStats?, NoError> in
|
||||
guard let peer = peer, let inputPeer = apiInputPeer(peer) else {
|
||||
return .never()
|
||||
}
|
||||
|
||||
var flags: Int32 = 0
|
||||
if dark {
|
||||
flags |= (1 << 1)
|
||||
}
|
||||
|
||||
let request = Api.functions.stats.getStoryStats(flags: flags, peer: inputPeer, id: storyId)
|
||||
let signal: Signal<Api.stats.StoryStats, MTRpcError>
|
||||
if network.datacenterId != datacenterId {
|
||||
signal = network.download(datacenterId: Int(datacenterId), isMedia: false, tag: nil)
|
||||
|> castError(MTRpcError.self)
|
||||
|> mapToSignal { worker in
|
||||
return worker.request(request)
|
||||
}
|
||||
} else {
|
||||
signal = network.request(request)
|
||||
}
|
||||
|
||||
let views: Int = 0
|
||||
let forwards: Int = 0
|
||||
// for attribute in story.attributes {
|
||||
// if let viewsAttribute = attribute as? ViewCountStoryAttribute {
|
||||
// views = viewsAttribute.count
|
||||
// } else if let forwardsAttribute = attribute as? ForwardCountStoryAttribute {
|
||||
// forwards = forwardsAttribute.count
|
||||
// }
|
||||
// }
|
||||
|
||||
return signal
|
||||
|> mapToSignal { result -> Signal<StoryStats?, MTRpcError> in
|
||||
if case let .storyStats(apiInteractionsGraph, apiReactionsGraph) = result {
|
||||
let interactionsGraph = StatsGraph(apiStatsGraph: apiInteractionsGraph)
|
||||
var interactionsGraphDelta: Int64 = 86400
|
||||
if case let .Loaded(_, data) = interactionsGraph {
|
||||
if let start = data.range(of: "[\"x\",") {
|
||||
let substring = data.suffix(from: start.upperBound)
|
||||
if let end = substring.range(of: "],") {
|
||||
let valuesString = substring.prefix(through: substring.index(before: end.lowerBound))
|
||||
let values = valuesString.components(separatedBy: ",").compactMap { Int64($0) }
|
||||
if values.count > 1 {
|
||||
let first = values[0]
|
||||
let second = values[1]
|
||||
let delta = abs(second - first) / 1000
|
||||
interactionsGraphDelta = delta
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let reactionsGraph = StatsGraph(apiStatsGraph: apiReactionsGraph)
|
||||
return .single(StoryStats(
|
||||
views: views,
|
||||
forwards: forwards,
|
||||
interactionsGraph: interactionsGraph,
|
||||
interactionsGraphDelta: interactionsGraphDelta,
|
||||
reactionsGraph: reactionsGraph
|
||||
))
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
|> retryRequest
|
||||
}
|
||||
}
|
||||
|
||||
private final class StoryStatsContextImpl {
|
||||
private let postbox: Postbox
|
||||
private let network: Network
|
||||
private let datacenterId: Int32
|
||||
private let peerId: EnginePeer.Id
|
||||
private let storyId: Int32
|
||||
|
||||
private var _state: StoryStatsContextState {
|
||||
didSet {
|
||||
if self._state != oldValue {
|
||||
self._statePromise.set(.single(self._state))
|
||||
}
|
||||
}
|
||||
}
|
||||
private let _statePromise = Promise<StoryStatsContextState>()
|
||||
var state: Signal<StoryStatsContextState, NoError> {
|
||||
return self._statePromise.get()
|
||||
}
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
private let disposables = DisposableDict<String>()
|
||||
|
||||
init(postbox: Postbox, network: Network, datacenterId: Int32, peerId: EnginePeer.Id, storyId: Int32) {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
|
||||
self.postbox = postbox
|
||||
self.network = network
|
||||
self.datacenterId = datacenterId
|
||||
self.peerId = peerId
|
||||
self.storyId = storyId
|
||||
self._state = StoryStatsContextState(stats: nil)
|
||||
self._statePromise.set(.single(self._state))
|
||||
|
||||
self.load()
|
||||
}
|
||||
|
||||
deinit {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
self.disposable.dispose()
|
||||
self.disposables.dispose()
|
||||
}
|
||||
|
||||
private func load() {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
|
||||
self.disposable.set((requestStoryStats(postbox: self.postbox, network: self.network, datacenterId: self.datacenterId, peerId: self.peerId, storyId: self.storyId)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] stats in
|
||||
if let strongSelf = self {
|
||||
strongSelf._state = StoryStatsContextState(stats: stats)
|
||||
strongSelf._statePromise.set(.single(strongSelf._state))
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func loadDetailedGraph(_ graph: StatsGraph, x: Int64) -> Signal<StatsGraph?, NoError> {
|
||||
if let token = graph.token {
|
||||
return requestGraph(network: self.network, datacenterId: self.datacenterId, token: token, x: x)
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class StoryStatsContext {
|
||||
private let impl: QueueLocalObject<StoryStatsContextImpl>
|
||||
|
||||
public var state: Signal<StoryStatsContextState, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.impl.with { impl in
|
||||
disposable.set(impl.state.start(next: { value in
|
||||
subscriber.putNext(value)
|
||||
}))
|
||||
}
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
public init(postbox: Postbox, network: Network, datacenterId: Int32, peerId: EnginePeer.Id, storyId: Int32) {
|
||||
self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: {
|
||||
return StoryStatsContextImpl(postbox: postbox, network: network, datacenterId: datacenterId, peerId: peerId, storyId: storyId)
|
||||
})
|
||||
}
|
||||
|
||||
public func loadDetailedGraph(_ graph: StatsGraph, x: Int64) -> Signal<StatsGraph?, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.impl.with { impl in
|
||||
disposable.set(impl.loadDetailedGraph(graph, x: x).start(next: { value in
|
||||
subscriber.putNext(value)
|
||||
subscriber.putCompletion()
|
||||
}))
|
||||
}
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user