Swiftgram/submodules/StatisticsUI/Sources/GroupStatsController.swift
2020-06-20 03:13:03 +03:00

743 lines
41 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Foundation
import UIKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import SyncCore
import MapKit
import TelegramPresentationData
import TelegramUIPreferences
import TelegramStringFormatting
import ItemListUI
import PresentationDataUtils
import AccountContext
import PresentationDataUtils
import AppBundle
import GraphUI
import ItemListPeerItem
private final class GroupStatsControllerArguments {
let context: AccountContext
let loadDetailedGraph: (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>
let openPeer: (PeerId) -> Void
let openPeerHistory: (PeerId) -> Void
let openPeerAdminActions: (PeerId) -> Void
let promotePeer: (PeerId) -> Void
let setPostersPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
let setAdminsPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
let setInvitersPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openPeer: @escaping (PeerId) -> Void, openPeerHistory: @escaping (PeerId) -> Void, openPeerAdminActions: @escaping (PeerId) -> Void, promotePeer: @escaping (PeerId) -> Void, setPostersPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setAdminsPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setInvitersPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void) {
self.context = context
self.loadDetailedGraph = loadDetailedGraph
self.openPeer = openPeer
self.openPeerHistory = openPeerHistory
self.openPeerAdminActions = openPeerAdminActions
self.promotePeer = promotePeer
self.setPostersPeerIdWithRevealedOptions = setPostersPeerIdWithRevealedOptions
self.setAdminsPeerIdWithRevealedOptions = setAdminsPeerIdWithRevealedOptions
self.setInvitersPeerIdWithRevealedOptions = setInvitersPeerIdWithRevealedOptions
}
}
private enum StatsSection: Int32 {
case overview
case growth
case members
case newMembersBySource
case languages
case messages
case actions
case topHours
case topWeekdays
case topPosters
case topAdmins
case topInviters
}
private enum StatsEntry: ItemListNodeEntry {
case overviewTitle(PresentationTheme, String, String)
case overview(PresentationTheme, GroupStats)
case growthTitle(PresentationTheme, String)
case growthGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case membersTitle(PresentationTheme, String)
case membersGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case newMembersBySourceTitle(PresentationTheme, String)
case newMembersBySourceGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case languagesTitle(PresentationTheme, String)
case languagesGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case messagesTitle(PresentationTheme, String)
case messagesGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case actionsTitle(PresentationTheme, String)
case actionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case topHoursTitle(PresentationTheme, String)
case topHoursGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case topWeekdaysTitle(PresentationTheme, String)
case topWeekdaysGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case topPostersTitle(PresentationTheme, String, String)
case topPoster(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopPoster, Bool)
case topAdminsTitle(PresentationTheme, String, String)
case topAdmin(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopAdmin, Bool)
case topInvitersTitle(PresentationTheme, String, String)
case topInviter(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopInviter, Bool)
var section: ItemListSectionId {
switch self {
case .overviewTitle, .overview:
return StatsSection.overview.rawValue
case .growthTitle, .growthGraph:
return StatsSection.growth.rawValue
case .membersTitle, .membersGraph:
return StatsSection.members.rawValue
case .newMembersBySourceTitle, .newMembersBySourceGraph:
return StatsSection.newMembersBySource.rawValue
case .languagesTitle, .languagesGraph:
return StatsSection.languages.rawValue
case .messagesTitle, . messagesGraph:
return StatsSection.messages.rawValue
case .actionsTitle, .actionsGraph:
return StatsSection.actions.rawValue
case .topHoursTitle, .topHoursGraph:
return StatsSection.topHours.rawValue
case .topWeekdaysTitle, .topWeekdaysGraph:
return StatsSection.topWeekdays.rawValue
case .topPostersTitle, .topPoster:
return StatsSection.topPosters.rawValue
case .topAdminsTitle, .topAdmin:
return StatsSection.topAdmins.rawValue
case .topInvitersTitle, .topInviter:
return StatsSection.topInviters.rawValue
}
}
var stableId: Int32 {
switch self {
case .overviewTitle:
return 0
case .overview:
return 1
case .growthTitle:
return 2
case .growthGraph:
return 3
case .membersTitle:
return 4
case .membersGraph:
return 5
case .newMembersBySourceTitle:
return 6
case .newMembersBySourceGraph:
return 7
case .languagesTitle:
return 8
case .languagesGraph:
return 9
case .messagesTitle:
return 10
case .messagesGraph:
return 11
case .actionsTitle:
return 12
case .actionsGraph:
return 13
case .topHoursTitle:
return 14
case .topHoursGraph:
return 15
case .topWeekdaysTitle:
return 16
case .topWeekdaysGraph:
return 17
case .topPostersTitle:
return 1000
case let .topPoster(index, _, _, _, _, _, _):
return 1001 + index
case .topAdminsTitle:
return 2000
case let .topAdmin(index, _, _, _, _, _, _):
return 2001 + index
case .topInvitersTitle:
return 3000
case let .topInviter(index, _, _, _, _, _, _):
return 30001 + index
}
}
static func ==(lhs: StatsEntry, rhs: StatsEntry) -> Bool {
switch lhs {
case let .overviewTitle(lhsTheme, lhsText, lhsDates):
if case let .overviewTitle(rhsTheme, rhsText, rhsDates) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsDates == rhsDates {
return true
} else {
return false
}
case let .overview(lhsTheme, lhsStats):
if case let .overview(rhsTheme, rhsStats) = rhs, lhsTheme === rhsTheme, lhsStats == rhsStats {
return true
} else {
return false
}
case let .growthTitle(lhsTheme, lhsText):
if case let .growthTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .growthGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
if case let .growthGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
return true
} else {
return false
}
case let .membersTitle(lhsTheme, lhsText):
if case let .membersTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .membersGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
if case let .membersGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
return true
} else {
return false
}
case let .newMembersBySourceTitle(lhsTheme, lhsText):
if case let .newMembersBySourceTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .newMembersBySourceGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
if case let .newMembersBySourceGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
return true
} else {
return false
}
case let .languagesTitle(lhsTheme, lhsText):
if case let .languagesTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .languagesGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
if case let .languagesGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
return true
} else {
return false
}
case let .messagesTitle(lhsTheme, lhsText):
if case let .messagesTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .messagesGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
if case let .messagesGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
return true
} else {
return false
}
case let .actionsTitle(lhsTheme, lhsText):
if case let .actionsTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .actionsGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
if case let .actionsGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
return true
} else {
return false
}
case let .topHoursTitle(lhsTheme, lhsText):
if case let .topHoursTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .topHoursGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
if case let .topHoursGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
return true
} else {
return false
}
case let .topWeekdaysTitle(lhsTheme, lhsText):
if case let .topWeekdaysTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .topWeekdaysGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
if case let .topWeekdaysGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
return true
} else {
return false
}
case let .topPostersTitle(lhsTheme, lhsText, lhsDates):
if case let .topPostersTitle(rhsTheme, rhsText, rhsDates) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsDates == rhsDates {
return true
} else {
return false
}
case let .topPoster(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopPoster, lhsRevealed):
if case let .topPoster(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopPoster, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopPoster == rhsTopPoster, lhsRevealed == rhsRevealed {
return true
} else {
return false
}
case let .topAdminsTitle(lhsTheme, lhsText, lhsDates):
if case let .topAdminsTitle(rhsTheme, rhsText, rhsDates) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsDates == rhsDates {
return true
} else {
return false
}
case let .topAdmin(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopAdmin, lhsRevealed):
if case let .topAdmin(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopAdmin, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopAdmin == rhsTopAdmin, lhsRevealed == rhsRevealed {
return true
} else {
return false
}
case let .topInvitersTitle(lhsTheme, lhsText, lhsDates):
if case let .topInvitersTitle(rhsTheme, rhsText, rhsDates) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsDates == rhsDates {
return true
} else {
return false
}
case let .topInviter(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopInviter, lhsRevealed):
if case let .topInviter(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopInviter, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopInviter == rhsTopInviter, lhsRevealed == rhsRevealed {
return true
} else {
return false
}
}
}
static func <(lhs: StatsEntry, rhs: StatsEntry) -> Bool {
return lhs.stableId < rhs.stableId
}
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! GroupStatsControllerArguments
switch self {
case let .overviewTitle(_, text, dates):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: dates, color: .generic), sectionId: self.section)
case let .growthTitle(_, text),
let .membersTitle(_, text),
let .newMembersBySourceTitle(_, text),
let .languagesTitle(_, text),
let .messagesTitle(_, text),
let .actionsTitle(_, text),
let .topHoursTitle(_, text),
let .topWeekdaysTitle(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .topPostersTitle(_, text, dates),
let .topAdminsTitle(_, text, dates),
let .topInvitersTitle(_, text, dates):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: dates, color: .generic), sectionId: self.section)
case let .overview(_, stats):
return StatsOverviewItem(presentationData: presentationData, stats: stats, sectionId: self.section, style: .blocks)
case let .growthGraph(_, _, _, graph, type),
let .membersGraph(_, _, _, graph, type),
let .newMembersBySourceGraph(_, _, _, graph, type),
let .languagesGraph(_, _, _, graph, type),
let .messagesGraph(_, _, _, graph, type),
let .actionsGraph(_, _, _, graph, type),
let .topHoursGraph(_, _, _, graph, type),
let .topWeekdaysGraph(_, _, _, graph, type):
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks)
case let .topPoster(_, _, strings, dateTimeFormat, peer, topPoster, revealed):
var textComponents: [String] = []
if topPoster.messageCount > 0 {
textComponents.append(strings.Stats_GroupTopPosterMessages(topPoster.messageCount))
if topPoster.averageChars > 0 {
textComponents.append(strings.Stats_GroupTopPosterChars(topPoster.averageChars))
}
}
var options: [ItemListPeerItemRevealOption] = []
options.append(ItemListPeerItemRevealOption(type: .accent, title: strings.Stats_GroupTopPoster_History, action: {
arguments.openPeerHistory(peer.id)
}))
options.append(ItemListPeerItemRevealOption(type: .neutral, title: strings.Stats_GroupTopPoster_Promote, action: {
arguments.promotePeer(peer.id)
}))
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", ")), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: {
arguments.openPeer(peer.id)
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
arguments.setPostersPeerIdWithRevealedOptions(peerId, fromPeerId)
}, removePeer: { _ in })
case let .topAdmin(_, _, strings, dateTimeFormat, peer, topAdmin, revealed):
var textComponents: [String] = []
if topAdmin.deletedCount > 0 {
textComponents.append(strings.Stats_GroupTopAdminDeletions(topAdmin.deletedCount))
}
if topAdmin.kickedCount > 0 {
textComponents.append(strings.Stats_GroupTopAdminKicks(topAdmin.kickedCount))
}
if topAdmin.bannedCount > 0 {
textComponents.append(strings.Stats_GroupTopAdminBans(topAdmin.bannedCount))
}
var options: [ItemListPeerItemRevealOption] = []
options.append(ItemListPeerItemRevealOption(type: .accent, title: strings.Stats_GroupTopAdmin_Actions, action: {
arguments.openPeerAdminActions(peer.id)
}))
options.append(ItemListPeerItemRevealOption(type: .neutral, title: strings.Stats_GroupTopAdmin_Promote, action: {
arguments.promotePeer(peer.id)
}))
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", ")), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: {
arguments.openPeer(peer.id)
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
arguments.setAdminsPeerIdWithRevealedOptions(peerId, fromPeerId)
}, removePeer: { _ in })
case let .topInviter(_, _, strings, dateTimeFormat, peer, topInviter, revealed):
var textComponents: [String] = []
textComponents.append(strings.Stats_GroupTopInviterInvites(topInviter.inviteCount))
var options: [ItemListPeerItemRevealOption] = []
options.append(ItemListPeerItemRevealOption(type: .accent, title: strings.Stats_GroupTopPoster_History, action: {
arguments.openPeerHistory(peer.id)
}))
options.append(ItemListPeerItemRevealOption(type: .neutral, title: strings.Stats_GroupTopPoster_Promote, action: {
arguments.promotePeer(peer.id)
}))
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", ")), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: {
arguments.openPeer(peer.id)
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
arguments.setInvitersPeerIdWithRevealedOptions(peerId, fromPeerId)
}, removePeer: { _ in })
}
}
}
private func groupStatsControllerEntries(state: GroupStatsState, data: GroupStats?, peers: [PeerId: Peer]?, presentationData: PresentationData) -> [StatsEntry] {
var entries: [StatsEntry] = []
if let data = data {
let minDate = stringForDate(timestamp: data.period.minDate, strings: presentationData.strings)
let maxDate = stringForDate(timestamp: data.period.maxDate, strings: presentationData.strings)
let dates = "\(minDate) \(maxDate)"
entries.append(.overviewTitle(presentationData.theme, presentationData.strings.Stats_Overview, dates))
entries.append(.overview(presentationData.theme, data))
if !data.growthGraph.isEmpty {
entries.append(.growthTitle(presentationData.theme, presentationData.strings.Stats_GroupGrowthTitle))
entries.append(.growthGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.growthGraph, .lines))
}
if !data.membersGraph.isEmpty {
entries.append(.membersTitle(presentationData.theme, presentationData.strings.Stats_GroupMembersTitle))
entries.append(.membersGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.membersGraph, .lines))
}
if !data.newMembersBySourceGraph.isEmpty {
entries.append(.newMembersBySourceTitle(presentationData.theme, presentationData.strings.Stats_GroupNewMembersBySourceTitle))
entries.append(.newMembersBySourceGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.newMembersBySourceGraph, .bars))
}
if !data.languagesGraph.isEmpty {
entries.append(.languagesTitle(presentationData.theme, presentationData.strings.Stats_GroupLanguagesTitle))
entries.append(.languagesGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.languagesGraph, .pie))
}
if !data.messagesGraph.isEmpty {
entries.append(.messagesTitle(presentationData.theme, presentationData.strings.Stats_GroupMessagesTitle))
entries.append(.messagesGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.messagesGraph, .bars))
}
if !data.actionsGraph.isEmpty {
entries.append(.actionsTitle(presentationData.theme, presentationData.strings.Stats_GroupActionsTitle))
entries.append(.actionsGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.actionsGraph, .lines))
}
if !data.topHoursGraph.isEmpty {
entries.append(.topHoursTitle(presentationData.theme, presentationData.strings.Stats_GroupTopHoursTitle))
entries.append(.topHoursGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.topHoursGraph, .hourlyStep))
}
if !data.topWeekdaysGraph.isEmpty {
entries.append(.topWeekdaysTitle(presentationData.theme, presentationData.strings.Stats_GroupTopWeekdaysTitle))
entries.append(.topWeekdaysGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.topWeekdaysGraph, .pie))
}
if let peers = peers {
if !data.topPosters.isEmpty {
entries.append(.topPostersTitle(presentationData.theme, presentationData.strings.Stats_GroupTopPostersTitle, dates))
var index: Int32 = 0
for topPoster in data.topPosters {
if let peer = peers[topPoster.peerId], topPoster.messageCount > 0 {
entries.append(.topPoster(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topPoster, topPoster.peerId == state.posterPeerIdWithRevealedOptions))
index += 1
}
}
}
if !data.topAdmins.isEmpty {
entries.append(.topAdminsTitle(presentationData.theme, presentationData.strings.Stats_GroupTopAdminsTitle, dates))
var index: Int32 = 0
for topAdmin in data.topAdmins {
if let peer = peers[topAdmin.peerId], (topAdmin.deletedCount + topAdmin.kickedCount + topAdmin.bannedCount) > 0 {
entries.append(.topAdmin(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topAdmin, topAdmin.peerId == state.adminPeerIdWithRevealedOptions))
index += 1
}
}
}
if !data.topInviters.isEmpty {
entries.append(.topInvitersTitle(presentationData.theme, presentationData.strings.Stats_GroupTopInvitersTitle, dates))
var index: Int32 = 0
for topInviter in data.topInviters {
if let peer = peers[topInviter.peerId], topInviter.inviteCount > 0 {
entries.append(.topInviter(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topInviter, topInviter.peerId == state.inviterPeerIdWithRevealedOptions))
index += 1
}
}
}
}
}
return entries
}
private struct GroupStatsState: Equatable {
let posterPeerIdWithRevealedOptions: PeerId?
let adminPeerIdWithRevealedOptions: PeerId?
let inviterPeerIdWithRevealedOptions: PeerId?
init() {
self.posterPeerIdWithRevealedOptions = nil
self.adminPeerIdWithRevealedOptions = nil
self.inviterPeerIdWithRevealedOptions = nil
}
init(posterPeerIdWithRevealedOptions: PeerId?, adminPeerIdWithRevealedOptions: PeerId?, inviterPeerIdWithRevealedOptions: PeerId?) {
self.posterPeerIdWithRevealedOptions = posterPeerIdWithRevealedOptions
self.adminPeerIdWithRevealedOptions = adminPeerIdWithRevealedOptions
self.inviterPeerIdWithRevealedOptions = inviterPeerIdWithRevealedOptions
}
static func ==(lhs: GroupStatsState, rhs: GroupStatsState) -> Bool {
if lhs.posterPeerIdWithRevealedOptions != rhs.posterPeerIdWithRevealedOptions {
return false
}
if lhs.adminPeerIdWithRevealedOptions != rhs.adminPeerIdWithRevealedOptions {
return false
}
if lhs.inviterPeerIdWithRevealedOptions != rhs.inviterPeerIdWithRevealedOptions {
return false
}
return true
}
func withUpdatedPosterPeerIdWithRevealedOptions(_ posterPeerIdWithRevealedOptions: PeerId?) -> GroupStatsState {
return GroupStatsState(posterPeerIdWithRevealedOptions: posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: posterPeerIdWithRevealedOptions != nil ? nil : self.adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: posterPeerIdWithRevealedOptions != nil ? nil : self.inviterPeerIdWithRevealedOptions)
}
func withUpdatedAdminPeerIdWithRevealedOptions(_ adminPeerIdWithRevealedOptions: PeerId?) -> GroupStatsState {
return GroupStatsState(posterPeerIdWithRevealedOptions: adminPeerIdWithRevealedOptions != nil ? nil : self.posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: adminPeerIdWithRevealedOptions != nil ? nil : self.inviterPeerIdWithRevealedOptions)
}
func withUpdatedInviterPeerIdWithRevealedOptions(_ inviterPeerIdWithRevealedOptions: PeerId?) -> GroupStatsState {
return GroupStatsState(posterPeerIdWithRevealedOptions: inviterPeerIdWithRevealedOptions != nil ? nil : self.posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: inviterPeerIdWithRevealedOptions != nil ? nil : self.adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: inviterPeerIdWithRevealedOptions)
}
}
public func groupStatsController(context: AccountContext, peerId: PeerId, cachedPeerData: CachedPeerData) -> ViewController {
let statePromise = ValuePromise(GroupStatsState())
let stateValue = Atomic(value: GroupStatsState())
let updateState: ((GroupStatsState) -> GroupStatsState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
let actionsDisposable = DisposableSet()
let dataPromise = Promise<GroupStats?>(nil)
let peersPromise = Promise<[PeerId: Peer]?>(nil)
var datacenterId: Int32 = 0
if let cachedData = cachedPeerData as? CachedChannelData {
datacenterId = cachedData.statsDatacenterId
}
var openPeerImpl: ((PeerId) -> Void)?
var openPeerHistoryImpl: ((PeerId) -> Void)?
var openPeerAdminActionsImpl: ((PeerId) -> Void)?
var promotePeerImpl: ((PeerId) -> Void)?
let peerView = Promise<PeerView>()
peerView.set(context.account.viewTracker.peerView(peerId, updateData: true))
let statsContext = GroupStatsContext(postbox: context.account.postbox, network: context.account.network, datacenterId: datacenterId, peerId: peerId)
let dataSignal: Signal<GroupStats?, NoError> = statsContext.state
|> map { state in
return state.stats
} |> afterNext({ [weak statsContext] stats in
if let statsContext = statsContext, let stats = stats {
if case .OnDemand = stats.topWeekdaysGraph {
statsContext.loadGrowthGraph()
statsContext.loadMembersGraph()
statsContext.loadNewMembersBySourceGraph()
statsContext.loadLanguagesGraph()
statsContext.loadMessagesGraph()
statsContext.loadActionsGraph()
statsContext.loadTopHoursGraph()
statsContext.loadTopWeekdaysGraph()
}
}
})
dataPromise.set(.single(nil) |> then(dataSignal))
peersPromise.set(.single(nil) |> then(dataPromise.get()
|> filter { value in
return value != nil
}
|> take(1)
|> map { stats -> [PeerId]? in
guard let stats = stats else {
return nil
}
var peerIds = Set<PeerId>()
peerIds.formUnion(stats.topPosters.map { $0.peerId })
peerIds.formUnion(stats.topAdmins.map { $0.peerId })
peerIds.formUnion(stats.topInviters.map { $0.peerId })
return Array(peerIds)
}
|> mapToSignal { peerIds -> Signal<[PeerId: Peer]?, NoError> in
return context.account.postbox.transaction { transaction -> [PeerId: Peer]? in
var peers: [PeerId: Peer] = [:]
if let peerIds = peerIds {
for peerId in peerIds {
if let peer = transaction.getPeer(peerId) {
peers[peerId] = peer
}
}
}
return peers
}
}))
let arguments = GroupStatsControllerArguments(context: context, loadDetailedGraph: { graph, x -> Signal<StatsGraph?, NoError> in
return statsContext.loadDetailedGraph(graph, x: x)
}, openPeer: { peerId in
openPeerImpl?(peerId)
}, openPeerHistory: { peerId in
openPeerHistoryImpl?(peerId)
}, openPeerAdminActions: { peerId in
openPeerAdminActionsImpl?(peerId)
}, promotePeer: { peerId in
promotePeerImpl?(peerId)
}, setPostersPeerIdWithRevealedOptions: { peerId, fromPeerId in
updateState { state in
if (peerId == nil && fromPeerId == state.posterPeerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) {
return state.withUpdatedPosterPeerIdWithRevealedOptions(peerId)
} else {
return state
}
}
}, setAdminsPeerIdWithRevealedOptions: { peerId, fromPeerId in
updateState { state in
if (peerId == nil && fromPeerId == state.adminPeerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) {
return state.withUpdatedAdminPeerIdWithRevealedOptions(peerId)
} else {
return state
}
}
}, setInvitersPeerIdWithRevealedOptions: { peerId, fromPeerId in
updateState { state in
if (peerId == nil && fromPeerId == state.inviterPeerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) {
return state.withUpdatedInviterPeerIdWithRevealedOptions(peerId)
} else {
return state
}
}
})
let longLoadingSignal: Signal<Bool, NoError> = .single(false) |> then(.single(true) |> delay(2.0, queue: Queue.mainQueue()))
let previousData = Atomic<GroupStats?>(value: nil)
let signal = combineLatest(statePromise.get(), context.sharedContext.presentationData, dataPromise.get(), peersPromise.get(), longLoadingSignal)
|> deliverOnMainQueue
|> map { state, presentationData, data, peers, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in
let previous = previousData.swap(data)
var emptyStateItem: ItemListControllerEmptyStateItem?
if data == nil {
if longLoading {
emptyStateItem = StatsEmptyStateItem(theme: presentationData.theme, strings: presentationData.strings)
} else {
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: groupStatsControllerEntries(state: state, data: data, peers: peers, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false)
return (controllerState, (listState, arguments))
}
|> afterDisposed {
actionsDisposable.dispose()
let _ = statsContext.state
}
let controller = ItemListController(context: context, state: signal)
controller.contentOffsetChanged = { [weak controller] _, _ in
controller?.forEachItemNode({ itemNode in
if let itemNode = itemNode as? StatsGraphItemNode {
itemNode.resetInteraction()
}
})
}
controller.didDisappear = { [weak controller] _ in
controller?.clearItemNodesHighlight(animated: true)
}
openPeerImpl = { [weak controller] peerId in
if let navigationController = controller?.navigationController as? NavigationController {
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|> take(1)
|> deliverOnMainQueue).start(next: { peer in
if let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
navigationController.pushViewController(controller)
}
})
}
}
openPeerHistoryImpl = { [weak controller] participantPeerId in
if let navigationController = controller?.navigationController as? NavigationController {
let _ = (context.account.postbox.loadedPeerWithId(participantPeerId)
|> take(1)
|> deliverOnMainQueue).start(next: { peer in
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, chatController: nil, context: context, chatLocation: .peer(peerId), subject: nil, botStart: nil, updateTextInputState: nil, activateInput: false, keepStack: .always, useExisting: false, purposefulAction: nil, scrollToEndIfExists: false, activateMessageSearch: (.member(peer), ""), animated: true))
})
}
}
openPeerAdminActionsImpl = { [weak controller] participantPeerId in
if let navigationController = controller?.navigationController as? NavigationController {
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|> take(1)
|> deliverOnMainQueue).start(next: { peer in
let controller = context.sharedContext.makeChatRecentActionsController(context: context, peer: peer)
navigationController.pushViewController(controller)
})
}
}
promotePeerImpl = { [weak controller] participantPeerId in
if let navigationController = controller?.navigationController as? NavigationController {
let _ = (fetchChannelParticipant(account: context.account, peerId: peerId, participantId: participantPeerId)
|> take(1)
|> deliverOnMainQueue).start(next: { participant in
if let participant = participant, let controller = context.sharedContext.makeChannelAdminController(context: context, peerId: peerId, adminId: participantPeerId, initialParticipant: participant) {
navigationController.pushViewController(controller)
}
})
}
}
return controller
}