Channel Statistics

This commit is contained in:
Ilya Laktyushin
2020-02-24 18:14:51 +04:00
parent c02450ce88
commit df97d36211
27 changed files with 3890 additions and 3941 deletions

View File

@@ -5346,3 +5346,14 @@ Any member of this group will be able to see messages in the channel.";
"PeerInfo.BioExpand" = "more";
"External.OpenIn" = "Open in %@";
"Stats.Overview" = "OVERVIEW";
"Stats.Followers" = "Followers";
"Stats.EnabledNotifications" = "Enabled Notifications";
"Stats.ViewsPerPost" = "Views Per Post";
"Stats.SharesPerPost" = "Shares Per Post";
"Stats.GrowthTitle" = "GROWTH";
"Stats.FollowersTitle" = "FOLLOWERS";
"Stats.NotificationsTitle" = "NOTIFICATIONS";
"Stats.InteractionsTitle" = "INTERACTIONS";

View File

@@ -19,7 +19,6 @@ class ChartStackSection: UIView, ColorModeContainer {
var sectionContainerView: UIView
var separators: [UIView] = []
var headerLabel: UILabel!
var titleLabel: UILabel!
var backButton: UIButton!
@@ -30,7 +29,6 @@ class ChartStackSection: UIView, ColorModeContainer {
chartView = ChartView()
rangeView = RangeChartView()
visibilityView = ChartVisibilityView()
headerLabel = UILabel()
titleLabel = UILabel()
backButton = UIButton()
@@ -40,9 +38,10 @@ class ChartStackSection: UIView, ColorModeContainer {
sectionContainerView.addSubview(chartView)
sectionContainerView.addSubview(rangeView)
sectionContainerView.addSubview(visibilityView)
sectionContainerView.addSubview(titleLabel)
headerLabel.font = UIFont.systemFont(ofSize: 14, weight: .regular)
titleLabel.font = UIFont.systemFont(ofSize: 14, weight: .bold)
titleLabel.textAlignment = .center
visibilityView.clipsToBounds = true
backButton.isExclusiveTouch = true
@@ -56,7 +55,6 @@ class ChartStackSection: UIView, ColorModeContainer {
override func awakeFromNib() {
super.awakeFromNib()
headerLabel.font = UIFont.systemFont(ofSize: 14, weight: .regular)
titleLabel.font = UIFont.systemFont(ofSize: 14, weight: .bold)
visibilityView.clipsToBounds = true
backButton.isExclusiveTouch = true
@@ -95,7 +93,6 @@ class ChartStackSection: UIView, ColorModeContainer {
}
self.titleLabel.setTextColor(colorMode.chartTitleColor, animated: animated && titleLabel.isVisibleInWindow)
self.headerLabel.setTextColor(colorMode.sectionTitleColor, animated: animated && headerLabel.isVisibleInWindow)
}
@IBAction func didTapBackButton() {
@@ -130,6 +127,7 @@ class ChartStackSection: UIView, ColorModeContainer {
super.layoutSubviews()
let bounds = self.bounds
self.titleLabel.frame = CGRect(origin: CGPoint(x: 0.0, y: 10.0), size: CGSize(width: bounds.width, height: 48.0))
self.sectionContainerView.frame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: 350.0))
self.chartView.frame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: 250.0))
self.rangeView.frame = CGRect(origin: CGPoint(x: 0.0, y: 250.0), size: CGSize(width: bounds.width, height: 48.0))
@@ -138,7 +136,6 @@ class ChartStackSection: UIView, ColorModeContainer {
func setup(controller: BaseChartController, title: String) {
self.controller = controller
self.headerLabel.text = title
// Chart
chartView.renderers = controller.mainChartRenderers
@@ -195,5 +192,10 @@ class ChartStackSection: UIView, ColorModeContainer {
controller.initializeChart()
updateToolViews(animated: false)
TimeInterval.animationDurationMultipler = 0.0001
rangeView.setRange(0.75...1.0, animated: false)
controller.updateChartRange(0.75...1.0)
TimeInterval.animationDurationMultipler = 1.0
}
}

View File

@@ -13,7 +13,7 @@ public protocol ChartViewRenderer: class {
func render(context: CGContext, bounds: CGRect, chartFrame: CGRect)
}
class ChartView: UIView {
class ChartView: UIControl {
override init(frame: CGRect) {
super.init(frame: frame)
@@ -63,11 +63,18 @@ class ChartView: UIView {
var userDidSelectCoordinateClosure: ((CGPoint) -> Void)?
var userDidDeselectCoordinateClosure: (() -> Void)?
private var _isTracking: Bool = false
private var touchInitialLocation: CGPoint?
override var isTracking: Bool {
return self._isTracking
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let point = touches.first?.location(in: self) {
let fractionPoint = CGPoint(x: (point.x - chartFrame.origin.x) / chartFrame.width,
y: (point.y - chartFrame.origin.y) / chartFrame.height)
userDidSelectCoordinateClosure?(fractionPoint)
self.touchInitialLocation = point
}
}
@@ -76,15 +83,23 @@ class ChartView: UIView {
let fractionPoint = CGPoint(x: (point.x - chartFrame.origin.x) / chartFrame.width,
y: (point.y - chartFrame.origin.y) / chartFrame.height)
userDidSelectCoordinateClosure?(fractionPoint)
if let initialPosition = self.touchInitialLocation, abs(initialPosition.x - point.x) > 3.0 {
self._isTracking = true
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
userDidDeselectCoordinateClosure?()
self.touchInitialLocation = nil
self._isTracking = false
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
userDidDeselectCoordinateClosure?()
self.touchInitialLocation = nil
self._isTracking = false
}
// MARK: Details View

View File

@@ -1,222 +0,0 @@
//
// ChartsStackViewController.swift
// GraphTest
//
// Created by Andrei Salavei on 4/13/19.
// Copyright © 2019 Andrei Salavei. All rights reserved.
//
import UIKit
class ChartsStackViewController: UIViewController {
@IBOutlet private var stackView: UIStackView!
@IBOutlet private var scrollView: UIScrollView!
@IBOutlet private var psLabel: UILabel!
@IBOutlet private var ppsLabel: UILabel!
@IBOutlet private var animationButton: ChartVisibilityItemView!
private var sections: [ChartStackSection] = []
private var colorMode: ColorMode = .night
private var colorModeButton: UIBarButtonItem!
private var performFastAnimation: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
title = "Statistics"
colorModeButton = UIBarButtonItem(title: colorMode.switchTitle, style: .plain, target: self, action: #selector(didTapSwitchColorMode))
navigationItem.rightBarButtonItem = colorModeButton
apply(colorMode: colorMode, animated: false)
self.navigationController?.navigationBar.barStyle = .black
self.navigationController?.navigationBar.isTranslucent = false
self.view.isUserInteractionEnabled = false
animationButton.backgroundColor = .clear
animationButton.tapClosure = { [weak self] in
guard let self = self else { return }
self.setSlowAnimationEnabled(!self.animationButton.isChecked)
}
self.setSlowAnimationEnabled(false)
loadChart1()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.async {
self.view.setNeedsUpdateConstraints()
self.view.setNeedsLayout()
}
}
func loadChart1() {
ChartsDataLoader.overviewData(type: .generalLines, sync: true, success: { collection in
let generalLinesChartController = GeneralLinesChartController(chartsCollection: collection)
self.addSection(controller: generalLinesChartController, title: "FOLLOWERS")
generalLinesChartController.getDetailsData = { date, completion in
ChartsDataLoader.detaildData(type: .generalLines, date: date, success: { collection in
completion(collection)
}, failure: { error in
completion(nil)
})
}
DispatchQueue.main.async {
self.loadChart2()
}
})
}
func loadChart2() {
ChartsDataLoader.overviewData(type: .twoAxisLines, success: { collection in
let twoAxisLinesChartController = TwoAxisLinesChartController(chartsCollection: collection)
self.addSection(controller: twoAxisLinesChartController, title: "INTERACTIONS")
twoAxisLinesChartController.getDetailsData = { date, completion in
ChartsDataLoader.detaildData(type: .twoAxisLines, date: date, success: { collection in
completion(collection)
}, failure: { error in
completion(nil)
})
}
DispatchQueue.main.async {
self.loadChart3()
}
})
}
func loadChart3() {
ChartsDataLoader.overviewData(type: .stackedBars, success: { collection in
let stackedBarsChartController = StackedBarsChartController(chartsCollection: collection)
self.addSection(controller: stackedBarsChartController, title: "FRUITS")
stackedBarsChartController.getDetailsData = { date, completion in
ChartsDataLoader.detaildData(type: .stackedBars, date: date, success: { collection in
completion(collection)
}, failure: { error in
completion(nil)
})
}
DispatchQueue.main.async {
self.loadChart4()
}
})
}
func loadChart4() {
ChartsDataLoader.overviewData(type: .dailyBars, success: { collection in
let dailyBarsChartController = DailyBarsChartController(chartsCollection: collection)
self.addSection(controller: dailyBarsChartController, title: "VIEWS")
dailyBarsChartController.getDetailsData = { date, completion in
ChartsDataLoader.detaildData(type: .dailyBars, date: date, success: { collection in
completion(collection)
}, failure: { error in
completion(nil)
})
}
DispatchQueue.main.async {
self.loadChart5()
}
})
}
func loadChart5() {
ChartsDataLoader.overviewData(type: .percentPie, success: { collection in
let percentPieChartController = PercentPieChartController(chartsCollection: collection)
self.addSection(controller: percentPieChartController, title: "MORE FRUITS")
self.finalizeChartsLoading()
})
}
func setSlowAnimationEnabled(_ isEnabled: Bool) {
animationButton.setChecked(isChecked: isEnabled, animated: true)
if isEnabled {
TimeInterval.animationDurationMultipler = 5
} else {
TimeInterval.animationDurationMultipler = 1
}
}
func finalizeChartsLoading() {
self.view.isUserInteractionEnabled = true
}
func addSection(controller: BaseChartController, title: String) {
let section = Bundle.main.loadNibNamed("ChartStackSection", owner: nil, options: nil)?.first as! ChartStackSection
section.frame = UIScreen.main.bounds
section.layoutIfNeeded()
section.setup(controller: controller, title: title)
section.apply(colorMode: colorMode, animated: false)
stackView.addArrangedSubview(section)
sections.append(section)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return (colorMode == .day) ? .default : .lightContent
}
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .fade
}
@objc private func didTapSwitchColorMode() {
self.colorMode = self.colorMode == .day ? .night : .day
apply(colorMode: self.colorMode, animated: !performFastAnimation)
colorModeButton.title = colorMode.switchTitle
}
}
extension ChartsStackViewController: UIScrollViewDelegate {
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
performFastAnimation = decelerate
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
performFastAnimation = false
}
}
extension ChartsStackViewController: ColorModeContainer {
func apply(colorMode: ColorMode, animated: Bool) {
UIView.perform(animated: animated) {
self.psLabel.setTextColor(colorMode.sectionTitleColor, animated: animated && self.psLabel.isVisibleInWindow)
self.ppsLabel.setTextColor(colorMode.sectionTitleColor, animated: animated && self.ppsLabel.isVisibleInWindow)
self.animationButton.item = ChartVisibilityItem(title: "Enable slow animations",
color: colorMode.sectionTitleColor)
self.view.backgroundColor = colorMode.tableBackgroundColor
if (animated) {
let animation = CATransition()
animation.timingFunction = CAMediaTimingFunction.init(name: .linear)
animation.type = .fade
animation.duration = .defaultDuration
self.navigationController?.navigationBar.layer.add(animation, forKey: "kCATransitionColorFade")
}
self.navigationController?.navigationBar.tintColor = colorMode.actionButtonColor
self.navigationController?.navigationBar.barTintColor = colorMode.chartBackgroundColor
self.navigationController?.navigationBar.titleTextAttributes = [.font: UIFont.systemFont(ofSize: 17, weight: .medium),
.foregroundColor: colorMode.viewTintColor]
self.view.layoutIfNeeded()
}
self.setNeedsStatusBarAppearanceUpdate()
for section in sections {
section.apply(colorMode: colorMode, animated: animated && section.isVisibleInWindow)
}
}
}
extension ColorMode {
var switchTitle: String {
switch self {
case .day:
return "Night Mode"
case .night:
return "Day Mode"
}
}
}

View File

@@ -42,7 +42,8 @@ class RangeChartView: UIControl {
private let cropFrameView = UIImageView()
private var selectedMarker: Marker?
private var selectedMarkerHorizontalOffet: CGFloat = 0
private var selectedMarkerHorizontalOffset: CGFloat = 0
private var selectedMarkerInitialLocation: CGPoint?
private var isBoundCropHighlighted: Bool = false
private var isRangePagingEnabled: Bool = false
@@ -137,18 +138,22 @@ class RangeChartView: UIControl {
if abs(locationInView(for: upperBound) - point.x + Constants.markerSelectionRange / 2) < Constants.markerSelectionRange {
selectedMarker = .upper
selectedMarkerHorizontalOffet = point.x - locationInView(for: upperBound)
selectedMarkerHorizontalOffset = point.x - locationInView(for: upperBound)
selectedMarkerInitialLocation = point
isBoundCropHighlighted = true
} else if abs(locationInView(for: lowerBound) - point.x - Constants.markerSelectionRange / 2) < Constants.markerSelectionRange {
selectedMarker = .lower
selectedMarkerHorizontalOffet = point.x - locationInView(for: lowerBound)
selectedMarkerHorizontalOffset = point.x - locationInView(for: lowerBound)
selectedMarkerInitialLocation = point
isBoundCropHighlighted = true
} else if point.x > locationInView(for: lowerBound) && point.x < locationInView(for: upperBound) {
selectedMarker = .center
selectedMarkerHorizontalOffet = point.x - locationInView(for: lowerBound)
selectedMarkerHorizontalOffset = point.x - locationInView(for: lowerBound)
selectedMarkerInitialLocation = point
isBoundCropHighlighted = true
} else {
selectedMarker = nil
selectedMarkerInitialLocation = nil
return
}
@@ -160,10 +165,14 @@ class RangeChartView: UIControl {
guard let selectedMarker = selectedMarker else { return }
guard let point = touches.first?.location(in: self) else { return }
let horizontalPosition = point.x - selectedMarkerHorizontalOffet
let horizontalPosition = point.x - selectedMarkerHorizontalOffset
let fraction = fractionFor(offsetX: horizontalPosition)
updateMarkerOffset(selectedMarker, fraction: fraction)
if let initialPosition = selectedMarkerInitialLocation, abs(initialPosition.x - point.x) > 3.0 {
self._isTracking = true
}
sendActions(for: .valueChanged)
}
@@ -175,11 +184,12 @@ class RangeChartView: UIControl {
}
guard let point = touches.first?.location(in: self) else { return }
let horizontalPosition = point.x - selectedMarkerHorizontalOffet
let horizontalPosition = point.x - selectedMarkerHorizontalOffset
let fraction = fractionFor(offsetX: horizontalPosition)
updateMarkerOffset(selectedMarker, fraction: fraction)
self.selectedMarker = nil
self.selectedMarkerInitialLocation = nil
self.isBoundCropHighlighted = false
if bounds.contains(point) {
sendActions(for: .touchUpInside)
@@ -187,13 +197,22 @@ class RangeChartView: UIControl {
sendActions(for: .touchUpOutside)
}
rangeDidChangeClosure?(lowerBound...upperBound)
self._isTracking = false
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
self.selectedMarker = nil
self.selectedMarkerInitialLocation = nil
self.isBoundCropHighlighted = false
self._isTracking = false
sendActions(for: .touchCancel)
}
private var _isTracking: Bool = false
override var isTracking: Bool {
return self._isTracking
}
}
private extension RangeChartView {

View File

@@ -23,12 +23,19 @@ public final class ChartNode: ASDisplayNode {
self.view.disablesInteractiveTransitionGestureRecognizer = true
}
@objc private func nop() {
}
public func setup(_ data: String, bar: Bool = false) {
var bar = bar
if data.contains("bar") {
bar = true
}
if let data = data.data(using: .utf8) {
ChartsDataManager().readChart(data: data, extraCopiesCount: 0, sync: true, success: { [weak self] collection in
let controller: BaseChartController
if bar {
controller = DailyBarsChartController(chartsCollection: collection)
controller = TwoAxisLinesChartController(chartsCollection: collection)
} else {
controller = GeneralLinesChartController(chartsCollection: collection)
}

View File

@@ -76,7 +76,7 @@ extension ChartsCollection {
guard axixValuesToSetup.isEmpty == false,
chartToSetup.isEmpty == false,
chartToSetup.firstIndex(where: { $0.values.count != axixValuesToSetup.count }) == nil else {
throw ChartsError.generalConversion("Saniazing: Invalid number of items: \(axixValuesToSetup), \(chartToSetup)")
throw ChartsError.generalConversion("Sanitazing: Invalid number of items: \(axixValuesToSetup), \(chartToSetup)")
}
self.axisValues = axixValuesToSetup
self.chartValues = chartToSetup

View File

@@ -128,7 +128,11 @@ class BaseChartController: ColorModeContainer {
fatalError("Abstract")
}
func updateChartRange(_ rangeFraction: ClosedRange<CGFloat>) {
func updateChartRangeTitle(animated: Bool) {
}
func updateChartRange(_ rangeFraction: ClosedRange<CGFloat>, animated: Bool = true) {
fatalError("Abstract")
}

View File

@@ -25,9 +25,9 @@ class BaseLinesChartController: BaseChartController {
func setupChartCollection(chartsCollection: ChartsCollection, animated: Bool, isZoomed: Bool) {
if animated {
TimeInterval.setDefaultSuration(.expandAnimationDuration)
TimeInterval.setDefaultDuration(.expandAnimationDuration)
DispatchQueue.main.asyncAfter(deadline: .now() + .expandAnimationDuration) {
TimeInterval.setDefaultSuration(.osXDuration)
TimeInterval.setDefaultDuration(.osXDuration)
}
}
@@ -39,7 +39,7 @@ class BaseLinesChartController: BaseChartController {
updateChartRangeTitle(animated: animated)
}
func updateChartRangeTitle(animated: Bool) {
override func updateChartRangeTitle(animated: Bool) {
let fromDate = Date(timeIntervalSince1970: TimeInterval(zoomedChartRange.lowerBound) + .hour)
let toDate = Date(timeIntervalSince1970: TimeInterval(zoomedChartRange.upperBound))
if Calendar.utc.startOfDay(for: fromDate) == Calendar.utc.startOfDay(for: toDate) {
@@ -64,7 +64,7 @@ class BaseLinesChartController: BaseChartController {
isChartInteractionBegun = false
}
override func updateChartRange(_ rangeFraction: ClosedRange<CGFloat>) {
override func updateChartRange(_ rangeFraction: ClosedRange<CGFloat>, animated: Bool) {
}

View File

@@ -172,7 +172,7 @@ class GeneralLinesChartController: BaseLinesChartController {
return visibleCharts
}
override func updateChartRange(_ rangeFraction: ClosedRange<CGFloat>) {
override func updateChartRange(_ rangeFraction: ClosedRange<CGFloat>, animated: Bool) {
cancelChartInteraction()
let horizontalRange = ClosedRange(uncheckedBounds:
@@ -183,8 +183,8 @@ class GeneralLinesChartController: BaseLinesChartController {
updateChartRangeTitle(animated: true)
updateMainChartHorizontalRange(range: horizontalRange, animated: false)
updateHorizontalLimists(horizontalRange: horizontalRange, animated: true)
updateVerticalLimitsAndRange(horizontalRange: horizontalRange, animated: true)
updateHorizontalLimists(horizontalRange: horizontalRange, animated: animated)
updateVerticalLimitsAndRange(horizontalRange: horizontalRange, animated: animated)
}
func updateMainChartHorizontalRange(range: ClosedRange<CGFloat>, animated: Bool) {

View File

@@ -194,7 +194,7 @@ class TwoAxisLinesChartController: BaseLinesChartController {
self.setupChartCollection(chartsCollection: initialChartCollection, animated: true, isZoomed: false)
}
override func updateChartRange(_ rangeFraction: ClosedRange<CGFloat>) {
override func updateChartRange(_ rangeFraction: ClosedRange<CGFloat>, animated: Bool) {
cancelChartInteraction()
let horizontalRange = ClosedRange(uncheckedBounds:
@@ -205,8 +205,8 @@ class TwoAxisLinesChartController: BaseLinesChartController {
updateChartRangeTitle(animated: true)
updateMainChartHorizontalRange(range: horizontalRange, animated: false)
updateHorizontalLimists(horizontalRange: horizontalRange, animated: true)
updateVerticalLimitsAndRange(horizontalRange: horizontalRange, animated: true)
updateHorizontalLimists(horizontalRange: horizontalRange, animated: animated)
updateVerticalLimitsAndRange(horizontalRange: horizontalRange, animated: animated)
}
func updateMainChartHorizontalRange(range: ClosedRange<CGFloat>, animated: Bool) {

View File

@@ -93,9 +93,9 @@ class PercentPieChartController: BaseChartController {
func switchToChart(chartsCollection: ChartsCollection, isZoomed: Bool, animated: Bool) {
if animated {
TimeInterval.setDefaultSuration(.expandAnimationDuration)
TimeInterval.setDefaultDuration(.expandAnimationDuration)
DispatchQueue.main.asyncAfter(deadline: .now() + .expandAnimationDuration) {
TimeInterval.setDefaultSuration(.osXDuration)
TimeInterval.setDefaultDuration(.osXDuration)
}
}
@@ -263,7 +263,7 @@ class PercentPieChartController: BaseChartController {
})
}
override func updateChartRange(_ rangeFraction: ClosedRange<CGFloat>) {
override func updateChartRange(_ rangeFraction: ClosedRange<CGFloat>, animated: Bool) {
if isZoomed {
return pieController.chartRangeFractionDidUpdated(rangeFraction)
} else {

View File

@@ -84,9 +84,9 @@ class DailyBarsChartController: BaseChartController {
func switchToChart(chartsCollection: ChartsCollection, isZoomed: Bool, animated: Bool) {
if animated {
TimeInterval.setDefaultSuration(.expandAnimationDuration)
TimeInterval.setDefaultDuration(.expandAnimationDuration)
DispatchQueue.main.asyncAfter(deadline: .now() + .expandAnimationDuration) {
TimeInterval.setDefaultSuration(.osXDuration)
TimeInterval.setDefaultDuration(.osXDuration)
}
}
@@ -225,7 +225,7 @@ class DailyBarsChartController: BaseChartController {
switchToChart(chartsCollection: barsController.chartsCollection, isZoomed: false, animated: true)
}
override func updateChartRange(_ rangeFraction: ClosedRange<CGFloat>) {
override func updateChartRange(_ rangeFraction: ClosedRange<CGFloat>, animated: Bool) {
if isZoomed {
return linesController.chartRangeFractionDidUpdated(rangeFraction)
} else {

View File

@@ -79,9 +79,9 @@ class StackedBarsChartController: BaseChartController {
func switchToChart(chartsCollection: ChartsCollection, isZoomed: Bool, animated: Bool) {
if animated {
TimeInterval.setDefaultSuration(.expandAnimationDuration)
TimeInterval.setDefaultDuration(.expandAnimationDuration)
DispatchQueue.main.asyncAfter(deadline: .now() + .expandAnimationDuration) {
TimeInterval.setDefaultSuration(.osXDuration)
TimeInterval.setDefaultDuration(.osXDuration)
}
}
@@ -226,7 +226,7 @@ class StackedBarsChartController: BaseChartController {
switchToChart(chartsCollection: barsController.chartsCollection, isZoomed: false, animated: true)
}
override func updateChartRange(_ rangeFraction: ClosedRange<CGFloat>) {
override func updateChartRange(_ rangeFraction: ClosedRange<CGFloat>, animated: Bool) {
if isZoomed {
return zoomedBarsController.chartRangeFractionDidUpdated(rangeFraction)
} else {

View File

@@ -21,7 +21,7 @@ extension TimeInterval {
}
private static var innerDefaultDuration: TimeInterval = osXDuration
static func setDefaultSuration(_ duration: TimeInterval) {
static func setDefaultDuration(_ duration: TimeInterval) {
innerDefaultDuration = duration
}
}

View File

@@ -354,6 +354,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
let trackingRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.trackingGesture(_:)))
trackingRecognizer.delegate = self
trackingRecognizer.cancelsTouchesInView = false
self.view.addGestureRecognizer(trackingRecognizer)
self.view.addGestureRecognizer(ListViewReorderingGestureRecognizer(shouldBegin: { [weak self] point in

View File

@@ -28,6 +28,11 @@ public final class ListViewScroller: UIScrollView, UIGestureRecognizerDelegate {
return gestureRecognizer.numberOfTouches < 2
}
}
if let view = gestureRecognizer.view?.hitTest(gestureRecognizer.location(in: gestureRecognizer.view), with: nil) as? UIControl {
return !view.isTracking
}
return true
} else {
return true

View File

@@ -36,28 +36,28 @@ private enum StatsEntry: ItemListNodeEntry {
case overview(PresentationTheme, ChannelStats)
case growthTitle(PresentationTheme, String)
case growthGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, String, ChannelStatsGraph)
case growthGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph)
case followersTitle(PresentationTheme, String)
case followersGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, String, ChannelStatsGraph)
case followersGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph)
case notificationsTitle(PresentationTheme, String)
case notificationsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, String, ChannelStatsGraph)
case notificationsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph)
case viewsByHourTitle(PresentationTheme, String)
case viewsByHourGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, String, ChannelStatsGraph)
case viewsByHourGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph)
case postInteractionsTitle(PresentationTheme, String)
case postInteractionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, String, ChannelStatsGraph)
case postInteractionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph)
case viewsBySourceTitle(PresentationTheme, String)
case viewsBySourceGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, String, ChannelStatsGraph)
case viewsBySourceGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph)
case followersBySourceTitle(PresentationTheme, String)
case followersBySourceGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, String, ChannelStatsGraph)
case followersBySourceGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph)
case languagesTitle(PresentationTheme, String)
case languagesGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, String, ChannelStatsGraph)
case languagesGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph)
var section: ItemListSectionId {
switch self {
@@ -143,8 +143,8 @@ private enum StatsEntry: ItemListNodeEntry {
} else {
return false
}
case let .growthGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsText, lhsGraph):
if case let .growthGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsText, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsText == rhsText, lhsGraph == rhsGraph {
case let .growthGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph):
if case let .growthGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph {
return true
} else {
return false
@@ -155,8 +155,8 @@ private enum StatsEntry: ItemListNodeEntry {
} else {
return false
}
case let .followersGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsText, lhsGraph):
if case let .followersGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsText, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsText == rhsText, lhsGraph == rhsGraph {
case let .followersGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph):
if case let .followersGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph {
return true
} else {
return false
@@ -167,8 +167,8 @@ private enum StatsEntry: ItemListNodeEntry {
} else {
return false
}
case let .notificationsGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsText, lhsGraph):
if case let .notificationsGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsText, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsText == rhsText, lhsGraph == rhsGraph {
case let .notificationsGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph):
if case let .notificationsGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph {
return true
} else {
return false
@@ -179,8 +179,8 @@ private enum StatsEntry: ItemListNodeEntry {
} else {
return false
}
case let .viewsByHourGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsText, lhsGraph):
if case let .viewsByHourGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsText, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsText == rhsText, lhsGraph == rhsGraph {
case let .viewsByHourGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph):
if case let .viewsByHourGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph {
return true
} else {
return false
@@ -191,8 +191,8 @@ private enum StatsEntry: ItemListNodeEntry {
} else {
return false
}
case let .postInteractionsGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsText, lhsGraph):
if case let .postInteractionsGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsText, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsText == rhsText, lhsGraph == rhsGraph {
case let .postInteractionsGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph):
if case let .postInteractionsGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph {
return true
} else {
return false
@@ -203,8 +203,8 @@ private enum StatsEntry: ItemListNodeEntry {
} else {
return false
}
case let .viewsBySourceGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsText, lhsGraph):
if case let .viewsBySourceGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsText, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsText == rhsText, lhsGraph == rhsGraph {
case let .viewsBySourceGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph):
if case let .viewsBySourceGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph {
return true
} else {
return false
@@ -215,8 +215,8 @@ private enum StatsEntry: ItemListNodeEntry {
} else {
return false
}
case let .followersBySourceGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsText, lhsGraph):
if case let .followersBySourceGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsText, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsText == rhsText, lhsGraph == rhsGraph {
case let .followersBySourceGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph):
if case let .followersBySourceGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph {
return true
} else {
return false
@@ -227,8 +227,8 @@ private enum StatsEntry: ItemListNodeEntry {
} else {
return false
}
case let .languagesGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsText, lhsGraph):
if case let .languagesGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsText, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsText == rhsText, lhsGraph == rhsGraph {
case let .languagesGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph):
if case let .languagesGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph {
return true
} else {
return false
@@ -254,15 +254,15 @@ private enum StatsEntry: ItemListNodeEntry {
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .overview(theme, stats):
return StatsOverviewItem(presentationData: presentationData, stats: stats, sectionId: self.section, style: .blocks)
case let .growthGraph(theme, strings, dateTimeFormat, title, graph),
let .followersGraph(theme, strings, dateTimeFormat, title, graph),
let .notificationsGraph(theme, strings, dateTimeFormat, title, graph),
let .viewsByHourGraph(theme, strings, dateTimeFormat, title, graph),
let .postInteractionsGraph(theme, strings, dateTimeFormat, title, graph),
let .viewsBySourceGraph(theme, strings, dateTimeFormat, title, graph),
let .followersBySourceGraph(theme, strings, dateTimeFormat, title, graph),
let .languagesGraph(theme, strings, dateTimeFormat, title, graph):
return StatsGraphItem(presentationData: presentationData, title: title, graph: graph, sectionId: self.section, style: .blocks)
case let .growthGraph(theme, strings, dateTimeFormat, graph),
let .followersGraph(theme, strings, dateTimeFormat, graph),
let .notificationsGraph(theme, strings, dateTimeFormat, graph),
let .viewsByHourGraph(theme, strings, dateTimeFormat, graph),
let .postInteractionsGraph(theme, strings, dateTimeFormat, graph),
let .viewsBySourceGraph(theme, strings, dateTimeFormat, graph),
let .followersBySourceGraph(theme, strings, dateTimeFormat, graph),
let .languagesGraph(theme, strings, dateTimeFormat, graph):
return StatsGraphItem(presentationData: presentationData, graph: graph, sectionId: self.section, style: .blocks)
}
}
}
@@ -271,17 +271,20 @@ private func statsControllerEntries(data: ChannelStats?, presentationData: Prese
var entries: [StatsEntry] = []
if let data = data {
entries.append(.overviewHeader(presentationData.theme, "OVERVIEW"))
entries.append(.overviewHeader(presentationData.theme, presentationData.strings.Stats_Overview))
entries.append(.overview(presentationData.theme, data))
entries.append(.growthTitle(presentationData.theme, "GROWTH"))
entries.append(.growthGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, "Growth", data.growthGraph))
entries.append(.growthTitle(presentationData.theme, presentationData.strings.Stats_GrowthTitle))
entries.append(.growthGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.growthGraph))
entries.append(.followersTitle(presentationData.theme, "FOLLOWERS"))
entries.append(.followersGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, "Followers", data.followersGraph))
entries.append(.followersTitle(presentationData.theme, presentationData.strings.Stats_FollowersTitle))
entries.append(.followersGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.followersGraph))
entries.append(.notificationsTitle(presentationData.theme, "NOTIFICATIONS"))
entries.append(.notificationsGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, "Notifications", data.muteGraph))
entries.append(.notificationsTitle(presentationData.theme, presentationData.strings.Stats_NotificationsTitle))
entries.append(.notificationsGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.muteGraph))
entries.append(.postInteractionsTitle(presentationData.theme, presentationData.strings.Stats_InteractionsTitle))
entries.append(.postInteractionsGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.interactionsGraph))
}
return entries
@@ -321,8 +324,13 @@ public func channelStatsController(context: AccountContext, peer: Peer, cachedPe
let signal = combineLatest(context.sharedContext.presentationData, dataPromise.get())
|> deliverOnMainQueue
|> map { presentationData, data -> (ItemListControllerState, (ItemListNodeState, Any)) in
var emptyStateItem: ItemListControllerEmptyStateItem?
if data == nil {
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
}
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: statsControllerEntries(data: data, presentationData: presentationData), style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: false)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: statsControllerEntries(data: data, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: false, animateChanges: false)
return (controllerState, (listState, arguments))
}
@@ -345,5 +353,6 @@ public func channelStatsController(context: AccountContext, peer: Peer, cachedPe
controller.present(c, in: .window(.root), with: a)
}
}
return controller
}

View File

@@ -12,14 +12,12 @@ import Charts
class StatsGraphItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData
let title: String
let graph: ChannelStatsGraph
let sectionId: ItemListSectionId
let style: ItemListStyle
init(presentationData: ItemListPresentationData, title: String, graph: ChannelStatsGraph, sectionId: ItemListSectionId, style: ItemListStyle) {
init(presentationData: ItemListPresentationData, graph: ChannelStatsGraph, sectionId: ItemListSectionId, style: ItemListStyle) {
self.presentationData = presentationData
self.title = title
self.graph = graph
self.sectionId = sectionId
self.style = style
@@ -117,12 +115,12 @@ class StatsGraphItemNode: ListViewItemNode {
case .plain:
itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor
itemSeparatorColor = item.presentationData.theme.list.itemPlainSeparatorColor
contentSize = CGSize(width: params.width, height: 320.0)
contentSize = CGSize(width: params.width, height: 340.0)
insets = itemListNeighborsPlainInsets(neighbors)
case .blocks:
itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor
contentSize = CGSize(width: params.width, height: 320.0)
contentSize = CGSize(width: params.width, height: 340.0)
insets = itemListNeighborsGroupedInsets(neighbors)
}
@@ -138,10 +136,6 @@ class StatsGraphItemNode: ListViewItemNode {
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
}
if let updatedGraph = updatedGraph, case let .Loaded(data) = updatedGraph {
strongSelf.chartNode.setup(data)
}
switch item.style {
case .plain:
if strongSelf.backgroundNode.supernode != nil {
@@ -179,12 +173,17 @@ class StatsGraphItemNode: ListViewItemNode {
bottomStripeInset = 0.0
}
strongSelf.chartNode.frame = CGRect(origin: CGPoint(x: leftInset, y: -30.0), size: CGSize(width: layout.size.width - leftInset - rightInset, height: 350.0))
strongSelf.chartNode.frame = CGRect(origin: CGPoint(x: leftInset, y: -10.0), size: CGSize(width: layout.size.width - leftInset - rightInset, height: 350.0))
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, 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 {
var data = data.replacingOccurrences(of: "step", with: "bar")
strongSelf.chartNode.setup(data)
}
}
})
}

View File

@@ -151,32 +151,60 @@ class StatsOverviewItemNode: ListViewItemNode {
updatedTheme = item.presentationData.theme
}
let valueFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize)
let valueFont = Font.semibold(item.presentationData.fontSize.itemListBaseFontSize)
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize)
let deltaFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize)
let (followersValueLabelLayout, followersValueLabelApply) = makeFollowersValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "221K", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (followersValueLabelLayout, followersValueLabelApply) = makeFollowersValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: compactNumericCountString(Int(item.stats.followers.current)), font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 140.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (viewsPerPostValueLabelLayout, viewsPerPostValueLabelApply) = makeViewsPerPostValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "120K", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (viewsPerPostValueLabelLayout, viewsPerPostValueLabelApply) = makeViewsPerPostValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: compactNumericCountString(Int(item.stats.viewsPerPost.current)), font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 140.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (sharesPerPostValueLabelLayout, sharesPerPostValueLabelApply) = makeSharesPerPostValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "350", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (sharesPerPostValueLabelLayout, sharesPerPostValueLabelApply) = makeSharesPerPostValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: compactNumericCountString(Int(item.stats.sharesPerPost.current)), font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (enabledNotificationsValueLabelLayout, enabledNotificationsValueLabelApply) = makeEnabledNotificationsValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "22.77%", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var enabledNotifications: Double = 0.0
if item.stats.enabledNotifications.total > 0 {
enabledNotifications = item.stats.enabledNotifications.value / item.stats.enabledNotifications.total
}
let (enabledNotificationsValueLabelLayout, enabledNotificationsValueLabelApply) = makeEnabledNotificationsValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: String(format: "%.02f%%", enabledNotifications * 100.0), font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (followersTitleLabelLayout, followersTitleLabelApply) = makeFollowersTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Followers", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (followersTitleLabelLayout, followersTitleLabelApply) = makeFollowersTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_Followers, font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 140.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (viewsPerPostTitleLabelLayout, viewsPerPostTitleLabelApply) = makeViewsPerPostTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Views Per Post", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (viewsPerPostTitleLabelLayout, viewsPerPostTitleLabelApply) = makeViewsPerPostTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_ViewsPerPost, font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 140.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (sharesPerPostTitleLabelLayout, sharesPerPostTitleLabelApply) = makeSharesPerPostTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Shares Per Post", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (sharesPerPostTitleLabelLayout, sharesPerPostTitleLabelApply) = makeSharesPerPostTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_SharesPerPost, font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 140.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (enabledNotificationsTitleLabelLayout, enabledNotificationsTitleLabelApply) = makeEnabledNotificationsTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Enabled Notifications", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (enabledNotificationsTitleLabelLayout, enabledNotificationsTitleLabelApply) = makeEnabledNotificationsTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_EnabledNotifications, font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 140.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (followersDeltaLabelLayout, followersDeltaLabelApply) = makeFollowersDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "+474 (0.21%)", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let followersDeltaValue = item.stats.followers.current - item.stats.followers.previous
let followersDeltaCompact = compactNumericCountString(abs(Int(followersDeltaValue)))
let followersDelta = followersDeltaValue > 0 ? "+\(followersDeltaCompact)" : "-\(followersDeltaCompact)"
var followersDeltaPercentage = 0.0
if item.stats.followers.previous > 0.0 {
followersDeltaPercentage = abs(followersDeltaValue / item.stats.followers.previous)
}
let (viewsPerPostDeltaLabelLayout, viewsPerPostDeltaLabelApply) = makeViewsPerPostDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "-14K (10.68%)", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (followersDeltaLabelLayout, followersDeltaLabelApply) = makeFollowersDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: String(format: "%@ (%.02f%%)", followersDelta, followersDeltaPercentage * 100.0), font: deltaFont, textColor: followersDeltaValue > 0.0 ? item.presentationData.theme.list.freeTextSuccessColor : item.presentationData.theme.list.freeTextErrorColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 140.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (sharesPerPostDeltaLabelLayout, sharesPerPostDeltaLabelApply) = makeSharesPerPostDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "-134 (27.68%)", font: valueFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let viewsPerPostDeltaValue = item.stats.viewsPerPost.current - item.stats.viewsPerPost.previous
let viewsPerPostDeltaCompact = compactNumericCountString(abs(Int(viewsPerPostDeltaValue)))
let viewsPerPostDelta = viewsPerPostDeltaValue > 0 ? "+\(viewsPerPostDeltaCompact)" : "-\(viewsPerPostDeltaCompact)"
var viewsPerPostDeltaPercentage = 0.0
if item.stats.viewsPerPost.previous > 0.0 {
viewsPerPostDeltaPercentage = abs(viewsPerPostDeltaValue / item.stats.viewsPerPost.previous)
}
let (viewsPerPostDeltaLabelLayout, viewsPerPostDeltaLabelApply) = makeViewsPerPostDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: String(format: "%@ (%.02f%%)", viewsPerPostDelta, viewsPerPostDeltaPercentage * 100.0), font: deltaFont, textColor: viewsPerPostDeltaValue > 0.0 ? item.presentationData.theme.list.freeTextSuccessColor : item.presentationData.theme.list.freeTextErrorColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 140.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let sharesPerPostDeltaValue = item.stats.sharesPerPost.current - item.stats.sharesPerPost.previous
let sharesPerPostDeltaCompact = compactNumericCountString(abs(Int(sharesPerPostDeltaValue)))
let sharesPerPostDelta = sharesPerPostDeltaValue > 0 ? "+\(sharesPerPostDeltaCompact)" : "-\(sharesPerPostDeltaCompact)"
var sharesPerPostDeltaPercentage = 0.0
if item.stats.sharesPerPost.previous > 0.0 {
sharesPerPostDeltaPercentage = abs(sharesPerPostDeltaValue / item.stats.sharesPerPost.previous)
}
let (sharesPerPostDeltaLabelLayout, sharesPerPostDeltaLabelApply) = makeSharesPerPostDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: String(format: "%@ (%.02f%%)", sharesPerPostDelta, sharesPerPostDeltaPercentage * 100.0), font: deltaFont, textColor: sharesPerPostDeltaValue > 0.0 ? item.presentationData.theme.list.freeTextSuccessColor : item.presentationData.theme.list.freeTextErrorColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 140.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let contentSize: CGSize
let insets: UIEdgeInsets
@@ -265,13 +293,28 @@ class StatsOverviewItemNode: ListViewItemNode {
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
}
strongSelf.followersValueLabel.frame = CGRect(origin: CGPoint(x: leftInset, y: 7.0), size: followersValueLabelLayout.size)
let horizontalSpacing: CGFloat = 4.0
let verticalSpacing: CGFloat = 70.0
let topInset: CGFloat = 14.0
let sideInset: CGFloat = 16.0
strongSelf.viewsPerPostValueLabel.frame = CGRect(origin: CGPoint(x: leftInset, y: 44.0), size: viewsPerPostValueLabelLayout.size)
strongSelf.followersValueLabel.frame = CGRect(origin: CGPoint(x: sideInset + leftInset, y: topInset), size: followersValueLabelLayout.size)
strongSelf.followersTitleLabel.frame = CGRect(origin: CGPoint(x: sideInset + leftInset, y: strongSelf.followersValueLabel.frame.maxY), size: followersTitleLabelLayout.size)
strongSelf.followersDeltaLabel.frame = CGRect(origin: CGPoint(x: strongSelf.followersValueLabel.frame.maxX + horizontalSpacing, y: strongSelf.followersValueLabel.frame.minY + 4.0), size: followersDeltaLabelLayout.size)
strongSelf.sharesPerPostValueLabel.frame = CGRect(origin: CGPoint(x: layout.size.width / 2.0, y: 44.0), size: sharesPerPostValueLabelLayout.size)
strongSelf.viewsPerPostValueLabel.frame = CGRect(origin: CGPoint(x: sideInset + leftInset, y: verticalSpacing), size: viewsPerPostValueLabelLayout.size)
strongSelf.viewsPerPostTitleLabel.frame = CGRect(origin: CGPoint(x: sideInset + leftInset, y: strongSelf.viewsPerPostValueLabel.frame.maxY), size: viewsPerPostTitleLabelLayout.size)
strongSelf.viewsPerPostDeltaLabel.frame = CGRect(origin: CGPoint(x: strongSelf.viewsPerPostValueLabel.frame.maxX + horizontalSpacing, y: strongSelf.viewsPerPostValueLabel.frame.minY + 4.0), size: viewsPerPostDeltaLabelLayout.size)
strongSelf.enabledNotificationsValueLabel.frame = CGRect(origin: CGPoint(x: layout.size.width / 2.0, y: 7.0), size: enabledNotificationsValueLabelLayout.size)
let rightColumnX = max(layout.size.width / 2.0, max(strongSelf.followersDeltaLabel.frame.maxX, strongSelf.viewsPerPostDeltaLabel.frame.maxX) + horizontalSpacing)
strongSelf.sharesPerPostValueLabel.frame = CGRect(origin: CGPoint(x: rightColumnX, y: verticalSpacing), size: sharesPerPostValueLabelLayout.size)
strongSelf.enabledNotificationsValueLabel.frame = CGRect(origin: CGPoint(x: rightColumnX, y: topInset), size: enabledNotificationsValueLabelLayout.size)
strongSelf.sharesPerPostTitleLabel.frame = CGRect(origin: CGPoint(x: rightColumnX, y: strongSelf.sharesPerPostValueLabel.frame.maxY), size: sharesPerPostTitleLabelLayout.size)
strongSelf.enabledNotificationsTitleLabel.frame = CGRect(origin: CGPoint(x: rightColumnX, y: strongSelf.enabledNotificationsValueLabel.frame.maxY), size: enabledNotificationsTitleLabelLayout.size)
strongSelf.sharesPerPostDeltaLabel.frame = CGRect(origin: CGPoint(x: strongSelf.sharesPerPostValueLabel.frame.maxX + horizontalSpacing, y: strongSelf.sharesPerPostValueLabel.frame.minY + 4.0), size: sharesPerPostDeltaLabelLayout.size)
}
})
}

View File

@@ -16,7 +16,7 @@ public struct ChannelStatsValue: Equatable {
}
public struct ChannelStatsPercentValue: Equatable {
public let fraction: Double
public let value: Double
public let total: Double
}
@@ -133,8 +133,7 @@ public struct ChannelStatsContextState: Equatable {
}
private func requestStats(network: Network, datacenterId: Int32, peer: Peer, dark: Bool = false) -> Signal<ChannelStats?, NoError> {
return .never()
/*guard let inputChannel = apiInputChannel(peer) else {
guard let inputChannel = apiInputChannel(peer) else {
return .never()
}
@@ -160,12 +159,11 @@ private func requestStats(network: Network, datacenterId: Int32, peer: Peer, dar
}
|> `catch` { _ -> Signal<ChannelStats?, NoError> in
return .single(nil)
}*/
}
}
private func requestGraph(network: Network, datacenterId: Int32, token: String) -> Signal<ChannelStatsGraph?, NoError> {
return .never()
/*let signal: Signal<Api.StatsGraph, MTRpcError>
let signal: Signal<Api.StatsGraph, MTRpcError>
if network.datacenterId != datacenterId {
signal = network.download(datacenterId: Int(datacenterId), isMedia: false, tag: nil)
|> castError(MTRpcError.self)
@@ -182,7 +180,7 @@ private func requestGraph(network: Network, datacenterId: Int32, token: String)
}
|> `catch` { _ -> Signal<ChannelStatsGraph?, NoError> in
return .single(nil)
}*/
}
}
private final class ChannelStatsContextImpl {
@@ -363,7 +361,7 @@ public final class ChannelStatsContext {
}
}
/*extension ChannelStatsGraph {
extension ChannelStatsGraph {
init(apiStatsGraph: Api.StatsGraph) {
switch apiStatsGraph {
case let .statsGraph(json):
@@ -411,7 +409,7 @@ extension ChannelStatsPercentValue {
init(apiPercentValue: Api.StatsPercentValue) {
switch apiPercentValue {
case let .statsPercentValue(part, total):
self = ChannelStatsPercentValue(fraction: part, total: total)
self = ChannelStatsPercentValue(value: part, total: total)
}
}
}
@@ -424,4 +422,3 @@ extension ChannelStats {
}
}
}
*/

View File

@@ -319,7 +319,7 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
}
switch fullChat {
case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, _, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, folderId, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, _, pts):
case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, _, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, folderId, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, pts):
var channelFlags = CachedChannelFlags()
if (flags & (1 << 3)) != 0 {
channelFlags.insert(.canDisplayParticipants)
@@ -450,7 +450,7 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
.withUpdatedSlowModeTimeout(slowmodeSeconds)
.withUpdatedSlowModeValidUntilTimestamp(slowmodeNextSendDate)
.withUpdatedHasScheduledMessages(hasScheduledMessages)
// .withUpdatedStatsDatacenterId(statsDc ?? 0)
.withUpdatedStatsDatacenterId(statsDc ?? 0)
})
if let minAvailableMessageId = minAvailableMessageId, minAvailableMessageIdUpdated {

View File

@@ -202,6 +202,7 @@ framework(
"//submodules/SemanticStatusNode:SemanticStatusNode",
"//submodules/AccountUtils:AccountUtils",
"//submodules/Svg:Svg",
"//submodules/StatisticsUI:StatisticsUI",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@@ -1362,32 +1362,61 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
let _ = (getBankCardInfo(account: strongSelf.context.account, cardNumber: number)
|> deliverOnMainQueue).start(next: { [weak self] info in
if let strongSelf = self, let info = info {
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
var items: [ActionSheetItem] = []
items.append(ActionSheetTextItem(title: info.title))
for url in info.urls {
items.append(ActionSheetButtonItem(title: url.title, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
strongSelf.controllerInteraction?.openUrl(url.url, false, false, message)
}
}))
}
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
UIPasteboard.general.string = number
}))
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])])
strongSelf.present(actionSheet, in: .window(.root))
}
})
// var signal = getBankCardInfo(account: strongSelf.context.account, cardNumber: number)
//
// var cancelImpl: (() -> Void)?
// let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
// let progressSignal = Signal<Never, NoError> { subscriber in
// let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
// cancelImpl?()
// }))
// strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
// return ActionDisposable { [weak controller] in
// Queue.mainQueue().async() {
// controller?.dismiss()
// }
// }
// }
// |> runOn(Queue.mainQueue())
// |> delay(0.15, queue: Queue.mainQueue())
// let progressDisposable = progressSignal.start()
//
// signal = signal
// |> afterDisposed {
// Queue.mainQueue().async {
// progressDisposable.dispose()
// }
// }
// cancelImpl = {
// disposable.set(nil)
// }
// disposable.set((signal
// |> deliverOnMainQueue).start(next: { [weak self] info in
// if let strongSelf = self, let info = info {
// let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
// var items: [ActionSheetItem] = []
// items.append(ActionSheetTextItem(title: info.title))
// for url in info.urls {
// items.append(ActionSheetButtonItem(title: url.title, color: .accent, action: { [weak actionSheet] in
// actionSheet?.dismissAnimated()
// if let strongSelf = self {
// strongSelf.controllerInteraction?.openUrl(url.url, false, false, message)
// }
// }))
// }
// items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in
// actionSheet?.dismissAnimated()
// UIPasteboard.general.string = number
// }))
// actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
// ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
// actionSheet?.dismissAnimated()
// })
// ])])
// strongSelf.present(actionSheet, in: .window(.root))
// }
// }))
strongSelf.chatDisplayNode.dismissInput()
}
}

View File

@@ -36,6 +36,7 @@ import LocationResources
import LocationUI
import Geocoding
import TextFormat
import StatisticsUI
protocol PeerInfoScreenItem: class {
var id: AnyHashable { get }
@@ -2215,6 +2216,13 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
}
} else if let channel = peer as? TelegramChannel {
if let cachedData = self.data?.cachedData as? CachedChannelData, cachedData.flags.contains(.canViewStats) {
items.append(ActionSheetButtonItem(title: presentationData.strings.ChannelInfo_Stats, color: .accent, action: { [weak self] in
dismissAction()
self?.openStats()
}))
}
var canReport = true
if channel.isVerified {
canReport = false
@@ -2691,6 +2699,15 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
})
}
private func openStats() {
guard let controller = self.controller, let data = self.data, let peer = data.peer, let cachedData = data.cachedData else {
return
}
self.view.endEditing(true)
controller.push(channelStatsController(context: self.context, peer: peer, cachedPeerData: cachedData))
}
private func openReport(user: Bool) {
guard let controller = self.controller else {
return