Update channel statistics [skip ci]

This commit is contained in:
Ilya Laktyushin
2020-02-26 15:04:05 +04:00
parent 0482ff87b2
commit 4da23aac27
10 changed files with 77 additions and 32 deletions

View File

@@ -38,6 +38,7 @@ struct ChartDetailsViewModel {
class ChartDetailsView: UIControl { class ChartDetailsView: UIControl {
let titleLabel = UILabel() let titleLabel = UILabel()
let arrowView = UIImageView() let arrowView = UIImageView()
let activityIndicator = UIActivityIndicatorView()
var prefixViews: [UILabel] = [] var prefixViews: [UILabel] = []
var labelsViews: [UILabel] = [] var labelsViews: [UILabel] = []
@@ -45,11 +46,7 @@ class ChartDetailsView: UIControl {
private var viewModel: ChartDetailsViewModel? private var viewModel: ChartDetailsViewModel?
private var colorMode: ColorMode = .day private var colorMode: ColorMode = .day
static func fromNib() -> ChartDetailsView {
return Bundle.main.loadNibNamed("ChartDetailsView", owner: nil, options: nil)?.first as! ChartDetailsView
}
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)

View File

@@ -189,7 +189,7 @@ class ChartStackSection: UIView, ColorModeContainer {
controller.chartRangeUpdatedClosure = { [unowned self] (range, animated) in controller.chartRangeUpdatedClosure = { [unowned self] (range, animated) in
self.rangeView.setRange(range, animated: animated) self.rangeView.setRange(range, animated: animated)
} }
controller.chartRangePagingClosure = { [unowned self] (isEnabled, pageSize) in controller.chartRangePagingClosure = { [unowned self] (isEnabled, pageSize) in
self.rangeView.setRangePaging(enabled: isEnabled, minimumSize: pageSize) self.rangeView.setRangePaging(enabled: isEnabled, minimumSize: pageSize)
} }

View File

@@ -58,7 +58,7 @@ extension ChartsCollection {
switch type { switch type {
case .axix: case .axix:
axixValuesToSetup = try column.dropFirst().map { Date(timeIntervalSince1970: try Convert.doubleFrom($0) / 1000) } axixValuesToSetup = try column.dropFirst().map { Date(timeIntervalSince1970: try Convert.doubleFrom($0) / 1000) }
case .chart, .bar, .area: case .chart, .bar, .area, .step:
guard let colorString = colors[columnId], guard let colorString = colors[columnId],
let color = UIColor(hexString: colorString) else { let color = UIColor(hexString: colorString) else {
throw ChartsError.generalConversion("Unable to get color name from: \(colors) - \(columnId)") throw ChartsError.generalConversion("Unable to get color name from: \(colors) - \(columnId)")
@@ -88,4 +88,5 @@ private enum ColumnType: String {
case chart = "line" case chart = "line"
case area = "area" case area = "area"
case bar = "bar" case bar = "bar"
case step = "step"
} }

View File

@@ -8,6 +8,7 @@ import SyncCore
import MapKit import MapKit
import TelegramPresentationData import TelegramPresentationData
import TelegramUIPreferences import TelegramUIPreferences
import TelegramStringFormatting
import ItemListUI import ItemListUI
import PresentationDataUtils import PresentationDataUtils
import AccountContext import AccountContext
@@ -15,8 +16,11 @@ import PresentationDataUtils
import AppBundle import AppBundle
import Charts import Charts
private final class StatsArguments { private final class StatsControllerArguments {
init() { let loadDetailedGraph: (ChannelStatsGraph, Int64) -> Signal<ChannelStatsGraph?, NoError>
init(loadDetailedGraph: @escaping (ChannelStatsGraph, Int64) -> Signal<ChannelStatsGraph?, NoError>) {
self.loadDetailedGraph = loadDetailedGraph
} }
} }
@@ -33,7 +37,7 @@ private enum StatsSection: Int32 {
} }
private enum StatsEntry: ItemListNodeEntry { private enum StatsEntry: ItemListNodeEntry {
case overviewHeader(PresentationTheme, String) case overviewHeader(PresentationTheme, String, String)
case overview(PresentationTheme, ChannelStats) case overview(PresentationTheme, ChannelStats)
case growthTitle(PresentationTheme, String) case growthTitle(PresentationTheme, String)
@@ -126,8 +130,8 @@ private enum StatsEntry: ItemListNodeEntry {
static func ==(lhs: StatsEntry, rhs: StatsEntry) -> Bool { static func ==(lhs: StatsEntry, rhs: StatsEntry) -> Bool {
switch lhs { switch lhs {
case let .overviewHeader(lhsTheme, lhsText): case let .overviewHeader(lhsTheme, lhsText, lhsDates):
if case let .overviewHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { if case let .overviewHeader(rhsTheme, rhsText, rhsDates) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsDates == rhsDates {
return true return true
} else { } else {
return false return false
@@ -242,9 +246,11 @@ private enum StatsEntry: ItemListNodeEntry {
} }
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! StatsControllerArguments
switch self { switch self {
case let .overviewHeader(theme, text), case let .overviewHeader(theme, text, dates):
let .growthTitle(theme, text), return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: dates, color: .generic), sectionId: self.section)
case let .growthTitle(theme, text),
let .followersTitle(theme, text), let .followersTitle(theme, text),
let .notificationsTitle(theme, text), let .notificationsTitle(theme, text),
let .viewsByHourTitle(theme, text), let .viewsByHourTitle(theme, text),
@@ -258,19 +264,21 @@ private enum StatsEntry: ItemListNodeEntry {
case let .growthGraph(theme, strings, dateTimeFormat, graph, type): case let .growthGraph(theme, strings, dateTimeFormat, graph, type):
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks) return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks)
case let .followersGraph(theme, strings, dateTimeFormat, graph, type): case let .followersGraph(theme, strings, dateTimeFormat, graph, type):
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks) return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks)
case let .notificationsGraph(theme, strings, dateTimeFormat, graph, type): case let .notificationsGraph(theme, strings, dateTimeFormat, graph, type):
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks) return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks)
case let .viewsByHourGraph(theme, strings, dateTimeFormat, graph, type): case let .viewsByHourGraph(theme, strings, dateTimeFormat, graph, type):
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks) return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks)
case let .postInteractionsGraph(theme, strings, dateTimeFormat, graph, type): case let .postInteractionsGraph(theme, strings, dateTimeFormat, graph, type):
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks) return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, getDetailsData: { date, completion in
let _ = arguments.loadDetailedGraph(graph, Int64(date.timeIntervalSince1970))
}, sectionId: self.section, style: .blocks)
case let .viewsBySourceGraph(theme, strings, dateTimeFormat, graph, type): case let .viewsBySourceGraph(theme, strings, dateTimeFormat, graph, type):
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, height: 160.0, sectionId: self.section, style: .blocks) return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, height: 160.0, sectionId: self.section, style: .blocks)
case let .followersBySourceGraph(theme, strings, dateTimeFormat, graph, type): case let .followersBySourceGraph(theme, strings, dateTimeFormat, graph, type):
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, height: 160.0, sectionId: self.section, style: .blocks) return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, height: 160.0, sectionId: self.section, style: .blocks)
case let .languagesGraph(theme, strings, dateTimeFormat, graph, type): case let .languagesGraph(theme, strings, dateTimeFormat, graph, type):
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, height: 100.0, sectionId: self.section, style: .blocks) return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, height: 100.0, sectionId: self.section, style: .blocks)
} }
} }
} }
@@ -279,7 +287,10 @@ private func statsControllerEntries(data: ChannelStats?, presentationData: Prese
var entries: [StatsEntry] = [] var entries: [StatsEntry] = []
if let data = data { if let data = data {
entries.append(.overviewHeader(presentationData.theme, presentationData.strings.Stats_Overview)) let minDate = stringForDate(timestamp: data.period.minDate, strings: presentationData.strings)
let maxDate = stringForDate(timestamp: data.period.maxDate, strings: presentationData.strings)
entries.append(.overviewHeader(presentationData.theme, presentationData.strings.Stats_Overview, "\(minDate) \(maxDate)"))
entries.append(.overview(presentationData.theme, data)) entries.append(.overview(presentationData.theme, data))
entries.append(.growthTitle(presentationData.theme, presentationData.strings.Stats_GrowthTitle)) entries.append(.growthTitle(presentationData.theme, presentationData.strings.Stats_GrowthTitle))
@@ -339,7 +350,9 @@ public func channelStatsController(context: AccountContext, peer: Peer, cachedPe
}) })
dataPromise.set(.single(nil) |> then(dataSignal)) dataPromise.set(.single(nil) |> then(dataSignal))
let arguments = StatsArguments() let arguments = StatsControllerArguments(loadDetailedGraph: { graph, x -> Signal<ChannelStatsGraph?, NoError> in
return statsContext.loadDetailedGraph(graph, x: x)
})
let signal = combineLatest(context.sharedContext.presentationData, dataPromise.get()) let signal = combineLatest(context.sharedContext.presentationData, dataPromise.get())
|> deliverOnMainQueue |> deliverOnMainQueue

