Statistics improvements

This commit is contained in:
Ilya Laktyushin 2023-11-16 16:31:58 +04:00
parent c52f8c108c
commit 266ff8370d
7 changed files with 441 additions and 29 deletions

View File

@ -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:)))

View File

@ -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()
}
}
})

View File

@ -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()))

View File

@ -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

View File

@ -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)
}

View File

@ -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

View 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
}
}
}