View File

@@ -15,14 +15,16 @@ class StatsGraphItem: ListViewItem, ItemListItem {
let graph: ChannelStatsGraph let graph: ChannelStatsGraph
let type: ChartType let type: ChartType
let height: CGFloat let height: CGFloat
let getDetailsData: ((Date, (String?) -> Void) -> Void)?
let sectionId: ItemListSectionId let sectionId: ItemListSectionId
let style: ItemListStyle let style: ItemListStyle
init(presentationData: ItemListPresentationData, graph: ChannelStatsGraph, type: ChartType, height: CGFloat = 0.0, sectionId: ItemListSectionId, style: ItemListStyle) { init(presentationData: ItemListPresentationData, graph: ChannelStatsGraph, type: ChartType, height: CGFloat = 0.0, getDetailsData: ((Date, (String?) -> Void) -> Void)? = nil, sectionId: ItemListSectionId, style: ItemListStyle) {
self.presentationData = presentationData self.presentationData = presentationData
self.graph = graph self.graph = graph
self.type = type self.type = type
self.height = height self.height = height
self.getDetailsData = getDetailsData
self.sectionId = sectionId self.sectionId = sectionId
self.style = style self.style = style
} }
@@ -184,10 +186,11 @@ class StatsGraphItemNode: ListViewItemNode {
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
} }
if let updatedGraph = updatedGraph, case let .Loaded(data) = updatedGraph { if let updatedGraph = updatedGraph, case let .Loaded(_, data) = updatedGraph {
var data = data.replacingOccurrences(of: "step", with: "bar") strongSelf.chartNode.setup(data, type: item.type, getDetailsData: { [weak self] date, completion in
strongSelf.chartNode.setup(data, type: item.type, getDetailsData: { date, completion in if let strongSelf = self, let item = strongSelf.item {
item.getDetailsData?(date, completion)
}
}) })
} }
} }

View File

@@ -23,7 +23,18 @@ public struct ChannelStatsPercentValue: Equatable {
public enum ChannelStatsGraph: Equatable { public enum ChannelStatsGraph: Equatable {
case OnDemand(token: String) case OnDemand(token: String)
case Failed(error: String) case Failed(error: String)
case Loaded(data: String) case Loaded(token: String?, data: String)
var token: String? {
switch self {
case let .OnDemand(token):
return token
case let .Loaded(token, data):
return token
default:
return nil
}
}
} }
public final class ChannelStats: Equatable { public final class ChannelStats: Equatable {
@@ -367,6 +378,14 @@ private final class ChannelStatsContextImpl {
}), forKey: token) }), forKey: token)
} }
} }
func loadDetailedGraph(_ graph: ChannelStatsGraph, x: Int64) -> Signal<ChannelStatsGraph?, 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 ChannelStatsContext { public final class ChannelStatsContext {
@@ -437,14 +456,26 @@ public final class ChannelStatsContext {
impl.loadLanguagesGraph() impl.loadLanguagesGraph()
} }
} }
public func loadDetailedGraph(_ graph: ChannelStatsGraph, x: Int64) -> Signal<ChannelStatsGraph?, 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)
}))
}
return disposable
}
}
} }
extension ChannelStatsGraph { extension ChannelStatsGraph {
init(apiStatsGraph: Api.StatsGraph) { init(apiStatsGraph: Api.StatsGraph) {
switch apiStatsGraph { switch apiStatsGraph {
case let .statsGraph(_, json, _): case let .statsGraph(_, json, zoomToken):
if case let .dataJSON(string) = json { if case let .dataJSON(string) = json {
self = .Loaded(data: string) self = .Loaded(token: zoomToken, data: string)
} else { } else {
self = .Failed(error: "") self = .Failed(error: "")
} }

View File

@@ -2,7 +2,7 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"filename" : "arrow_left.pdf" "filename" : "arrow_right.pdf"
} }
], ],
"info" : { "info" : {

View File

@@ -2,7 +2,7 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"filename" : "arrow_right.pdf" "filename" : "arrow_left.pdf"
} }
], ],
"info" : { "info" : {