Statistics improvements

This commit is contained in:
Ilya Laktyushin 2023-11-17 07:22:25 +04:00
parent 1fced74098
commit 13baadc3e7
29 changed files with 1467 additions and 119 deletions

View File

@ -10498,7 +10498,13 @@ Sorry for the inconvenience.";
"Stats.MessageReactionsTitle" = "REACTIONS"; "Stats.MessageReactionsTitle" = "REACTIONS";
"MediaEditor.RemoveVideo" = "Remove Video";
"Conversation.LaunchApp" = "LAUNCH APP"; "Conversation.LaunchApp" = "LAUNCH APP";
"Message.AdSponsoredLabel" = "Sponsored"; "Message.AdSponsoredLabel" = "Sponsored";
"Message.AdRecommendedLabel" = "Recommended"; "Message.AdRecommendedLabel" = "Recommended";
"Stats.StoryTitle" = "Story Statistics";
"Channel.Info.Settings" = "Channel Settings";

View File

@ -211,9 +211,9 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
if peer.unreadCount > 0 { if peer.unreadCount > 0 {
badge = ContactsPeerItemBadge(count: peer.unreadCount, type: isMuted ? .inactive : .active) badge = ContactsPeerItemBadge(count: peer.unreadCount, type: isMuted ? .inactive : .active)
} }
return ContactsPeerItem( return ContactsPeerItem(
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder), presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat),
sortOrder: nameSortOrder, sortOrder: nameSortOrder,
displayOrder: nameDisplayOrder, displayOrder: nameDisplayOrder,
context: context, context: context,

View File

@ -355,7 +355,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
} }
} }
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListAdditionalCategoryItem( return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListAdditionalCategoryItem(
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder), presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat),
context: context, context: context,
title: title, title: title,
image: image, image: image,
@ -569,7 +569,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
} }
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem( return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder), presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat),
sortOrder: presentationData.nameSortOrder, sortOrder: presentationData.nameSortOrder,
displayOrder: presentationData.nameDisplayOrder, displayOrder: presentationData.nameDisplayOrder,
context: context, context: context,
@ -608,7 +608,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
let status: ContactsPeerItemStatus = .none let status: ContactsPeerItemStatus = .none
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem( return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder), presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat),
sortOrder: presentationData.nameSortOrder, sortOrder: presentationData.nameSortOrder,
displayOrder: presentationData.nameDisplayOrder, displayOrder: presentationData.nameDisplayOrder,
context: context, context: context,
@ -670,7 +670,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
let peerContent: ContactsPeerItemPeer = .peer(peer: contactEntry.peer, chatPeer: contactEntry.peer) let peerContent: ContactsPeerItemPeer = .peer(peer: contactEntry.peer, chatPeer: contactEntry.peer)
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem( return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder), presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat),
sortOrder: presentationData.nameSortOrder, sortOrder: presentationData.nameSortOrder,
displayOrder: presentationData.nameDisplayOrder, displayOrder: presentationData.nameDisplayOrder,
context: context, context: context,
@ -889,7 +889,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
} }
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem( return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder), presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat),
sortOrder: presentationData.nameSortOrder, sortOrder: presentationData.nameSortOrder,
displayOrder: presentationData.nameDisplayOrder, displayOrder: presentationData.nameDisplayOrder,
context: context, context: context,
@ -928,7 +928,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
let status: ContactsPeerItemStatus = .none let status: ContactsPeerItemStatus = .none
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem( return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder), presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat),
sortOrder: presentationData.nameSortOrder, sortOrder: presentationData.nameSortOrder,
displayOrder: presentationData.nameDisplayOrder, displayOrder: presentationData.nameDisplayOrder,
context: context, context: context,
@ -990,7 +990,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
let peerContent: ContactsPeerItemPeer = .peer(peer: contactEntry.peer, chatPeer: contactEntry.peer) let peerContent: ContactsPeerItemPeer = .peer(peer: contactEntry.peer, chatPeer: contactEntry.peer)
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem( return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder), presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat),
sortOrder: presentationData.nameSortOrder, sortOrder: presentationData.nameSortOrder,
displayOrder: presentationData.nameDisplayOrder, displayOrder: presentationData.nameDisplayOrder,
context: context, context: context,
@ -1057,7 +1057,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
} }
} }
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListAdditionalCategoryItem( return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListAdditionalCategoryItem(
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder), presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat),
context: context, context: context,
title: title, title: title,
image: image, image: image,

View File

@ -161,12 +161,14 @@ public final class ItemListPresentationData: Equatable {
public let fontSize: PresentationFontSize public let fontSize: PresentationFontSize
public let strings: PresentationStrings public let strings: PresentationStrings
public let nameDisplayOrder: PresentationPersonNameOrder public let nameDisplayOrder: PresentationPersonNameOrder
public let dateTimeFormat: PresentationDateTimeFormat
public init(theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) { public init(theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat) {
self.theme = theme self.theme = theme
self.fontSize = fontSize self.fontSize = fontSize
self.strings = strings self.strings = strings
self.nameDisplayOrder = nameDisplayOrder self.nameDisplayOrder = nameDisplayOrder
self.dateTimeFormat = dateTimeFormat
} }
public static func ==(lhs: ItemListPresentationData, rhs: ItemListPresentationData) -> Bool { public static func ==(lhs: ItemListPresentationData, rhs: ItemListPresentationData) -> Bool {
@ -182,6 +184,9 @@ public final class ItemListPresentationData: Equatable {
if lhs.nameDisplayOrder != rhs.nameDisplayOrder { if lhs.nameDisplayOrder != rhs.nameDisplayOrder {
return false return false
} }
if lhs.dateTimeFormat != rhs.dateTimeFormat {
return false
}
return true return true
} }
} }
@ -232,6 +237,6 @@ public extension PresentationFontSize {
public extension ItemListPresentationData { public extension ItemListPresentationData {
convenience init(_ presentationData: PresentationData) { convenience init(_ presentationData: PresentationData) {
self.init(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder) self.init(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat)
} }
} }

View File

@ -13,6 +13,7 @@ swift_library(
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display", "//submodules/Display:Display",
"//submodules/ComponentFlow",
"//submodules/Postbox:Postbox", "//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore", "//submodules/TelegramCore:TelegramCore",
"//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/TelegramPresentationData:TelegramPresentationData",
@ -35,6 +36,7 @@ swift_library(
"//submodules/PremiumUI:PremiumUI", "//submodules/PremiumUI:PremiumUI",
"//submodules/InviteLinksUI:InviteLinksUI", "//submodules/InviteLinksUI:InviteLinksUI",
"//submodules/ShareController:ShareController", "//submodules/ShareController:ShareController",
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -27,7 +27,7 @@ private let initialBoostersDisplayedLimit: Int32 = 5
private final class ChannelStatsControllerArguments { private final class ChannelStatsControllerArguments {
let context: AccountContext let context: AccountContext
let loadDetailedGraph: (StatsGraph, Int64) -> Signal<StatsGraph?, NoError> let loadDetailedGraph: (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>
let openMessageStats: (MessageId) -> Void let openPostStats: (EnginePeer, StatsPostItem) -> Void
let contextAction: (MessageId, ASDisplayNode, ContextGesture?) -> Void let contextAction: (MessageId, ASDisplayNode, ContextGesture?) -> Void
let copyBoostLink: (String) -> Void let copyBoostLink: (String) -> Void
let shareBoostLink: (String) -> Void let shareBoostLink: (String) -> Void
@ -37,10 +37,10 @@ private final class ChannelStatsControllerArguments {
let createPrepaidGiveaway: (PrepaidGiveaway) -> Void let createPrepaidGiveaway: (PrepaidGiveaway) -> Void
let updateGiftsSelected: (Bool) -> Void let updateGiftsSelected: (Bool) -> Void
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openMessage: @escaping (MessageId) -> Void, contextAction: @escaping (MessageId, ASDisplayNode, ContextGesture?) -> Void, copyBoostLink: @escaping (String) -> Void, shareBoostLink: @escaping (String) -> Void, openBoost: @escaping (ChannelBoostersContext.State.Boost) -> Void, expandBoosters: @escaping () -> Void, openGifts: @escaping () -> Void, createPrepaidGiveaway: @escaping (PrepaidGiveaway) -> Void, updateGiftsSelected: @escaping (Bool) -> Void) { init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openPostStats: @escaping (EnginePeer, StatsPostItem) -> Void, contextAction: @escaping (MessageId, ASDisplayNode, ContextGesture?) -> Void, copyBoostLink: @escaping (String) -> Void, shareBoostLink: @escaping (String) -> Void, openBoost: @escaping (ChannelBoostersContext.State.Boost) -> Void, expandBoosters: @escaping () -> Void, openGifts: @escaping () -> Void, createPrepaidGiveaway: @escaping (PrepaidGiveaway) -> Void, updateGiftsSelected: @escaping (Bool) -> Void) {
self.context = context self.context = context
self.loadDetailedGraph = loadDetailedGraph self.loadDetailedGraph = loadDetailedGraph
self.openMessageStats = openMessage self.openPostStats = openPostStats
self.contextAction = contextAction self.contextAction = contextAction
self.copyBoostLink = copyBoostLink self.copyBoostLink = copyBoostLink
self.shareBoostLink = shareBoostLink self.shareBoostLink = shareBoostLink
@ -75,6 +75,45 @@ private enum StatsSection: Int32 {
case gifts case gifts
} }
enum StatsPostItem: Equatable {
static func == (lhs: StatsPostItem, rhs: StatsPostItem) -> Bool {
switch lhs {
case let .message(lhsMessage):
if case let .message(rhsMessage) = rhs {
return lhsMessage.id == rhsMessage.id
} else {
return false
}
case let .story(lhsStory):
if case let .story(rhsStory) = rhs, lhsStory == rhsStory {
return true
} else {
return false
}
}
}
case message(Message)
case story(EngineStoryItem)
var isStory: Bool {
if case .story = self {
return true
} else {
return false
}
}
var timestamp: Int32 {
switch self {
case let .message(message):
return message.timestamp
case let .story(story):
return story.timestamp
}
}
}
private enum StatsEntry: ItemListNodeEntry { private enum StatsEntry: ItemListNodeEntry {
case overviewTitle(PresentationTheme, String, String) case overviewTitle(PresentationTheme, String, String)
case overview(PresentationTheme, ChannelStats) case overview(PresentationTheme, ChannelStats)
@ -116,7 +155,7 @@ private enum StatsEntry: ItemListNodeEntry {
case instantPageInteractionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType) case instantPageInteractionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case postsTitle(PresentationTheme, String) case postsTitle(PresentationTheme, String)
case post(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Message, ChannelStatsMessageInteractions) case post(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, StatsPostItem, ChannelStatsMessageInteractions)
case boostLevel(PresentationTheme, Int32, Int32, CGFloat) case boostLevel(PresentationTheme, Int32, Int32, CGFloat)
@ -242,7 +281,7 @@ private enum StatsEntry: ItemListNodeEntry {
return 25 return 25
case .postsTitle: case .postsTitle:
return 26 return 26
case let .post(index, _, _, _, _, _): case let .post(index, _, _, _, _, _, _):
return 27 + index return 27 + index
case .boostLevel: case .boostLevel:
return 2000 return 2000
@ -445,8 +484,8 @@ private enum StatsEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .post(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsMessage, lhsInteractions): case let .post(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsPost, lhsInteractions):
if case let .post(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsMessage, rhsInteractions) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsMessage.id == rhsMessage.id, lhsInteractions == rhsInteractions { if case let .post(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsPost, rhsInteractions) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsPost == rhsPost, lhsInteractions == rhsInteractions {
return true return true
} else { } else {
return false return false
@ -610,12 +649,14 @@ private enum StatsEntry: ItemListNodeEntry {
} }
}) })
}, sectionId: self.section, style: .blocks) }, sectionId: self.section, style: .blocks)
case let .post(_, _, _, _, message, interactions): case let .post(_, _, _, _, peer, post, interactions):
return StatsMessageItem(context: arguments.context, presentationData: presentationData, message: message, views: interactions.views, forwards: interactions.forwards, sectionId: self.section, style: .blocks, action: { return StatsMessageItem(context: arguments.context, presentationData: presentationData, peer: peer, item: post, views: interactions.views, reactions: interactions.reactions, forwards: interactions.forwards, sectionId: self.section, style: .blocks, action: {
arguments.openMessageStats(message.id) arguments.openPostStats(EnginePeer(peer), post)
}, contextAction: { node, gesture in }, contextAction: !post.isStory ? { node, gesture in
arguments.contextAction(message.id, node, gesture) if case let .message(message) = post {
}) arguments.contextAction(message.id, node, gesture)
}
} : nil)
case let .boosterTabs(_, boostText, giftText, giftSelected): case let .boosterTabs(_, boostText, giftText, giftSelected):
return BoostsTabsItem(theme: presentationData.theme, boostsText: boostText, giftsText: giftText, selectedTab: giftSelected ? .gifts : .boosts, sectionId: self.section, selectionUpdated: { tab in return BoostsTabsItem(theme: presentationData.theme, boostsText: boostText, giftsText: giftText, selectedTab: giftSelected ? .gifts : .boosts, sectionId: self.section, selectionUpdated: { tab in
arguments.updateGiftsSelected(tab == .gifts) arguments.updateGiftsSelected(tab == .gifts)
@ -775,7 +816,7 @@ private struct ChannelStatsControllerState: Equatable {
} }
private func channelStatsControllerEntries(state: ChannelStatsControllerState, peer: EnginePeer?, data: ChannelStats?, messages: [Message]?, interactions: [MessageId: ChannelStatsMessageInteractions]?, boostData: ChannelBoostStatus?, boostersState: ChannelBoostersContext.State?, giftsState: ChannelBoostersContext.State?, presentationData: PresentationData, giveawayAvailable: Bool) -> [StatsEntry] { private func channelStatsControllerEntries(state: ChannelStatsControllerState, peer: EnginePeer?, data: ChannelStats?, messages: [Message]?, stories: PeerStoryListContext.State?, interactions: [MessageId: ChannelStatsMessageInteractions]?, boostData: ChannelBoostStatus?, boostersState: ChannelBoostersContext.State?, giftsState: ChannelBoostersContext.State?, presentationData: PresentationData, giveawayAvailable: Bool) -> [StatsEntry] {
var entries: [StatsEntry] = [] var entries: [StatsEntry] = []
switch state.section { switch state.section {
@ -847,14 +888,35 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
entries.append(.storyReactionsByEmotionGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.storyReactionsByEmotionGraph, .bars)) entries.append(.storyReactionsByEmotionGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.storyReactionsByEmotionGraph, .bars))
} }
if let messages = messages, !messages.isEmpty, let interactions = interactions, !interactions.isEmpty {
var posts: [StatsPostItem] = []
if let messages, let interactions {
for message in messages {
if let _ = interactions[message.id] {
posts.append(.message(message))
}
}
}
if let stories {
for story in stories.items {
posts.append(.story(story))
}
}
posts.sort(by: { $0.timestamp > $1.timestamp })
if !posts.isEmpty, let interactions, let peer = peer?._asPeer() {
entries.append(.postsTitle(presentationData.theme, presentationData.strings.Stats_PostsTitle)) entries.append(.postsTitle(presentationData.theme, presentationData.strings.Stats_PostsTitle))
var index: Int32 = 0 var index: Int32 = 0
for message in messages { for post in posts {
if let interactions = interactions[message.id] { switch post {
entries.append(.post(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, message, interactions)) case let .message(message):
index += 1 if let interactions = interactions[message.id] {
entries.append(.post(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, post, interactions))
}
case let .story(story):
entries.append(.post(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, post, ChannelStatsMessageInteractions(messageId: MessageId(peerId: PeerId(0), namespace: 0, id: 0), views: Int32(story.views?.seenCount ?? 0), forwards: Int32(story.views?.forwardCount ?? 0), reactions: Int32(story.views?.reactedCount ?? 0))))
} }
index += 1
} }
} }
} }
@ -979,13 +1041,15 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
var openMessageStatsImpl: ((MessageId) -> Void)? var openPostStatsImpl: ((EnginePeer, StatsPostItem) -> Void)?
var contextActionImpl: ((MessageId, ASDisplayNode, ContextGesture?) -> Void)? var contextActionImpl: ((MessageId, ASDisplayNode, ContextGesture?) -> Void)?
let actionsDisposable = DisposableSet() let actionsDisposable = DisposableSet()
let dataPromise = Promise<ChannelStats?>(nil) let dataPromise = Promise<ChannelStats?>(nil)
let messagesPromise = Promise<MessageHistoryView?>(nil) let messagesPromise = Promise<MessageHistoryView?>(nil)
let storiesPromise = Promise<PeerStoryListContext.State?>()
let datacenterId: Int32 = statsDatacenterId ?? 0 let datacenterId: Int32 = statsDatacenterId ?? 0
let statsContext = ChannelStatsContext(postbox: context.account.postbox, network: context.account.network, datacenterId: datacenterId, peerId: peerId) let statsContext = ChannelStatsContext(postbox: context.account.postbox, network: context.account.network, datacenterId: datacenterId, peerId: peerId)
@ -1027,8 +1091,8 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
let arguments = ChannelStatsControllerArguments(context: context, loadDetailedGraph: { graph, x -> Signal<StatsGraph?, NoError> in let arguments = ChannelStatsControllerArguments(context: context, loadDetailedGraph: { graph, x -> Signal<StatsGraph?, NoError> in
return statsContext.loadDetailedGraph(graph, x: x) return statsContext.loadDetailedGraph(graph, x: x)
}, openMessage: { messageId in }, openPostStats: { peer, item in
openMessageStatsImpl?(messageId) openPostStatsImpl?(peer, item)
}, contextAction: { messageId, node, gesture in }, contextAction: { messageId, node, gesture in
contextActionImpl?(messageId, node, gesture) contextActionImpl?(messageId, node, gesture)
}, copyBoostLink: { link in }, copyBoostLink: { link in
@ -1138,6 +1202,16 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
} }
messagesPromise.set(.single(nil) |> then(messageView)) messagesPromise.set(.single(nil) |> then(messageView))
let storyList = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false)
storyList.loadMore()
storiesPromise.set(
.single(nil)
|> then(
storyList.state
|> map(Optional.init)
)
)
let longLoadingSignal: Signal<Bool, NoError> = .single(false) |> then(.single(true) |> delay(2.0, queue: Queue.mainQueue())) let longLoadingSignal: Signal<Bool, NoError> = .single(false) |> then(.single(true) |> delay(2.0, queue: Queue.mainQueue()))
let previousData = Atomic<ChannelStats?>(value: nil) let previousData = Atomic<ChannelStats?>(value: nil)
@ -1149,13 +1223,14 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)),
dataPromise.get(), dataPromise.get(),
messagesPromise.get(), messagesPromise.get(),
storiesPromise.get(),
boostData, boostData,
boostsContext.state, boostsContext.state,
giftsContext.state, giftsContext.state,
longLoadingSignal longLoadingSignal
) )
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { presentationData, state, peer, data, messageView, boostData, boostersState, giftsState, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { presentationData, state, peer, data, messageView, stories, boostData, boostersState, giftsState, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in
let previous = previousData.swap(data) let previous = previousData.swap(data)
var emptyStateItem: ItemListControllerEmptyStateItem? var emptyStateItem: ItemListControllerEmptyStateItem?
switch state.section { switch state.section {
@ -1183,13 +1258,14 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
} }
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .sectionControl([presentationData.strings.Stats_Statistics, presentationData.strings.Stats_Boosts], state.section == .boosts ? 1 : 0), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .sectionControl([presentationData.strings.Stats_Statistics, presentationData.strings.Stats_Boosts], state.section == .boosts ? 1 : 0), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelStatsControllerEntries(state: state, peer: peer, data: data, messages: messages, interactions: interactions, boostData: boostData, boostersState: boostersState, giftsState: giftsState, presentationData: presentationData, giveawayAvailable: premiumConfiguration.giveawayGiftsPurchaseAvailable), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelStatsControllerEntries(state: state, peer: peer, data: data, messages: messages, stories: stories, interactions: interactions, boostData: boostData, boostersState: boostersState, giftsState: giftsState, presentationData: presentationData, giveawayAvailable: premiumConfiguration.giveawayGiftsPurchaseAvailable), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} }
|> afterDisposed { |> afterDisposed {
actionsDisposable.dispose() actionsDisposable.dispose()
let _ = statsContext.state let _ = statsContext.state
let _ = storyList.state
} }
let controller = ItemListController(context: context, state: signal) let controller = ItemListController(context: context, state: signal)
@ -1206,8 +1282,15 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
controller.didDisappear = { [weak controller] _ in controller.didDisappear = { [weak controller] _ in
controller?.clearItemNodesHighlight(animated: true) controller?.clearItemNodesHighlight(animated: true)
} }
openMessageStatsImpl = { [weak controller] messageId in openPostStatsImpl = { [weak controller] peer, post in
controller?.push(messageStatsController(context: context, messageId: messageId, statsDatacenterId: statsDatacenterId)) let subject: StatsSubject
switch post {
case let .message(message):
subject = .message(id: message.id)
case let .story(story):
subject = .story(peer: peer, storyItem: story)
}
controller?.push(messageStatsController(context: context, subject: subject, statsDatacenterId: statsDatacenterId))
} }
contextActionImpl = { [weak controller] messageId, sourceNode, gesture in contextActionImpl = { [weak controller] messageId, sourceNode, gesture in
guard let controller = controller, let sourceNode = sourceNode as? ContextExtractedContentContainingNode else { guard let controller = controller, let sourceNode = sourceNode as? ContextExtractedContentContainingNode else {

View File

@ -2,6 +2,7 @@ import Foundation
import UIKit import UIKit
import Display import Display
import SwiftSignalKit import SwiftSignalKit
import AsyncDisplayKit
import TelegramCore import TelegramCore
import TelegramPresentationData import TelegramPresentationData
import TelegramUIPreferences import TelegramUIPreferences
@ -35,7 +36,7 @@ private enum StatsSection: Int32 {
private enum StatsEntry: ItemListNodeEntry { private enum StatsEntry: ItemListNodeEntry {
case overviewTitle(PresentationTheme, String) case overviewTitle(PresentationTheme, String)
case overview(PresentationTheme, MessageStats, Int32?) case overview(PresentationTheme, PostStats, Int32?)
case interactionsTitle(PresentationTheme, String) case interactionsTitle(PresentationTheme, String)
case interactionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType) case interactionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
@ -89,8 +90,14 @@ private enum StatsEntry: ItemListNodeEntry {
return false return false
} }
case let .overview(lhsTheme, lhsStats, lhsPublicShares): case let .overview(lhsTheme, lhsStats, lhsPublicShares):
if case let .overview(rhsTheme, rhsStats, rhsPublicShares) = rhs, lhsTheme === rhsTheme, lhsStats == rhsStats, lhsPublicShares == rhsPublicShares { if case let .overview(rhsTheme, rhsStats, rhsPublicShares) = rhs, lhsTheme === rhsTheme, lhsPublicShares == rhsPublicShares {
return true if let lhsMessageStats = lhsStats as? MessageStats, let rhsMessageStats = rhsStats as? MessageStats {
return lhsMessageStats == rhsMessageStats
} else if let lhsStoryStats = lhsStats as? StoryStats, let rhsStoryStats = rhsStats as? StoryStats {
return lhsStoryStats == rhsStoryStats
} else {
return false
}
} else { } else {
return false return false
} }
@ -172,7 +179,7 @@ private enum StatsEntry: ItemListNodeEntry {
} }
} }
private func messageStatsControllerEntries(data: MessageStats?, messages: SearchMessagesResult?, presentationData: PresentationData) -> [StatsEntry] { private func messageStatsControllerEntries(data: PostStats?, messages: SearchMessagesResult?, presentationData: PresentationData) -> [StatsEntry] {
var entries: [StatsEntry] = [] var entries: [StatsEntry] = []
if let data = data { if let data = data {
@ -212,44 +219,98 @@ private func messageStatsControllerEntries(data: MessageStats?, messages: Search
return entries return entries
} }
public func messageStatsController(context: AccountContext, messageId: EngineMessage.Id, statsDatacenterId: Int32?) -> ViewController { public enum StatsSubject {
case message(id: EngineMessage.Id)
case story(peer: EnginePeer, storyItem: EngineStoryItem)
}
protocol PostStats {
var views: Int { get }
var forwards: Int { get }
var interactionsGraph: StatsGraph { get }
var interactionsGraphDelta: Int64 { get }
var reactionsGraph: StatsGraph { get }
}
extension MessageStats: PostStats {
}
extension StoryStats: PostStats {
}
public func messageStatsController(context: AccountContext, subject: StatsSubject, statsDatacenterId: Int32?) -> ViewController {
var navigateToMessageImpl: ((EngineMessage.Id) -> Void)? var navigateToMessageImpl: ((EngineMessage.Id) -> Void)?
let actionsDisposable = DisposableSet() let actionsDisposable = DisposableSet()
let dataPromise = Promise<MessageStats?>(nil) let dataPromise = Promise<PostStats?>(nil)
let messagesPromise = Promise<(SearchMessagesResult, SearchMessagesState)?>(nil) let messagesPromise = Promise<(SearchMessagesResult, SearchMessagesState)?>(nil)
let datacenterId: Int32 = statsDatacenterId ?? 0 let datacenterId: Int32 = statsDatacenterId ?? 0
let statsContext = MessageStatsContext(postbox: context.account.postbox, network: context.account.network, datacenterId: datacenterId, messageId: messageId) let anyStatsContext: Any
let dataSignal: Signal<MessageStats?, NoError> = statsContext.state let dataSignal: Signal<PostStats?, NoError>
|> map { state in var loadDetailedGraphImpl: ((StatsGraph, Int64) -> Signal<StatsGraph?, NoError>)?
return state.stats switch subject {
case let .message(id):
let statsContext = MessageStatsContext(postbox: context.account.postbox, network: context.account.network, datacenterId: datacenterId, messageId: id)
loadDetailedGraphImpl = { [weak statsContext] graph, x in
return statsContext?.loadDetailedGraph(graph, x: x) ?? .single(nil)
}
dataSignal = statsContext.state
|> map { state in
return state.stats
}
dataPromise.set(.single(nil) |> then(dataSignal))
anyStatsContext = statsContext
case let .story(peer, storyItem):
let statsContext = StoryStatsContext(postbox: context.account.postbox, network: context.account.network, datacenterId: datacenterId, peerId: peer.id, storyId: storyItem.id)
loadDetailedGraphImpl = { [weak statsContext] graph, x in
return statsContext?.loadDetailedGraph(graph, x: x) ?? .single(nil)
}
dataSignal = statsContext.state
|> map { state in
return state.stats
}
dataPromise.set(.single(nil) |> then(dataSignal))
anyStatsContext = statsContext
} }
dataPromise.set(.single(nil) |> then(dataSignal))
let arguments = MessageStatsControllerArguments(context: context, loadDetailedGraph: { graph, x -> Signal<StatsGraph?, NoError> in let arguments = MessageStatsControllerArguments(context: context, loadDetailedGraph: { graph, x -> Signal<StatsGraph?, NoError> in
return statsContext.loadDetailedGraph(graph, x: x) return loadDetailedGraphImpl?(graph, x) ?? .single(nil)
}, openMessage: { messageId in }, openMessage: { messageId in
navigateToMessageImpl?(messageId) navigateToMessageImpl?(messageId)
}) })
let longLoadingSignal: Signal<Bool, NoError> = .single(false) |> then(.single(true) |> delay(2.0, queue: Queue.mainQueue())) let longLoadingSignal: Signal<Bool, NoError> = .single(false) |> then(.single(true) |> delay(2.0, queue: Queue.mainQueue()))
let previousData = Atomic<MessageStats?>(value: nil) let previousData = Atomic<PostStats?>(value: nil)
let searchSignal = context.engine.messages.searchMessages(location: .publicForwards(messageId: messageId, datacenterId: Int(datacenterId)), query: "", state: nil) if case let .message(id) = subject {
|> map(Optional.init) let searchSignal = context.engine.messages.searchMessages(location: .publicForwards(messageId: id, datacenterId: Int(datacenterId)), query: "", state: nil)
|> afterNext { result in |> map(Optional.init)
if let result = result { |> afterNext { result in
for message in result.0.messages { if let result = result {
if let peer = message.peers[message.id.peerId], let peerReference = PeerReference(peer) { for message in result.0.messages {
let _ = context.engine.peers.updatedRemotePeer(peer: peerReference).start() if let peer = message.peers[message.id.peerId], let peerReference = PeerReference(peer) {
let _ = context.engine.peers.updatedRemotePeer(peer: peerReference).start()
}
} }
} }
} }
messagesPromise.set(.single(nil) |> then(searchSignal))
} else {
messagesPromise.set(.single(nil))
}
let iconNode: ASDisplayNode?
if case let .story(peer, storyItem) = subject {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
iconNode = StoryIconNode(context: context, theme: presentationData.theme, peer: peer._asPeer(), storyItem: storyItem)
} else {
iconNode = nil
} }
messagesPromise.set(.single(nil) |> then(searchSignal))
let signal = combineLatest(context.sharedContext.presentationData, dataPromise.get(), messagesPromise.get(), longLoadingSignal) let signal = combineLatest(context.sharedContext.presentationData, dataPromise.get(), messagesPromise.get(), longLoadingSignal)
|> deliverOnMainQueue |> deliverOnMainQueue
@ -264,14 +325,22 @@ public func messageStatsController(context: AccountContext, messageId: EngineMes
} }
} }
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Stats_MessageTitle), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) let title: String
switch subject {
case .message:
title = presentationData.strings.Stats_MessageTitle
case .story:
title = presentationData.strings.Stats_StoryTitle
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: nil, rightNavigationButton: iconNode.flatMap { ItemListNavigationButton(content: .node($0), style: .regular, enabled: true, action: { }) }, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: messageStatsControllerEntries(data: data, messages: search?.0, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: messageStatsControllerEntries(data: data, messages: search?.0, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} }
|> afterDisposed { |> afterDisposed {
actionsDisposable.dispose() actionsDisposable.dispose()
let _ = statsContext.state let _ = anyStatsContext
} }
let controller = ItemListController(context: context, state: signal) let controller = ItemListController(context: context, state: signal)
@ -282,6 +351,12 @@ public func messageStatsController(context: AccountContext, messageId: EngineMes
} }
}) })
} }
// controller.visibleBottomContentOffsetChanged = { offset in
// let state = stateValue.with { $0 }
// if case let .known(value) = offset, value < 100.0, case .boosts = state.section, state.boostersExpanded {
// boostsContext.loadMore()
// }
// }
controller.didDisappear = { [weak controller] _ in controller.didDisappear = { [weak controller] _ in
controller?.clearItemNodesHighlight(animated: true) controller?.clearItemNodesHighlight(animated: true)
} }

View File

@ -10,12 +10,12 @@ import PresentationDataUtils
class MessageStatsOverviewItem: ListViewItem, ItemListItem { class MessageStatsOverviewItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let stats: MessageStats let stats: PostStats
let publicShares: Int32? let publicShares: Int32?
let sectionId: ItemListSectionId let sectionId: ItemListSectionId
let style: ItemListStyle let style: ItemListStyle
init(presentationData: ItemListPresentationData, stats: MessageStats, publicShares: Int32?, sectionId: ItemListSectionId, style: ItemListStyle) { init(presentationData: ItemListPresentationData, stats: PostStats, publicShares: Int32?, sectionId: ItemListSectionId, style: ItemListStyle) {
self.presentationData = presentationData self.presentationData = presentationData
self.stats = stats self.stats = stats
self.publicShares = publicShares self.publicShares = publicShares

View File

@ -2,6 +2,7 @@ import Foundation
import UIKit import UIKit
import Display import Display
import AsyncDisplayKit import AsyncDisplayKit
import ComponentFlow
import SwiftSignalKit import SwiftSignalKit
import Postbox import Postbox
import TelegramCore import TelegramCore
@ -11,23 +12,28 @@ import TelegramStringFormatting
import ItemListUI import ItemListUI
import PresentationDataUtils import PresentationDataUtils
import PhotoResources import PhotoResources
import AvatarStoryIndicatorComponent
public class StatsMessageItem: ListViewItem, ItemListItem { public class StatsMessageItem: ListViewItem, ItemListItem {
let context: AccountContext let context: AccountContext
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let message: Message let peer: Peer
let item: StatsPostItem
let views: Int32 let views: Int32
let reactions: Int32
let forwards: Int32 let forwards: Int32
public let sectionId: ItemListSectionId public let sectionId: ItemListSectionId
let style: ItemListStyle let style: ItemListStyle
let action: (() -> Void)? let action: (() -> Void)?
let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
init(context: AccountContext, presentationData: ItemListPresentationData, message: Message, views: Int32, forwards: Int32, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?) { init(context: AccountContext, presentationData: ItemListPresentationData, peer: Peer, item: StatsPostItem, views: Int32, reactions: Int32, forwards: Int32, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?) {
self.context = context self.context = context
self.presentationData = presentationData self.presentationData = presentationData
self.message = message self.peer = peer
self.item = item
self.views = views self.views = views
self.reactions = reactions
self.forwards = forwards self.forwards = forwards
self.sectionId = sectionId self.sectionId = sectionId
self.style = style self.style = style
@ -79,7 +85,7 @@ public class StatsMessageItem: ListViewItem, ItemListItem {
private let badgeFont = Font.regular(15.0) private let badgeFont = Font.regular(15.0)
public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode { final class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
private let backgroundNode: ASDisplayNode private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode
@ -96,9 +102,14 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
private var nonExtractedRect: CGRect? private var nonExtractedRect: CGRect?
let contentImageNode: TransformImageNode let contentImageNode: TransformImageNode
var storyIndicator: ComponentView<Empty>?
let titleNode: TextNode let titleNode: TextNode
let labelNode: TextNode let labelNode: TextNode
let viewsNode: TextNode let viewsNode: TextNode
let reactionsIconNode: ASImageNode
let reactionsNode: TextNode
let forwardsIconNode: ASImageNode
let forwardsNode: TextNode let forwardsNode: TextNode
private let activateArea: AccessibilityAreaNode private let activateArea: AccessibilityAreaNode
@ -152,6 +163,15 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
self.forwardsNode = TextNode() self.forwardsNode = TextNode()
self.forwardsNode.isUserInteractionEnabled = false self.forwardsNode.isUserInteractionEnabled = false
self.forwardsIconNode = ASImageNode()
self.forwardsIconNode.displaysAsynchronously = false
self.reactionsNode = TextNode()
self.reactionsNode.isUserInteractionEnabled = false
self.reactionsIconNode = ASImageNode()
self.reactionsIconNode.displaysAsynchronously = false
self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true self.highlightedBackgroundNode.isLayerBacked = true
@ -172,6 +192,9 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
self.offsetContainerNode.addSubnode(self.labelNode) self.offsetContainerNode.addSubnode(self.labelNode)
self.countersContainerNode.addSubnode(self.viewsNode) self.countersContainerNode.addSubnode(self.viewsNode)
self.countersContainerNode.addSubnode(self.forwardsNode) self.countersContainerNode.addSubnode(self.forwardsNode)
self.countersContainerNode.addSubnode(self.forwardsIconNode)
self.countersContainerNode.addSubnode(self.reactionsNode)
self.countersContainerNode.addSubnode(self.reactionsIconNode)
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
self.addSubnode(self.activateArea) self.addSubnode(self.activateArea)
@ -200,8 +223,8 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
transition.updateAlpha(node: strongSelf.countersContainerNode, alpha: isExtracted ? 0.0 : 1.0) transition.updateAlpha(node: strongSelf.countersContainerNode, alpha: isExtracted ? 0.0 : 1.0)
transition.updateSublayerTransformOffset(layer: strongSelf.countersContainerNode.layer, offset: CGPoint(x: isExtracted ? -24.0 : 0.0, y: 0.0)) transition.updateSublayerTransformOffset(layer: strongSelf.countersContainerNode.layer, offset: CGPoint(x: isExtracted ? -16.0 : 0.0, y: 0.0))
transition.updateSublayerTransformOffset(layer: strongSelf.offsetContainerNode.layer, offset: CGPoint(x: isExtracted ? 12.0 : 0.0, y: 0.0)) transition.updateSublayerTransformOffset(layer: strongSelf.offsetContainerNode.layer, offset: CGPoint(x: isExtracted ? 16.0 : 0.0, y: 0.0))
transition.updateAlpha(node: strongSelf.extractedBackgroundImageNode, alpha: isExtracted ? 1.0 : 0.0, completion: { _ in transition.updateAlpha(node: strongSelf.extractedBackgroundImageNode, alpha: isExtracted ? 1.0 : 0.0, completion: { _ in
if !isExtracted { if !isExtracted {
@ -215,6 +238,7 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeLabelLayout = TextNode.asyncLayout(self.labelNode) let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
let makeViewsLayout = TextNode.asyncLayout(self.viewsNode) let makeViewsLayout = TextNode.asyncLayout(self.viewsNode)
let makeReactionsLayout = TextNode.asyncLayout(self.reactionsNode)
let makeForwardsLayout = TextNode.asyncLayout(self.forwardsNode) let makeForwardsLayout = TextNode.asyncLayout(self.forwardsNode)
let currentItem = self.item let currentItem = self.item
@ -236,67 +260,100 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
let leftInset = 16.0 + params.leftInset let leftInset = 16.0 + params.leftInset
let rightInset = 16.0 + params.rightInset let rightInset = 16.0 + params.rightInset
var totalLeftInset = leftInset var totalLeftInset = leftInset
let additionalRightInset: CGFloat = 128.0
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize) let titleFont = Font.semibold(item.presentationData.fontSize.itemListBaseFontSize)
let labelFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 13.0 / 17.0))
let presentationData = item.context.sharedContext.currentPresentationData.with { $0 } let presentationData = item.context.sharedContext.currentPresentationData.with { $0 }
let contentKind = messageContentKind(contentSettings: item.context.currentContentSettings.with { $0 }, message: EngineMessage(item.message), strings: item.presentationData.strings, nameDisplayOrder: .firstLast, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: item.context.account.peerId)
var text = !item.message.text.isEmpty ? item.message.text : stringForMediaKind(contentKind, strings: item.presentationData.strings).0.string
text = foldLineBreaks(text)
var text: String
var contentImageMedia: Media? var contentImageMedia: Media?
for media in item.message.media { let timestamp: Int32
if let image = media as? TelegramMediaImage {
contentImageMedia = image switch item.item {
break case let .message(message):
} else if let file = media as? TelegramMediaFile { let contentKind: MessageContentKind
if file.isVideo && !file.isInstantVideo { contentKind = messageContentKind(contentSettings: item.context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: item.presentationData.strings, nameDisplayOrder: .firstLast, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: item.context.account.peerId)
contentImageMedia = file text = !message.text.isEmpty ? message.text : stringForMediaKind(contentKind, strings: item.presentationData.strings).0.string
break
} for media in message.media {
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { if let image = media as? TelegramMediaImage {
if let image = content.image {
contentImageMedia = image contentImageMedia = image
break break
} else if let file = content.file { } else if let file = media as? TelegramMediaFile {
if file.isVideo && !file.isInstantVideo { if file.isVideo && !file.isInstantVideo {
contentImageMedia = file contentImageMedia = file
break break
} }
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
if let image = content.image {
contentImageMedia = image
break
} else if let file = content.file {
if file.isVideo && !file.isInstantVideo {
contentImageMedia = file
break
}
}
} }
} }
timestamp = message.timestamp
case let .story(story):
text = item.presentationData.strings.Message_Story
timestamp = story.timestamp
if let image = story.media._asMedia() as? TelegramMediaImage {
contentImageMedia = image
break
} else if let file = story.media._asMedia() as? TelegramMediaFile {
contentImageMedia = file
break
}
} }
text = foldLineBreaks(text)
if let _ = contentImageMedia { if let _ = contentImageMedia {
totalLeftInset += 48.0 totalLeftInset += 46.0
} }
var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
if let contentImageMedia = contentImageMedia { if let contentImageMedia = contentImageMedia {
if let currentContentImageMedia = currentContentImageMedia, contentImageMedia.isSemanticallyEqual(to: currentContentImageMedia) { if let currentContentImageMedia = currentContentImageMedia, contentImageMedia.isSemanticallyEqual(to: currentContentImageMedia) {
} else { } else {
if let image = contentImageMedia as? TelegramMediaImage { switch item.item {
updateImageSignal = mediaGridMessagePhoto(account: item.context.account, userLocation: .peer(item.message.id.peerId), photoReference: .message(message: MessageReference(item.message), media: image)) case let .message(message):
} else if let file = contentImageMedia as? TelegramMediaFile { if let image = contentImageMedia as? TelegramMediaImage {
updateImageSignal = mediaGridMessageVideo(postbox: item.context.account.postbox, userLocation: .peer(item.message.id.peerId), videoReference: .message(message: MessageReference(item.message), media: file), autoFetchFullSizeThumbnail: true) updateImageSignal = mediaGridMessagePhoto(account: item.context.account, userLocation: .peer(message.id.peerId), photoReference: .message(message: MessageReference(message), media: image))
} else if let file = contentImageMedia as? TelegramMediaFile {
updateImageSignal = mediaGridMessageVideo(postbox: item.context.account.postbox, userLocation: .peer(message.id.peerId), videoReference: .message(message: MessageReference(message), media: file), autoFetchFullSizeThumbnail: true)
}
case let .story(story):
if let peerReference = PeerReference(item.peer) {
if let image = contentImageMedia as? TelegramMediaImage {
updateImageSignal = mediaGridMessagePhoto(account: item.context.account, userLocation: .peer(item.peer.id), photoReference: .story(peer: peerReference, id: story.id, media: image))
} else if let file = contentImageMedia as? TelegramMediaFile {
updateImageSignal = mediaGridMessageVideo(postbox: item.context.account.postbox, userLocation: .peer(item.peer.id), videoReference: .story(peer: peerReference, id: story.id, media: file), autoFetchFullSizeThumbnail: true)
}
}
} }
} }
} }
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: text, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - totalLeftInset - rightInset - additionalRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let labelFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 13.0 / 17.0))
let label = stringForMediumDate(timestamp: item.message.timestamp, strings: item.presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat)
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: label, font: labelFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - totalLeftInset - rightInset - additionalRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (viewsLayout, viewsApply) = makeViewsLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_MessageViews(item.views), font: labelFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 128.0, height: CGFloat.greatestFiniteMagnitude), alignment: .right, cutout: nil, insets: UIEdgeInsets())) let (viewsLayout, viewsApply) = makeViewsLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_MessageViews(item.views), font: labelFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 128.0, height: CGFloat.greatestFiniteMagnitude), alignment: .right, cutout: nil, insets: UIEdgeInsets()))
let (forwardsLayout, forwardsApply) = makeForwardsLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_MessageForwards(item.forwards), font: labelFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 128.0, height: CGFloat.greatestFiniteMagnitude), alignment: .right, cutout: nil, insets: UIEdgeInsets())) let reactions = item.reactions > 0 ? compactNumericCountString(Int(item.reactions), decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator) : ""
let (reactionsLayout, reactionsApply) = makeReactionsLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: reactions, font: labelFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 128.0, height: CGFloat.greatestFiniteMagnitude), alignment: .right, cutout: nil, insets: UIEdgeInsets()))
let verticalInset: CGFloat = 11.0 let forwards = item.forwards > 0 ? compactNumericCountString(Int(item.forwards), decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator) : ""
let (forwardsLayout, forwardsApply) = makeForwardsLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: forwards, font: labelFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 128.0, height: CGFloat.greatestFiniteMagnitude), alignment: .right, cutout: nil, insets: UIEdgeInsets()))
let additionalRightInset = max(viewsLayout.size.width, reactionsLayout.size.width + forwardsLayout.size.width + 36.0) + 8.0
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: text, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - totalLeftInset - rightInset - additionalRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let label = stringForMediumDate(timestamp: timestamp, strings: item.presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat)
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: label, font: labelFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - totalLeftInset - rightInset - additionalRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let verticalInset: CGFloat = 10.0
let titleSpacing: CGFloat = 3.0 let titleSpacing: CGFloat = 3.0
let height: CGFloat = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + labelLayout.size.height let height: CGFloat = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + labelLayout.size.height
@ -318,8 +375,14 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
if let strongSelf = self { if let strongSelf = self {
let themeUpdated = strongSelf.item?.presentationData.theme !== item.presentationData.theme
strongSelf.item = item strongSelf.item = item
if themeUpdated {
strongSelf.forwardsIconNode.image = PresentationResourcesItemList.statsForwardsIcon(item.presentationData.theme)
strongSelf.reactionsIconNode.image = PresentationResourcesItemList.statsReactionsIcon(item.presentationData.theme)
}
let nonExtractedRect = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width - 16.0, height: layout.contentSize.height)) let nonExtractedRect = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width - 16.0, height: layout.contentSize.height))
let extractedRect = CGRect(origin: CGPoint(), size: layout.contentSize).insetBy(dx: 16.0 + params.leftInset, dy: 0.0) let extractedRect = CGRect(origin: CGPoint(), size: layout.contentSize).insetBy(dx: 16.0 + params.leftInset, dy: 0.0)
strongSelf.extractedRect = extractedRect strongSelf.extractedRect = extractedRect
@ -357,13 +420,21 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
dimensions = contentImageMedia.dimensions?.cgSize dimensions = contentImageMedia.dimensions?.cgSize
} }
let contentImageSize = CGSize(width: 40.0, height: 40.0) var contentImageSize = CGSize(width: 40.0, height: 40.0)
var contentImageInset = leftInset - 6.0
if let dimensions = dimensions { if let dimensions = dimensions {
let makeImageLayout = strongSelf.contentImageNode.asyncLayout() let makeImageLayout = strongSelf.contentImageNode.asyncLayout()
let imageSize = contentImageSize
let applyImageLayout = makeImageLayout(TransformImageArguments(corners: ImageCorners(radius: 4.0), imageSize: dimensions.aspectFilled(imageSize), boundingSize: imageSize, intrinsicInsets: UIEdgeInsets())) let cornerRadius: CGFloat
if case .story = item.item {
contentImageInset += 3.0
contentImageSize = CGSize(width: 34.0, height: 34.0)
cornerRadius = contentImageSize.width / 2.0
} else {
cornerRadius = 6.0
}
let applyImageLayout = makeImageLayout(TransformImageArguments(corners: ImageCorners(radius: cornerRadius), imageSize: dimensions.aspectFilled(contentImageSize), boundingSize: contentImageSize, intrinsicInsets: UIEdgeInsets()))
applyImageLayout() applyImageLayout()
if let updateImageSignal = updateImageSignal { if let updateImageSignal = updateImageSignal {
@ -384,6 +455,7 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
let _ = labelApply() let _ = labelApply()
let _ = viewsApply() let _ = viewsApply()
let _ = forwardsApply() let _ = forwardsApply()
let _ = reactionsApply()
switch item.style { switch item.style {
case .plain: case .plain:
@ -427,7 +499,7 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
let bottomStripeInset: CGFloat let bottomStripeInset: CGFloat
switch neighbors.bottom { switch neighbors.bottom {
case .sameSection(false): case .sameSection(false):
bottomStripeInset = leftInset bottomStripeInset = totalLeftInset
strongSelf.bottomStripeNode.isHidden = false strongSelf.bottomStripeNode.isHidden = false
default: default:
bottomStripeInset = 0.0 bottomStripeInset = 0.0
@ -443,22 +515,106 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
} }
let contentImageFrame = CGRect(origin: CGPoint(x: leftInset, y: floorToScreenPixels((height - contentImageSize.height) / 2.0)), size: contentImageSize) let contentImageFrame = CGRect(origin: CGPoint(x: contentImageInset, y: floorToScreenPixels((height - contentImageSize.height) / 2.0)), size: contentImageSize)
strongSelf.contentImageNode.frame = contentImageFrame strongSelf.contentImageNode.frame = contentImageFrame
let titleFrame = CGRect(origin: CGPoint(x: totalLeftInset, y: 11.0), size: titleLayout.size) let titleFrame = CGRect(origin: CGPoint(x: totalLeftInset, y: 9.0), size: titleLayout.size)
strongSelf.titleNode.frame = titleFrame strongSelf.titleNode.frame = titleFrame
let labelFrame = CGRect(origin: CGPoint(x: totalLeftInset, y: titleFrame.maxY + titleSpacing), size: labelLayout.size) let labelFrame = CGRect(origin: CGPoint(x: totalLeftInset, y: titleFrame.maxY + titleSpacing), size: labelLayout.size)
strongSelf.labelNode.frame = labelFrame strongSelf.labelNode.frame = labelFrame
let viewsFrame = CGRect(origin: CGPoint(x: params.width - rightInset - viewsLayout.size.width, y: 15.0), size: viewsLayout.size) let viewsFrame = CGRect(origin: CGPoint(x: params.width - rightInset - viewsLayout.size.width, y: 13.0), size: viewsLayout.size)
strongSelf.viewsNode.frame = viewsFrame strongSelf.viewsNode.frame = viewsFrame
let forwardsFrame = CGRect(origin: CGPoint(x: params.width - rightInset - forwardsLayout.size.width, y: titleFrame.maxY + titleSpacing), size: forwardsLayout.size) let iconSpacing: CGFloat = 3.0 - UIScreenPixel
strongSelf.forwardsNode.frame = forwardsFrame
var rightContentInset: CGFloat = rightInset
if forwardsLayout.size.width > 0.0 {
strongSelf.forwardsIconNode.isHidden = false
strongSelf.forwardsNode.isHidden = false
let forwardsFrame = CGRect(origin: CGPoint(x: params.width - rightContentInset - forwardsLayout.size.width, y: titleFrame.maxY + titleSpacing), size: forwardsLayout.size)
strongSelf.forwardsNode.frame = forwardsFrame
if let icon = strongSelf.forwardsIconNode.image {
let forwardsIconFrame = CGRect(origin: CGPoint(x: params.width - rightContentInset - forwardsLayout.size.width - icon.size.width - iconSpacing, y: titleFrame.maxY + titleSpacing - 2.0 + UIScreenPixel), size: icon.size)
strongSelf.forwardsIconNode.frame = forwardsIconFrame
rightContentInset += forwardsIconFrame.width + forwardsFrame.width + iconSpacing
}
rightContentInset += 10.0
} else {
strongSelf.forwardsIconNode.isHidden = true
strongSelf.forwardsNode.isHidden = true
}
if reactionsLayout.size.width > 0.0 {
strongSelf.reactionsIconNode.isHidden = false
strongSelf.reactionsNode.isHidden = false
let reactionsFrame = CGRect(origin: CGPoint(x: params.width - rightContentInset - reactionsLayout.size.width, y: titleFrame.maxY + titleSpacing), size: reactionsLayout.size)
strongSelf.reactionsNode.frame = reactionsFrame
if let icon = strongSelf.reactionsIconNode.image {
let reactionsIconFrame = CGRect(origin: CGPoint(x: params.width - rightContentInset - reactionsLayout.size.width - icon.size.width - iconSpacing, y: titleFrame.maxY + titleSpacing - 2.0 + UIScreenPixel), size: icon.size)
strongSelf.reactionsIconNode.frame = reactionsIconFrame
rightContentInset += reactionsIconFrame.width + reactionsFrame.width + iconSpacing
}
} else {
strongSelf.reactionsIconNode.isHidden = true
strongSelf.reactionsNode.isHidden = true
}
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: height + UIScreenPixel)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: height + UIScreenPixel))
if case .story = item.item {
let lineWidth: CGFloat = 1.5
let imageSize = CGSize(width: contentImageFrame.width + 6.0, height: contentImageFrame.height + 6.0)
let indicatorSize = CGSize(width: imageSize.width - lineWidth * 4.0, height: imageSize.height - lineWidth * 4.0)
let storyIndicator: ComponentView<Empty>
let indicatorTransition: Transition = .immediate
if let current = strongSelf.storyIndicator {
storyIndicator = current
} else {
storyIndicator = ComponentView()
strongSelf.storyIndicator = storyIndicator
}
let _ = storyIndicator.update(
transition: indicatorTransition,
component: AnyComponent(AvatarStoryIndicatorComponent(
hasUnseen: true,
hasUnseenCloseFriendsItems: false,
colors: AvatarStoryIndicatorComponent.Colors(
unseenColors: item.presentationData.theme.chatList.storyUnseenColors.array,
unseenCloseFriendsColors: item.presentationData.theme.chatList.storyUnseenPrivateColors.array,
seenColors: item.presentationData.theme.chatList.storySeenColors.array
),
activeLineWidth: lineWidth,
inactiveLineWidth: lineWidth,
counters: AvatarStoryIndicatorComponent.Counters(
totalCount: 1,
unseenCount: 1
),
progress: nil
)),
environment: {},
containerSize: indicatorSize
)
if let storyIndicatorView = storyIndicator.view {
if storyIndicatorView.superview == nil {
strongSelf.offsetContainerNode.view.addSubview(storyIndicatorView)
}
indicatorTransition.setFrame(view: storyIndicatorView, frame: CGRect(origin: CGPoint(x: contentImageFrame.midX - indicatorSize.width / 2.0, y: contentImageFrame.midY - indicatorSize.height / 2.0), size: indicatorSize))
}
} else if let storyIndicator = strongSelf.storyIndicator {
if let storyIndicatorView = storyIndicator.view {
storyIndicatorView.removeFromSuperview()
}
strongSelf.storyIndicator = nil
}
} }
}) })
} }

View File

@ -0,0 +1,106 @@
import Foundation
import Display
import AsyncDisplayKit
import ComponentFlow
import SwiftSignalKit
import Postbox
import TelegramCore
import PhotoResources
import AvatarStoryIndicatorComponent
import AccountContext
import TelegramPresentationData
final class StoryIconNode: ASDisplayNode {
private let imageNode = TransformImageNode()
private let storyIndicator = ComponentView<Empty>()
init(context: AccountContext, theme: PresentationTheme, peer: Peer, storyItem: EngineStoryItem) {
self.imageNode.displaysAsynchronously = false
super.init()
self.addSubnode(self.imageNode)
let imageSize = CGSize(width: 30.0, height: 30.0)
let size = CGSize(width: 36.0, height: 36.0)
let bounds = CGRect(origin: .zero, size: size)
self.imageNode.frame = bounds.insetBy(dx: 3.0, dy: 3.0)
self.frame = bounds
let media: Media?
switch storyItem.media {
case let .image(image):
media = image
case let .file(file):
media = file
default:
media = nil
}
var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
var dimensions: CGSize?
if let peerReference = PeerReference(peer), let media {
if let image = media as? TelegramMediaImage {
updateImageSignal = mediaGridMessagePhoto(account: context.account, userLocation: .peer(peer.id), photoReference: .story(peer: peerReference, id: storyItem.id, media: image))
dimensions = largestRepresentationForPhoto(image)?.dimensions.cgSize
} else if let file = media as? TelegramMediaFile {
updateImageSignal = mediaGridMessageVideo(postbox: context.account.postbox, userLocation: .peer(peer.id), videoReference: .story(peer: peerReference, id: storyItem.id, media: file), autoFetchFullSizeThumbnail: true)
dimensions = file.dimensions?.cgSize
}
}
if let updateImageSignal {
self.imageNode.setSignal(updateImageSignal)
}
if let dimensions {
let cornerRadius = imageSize.width / 2.0
let makeImageLayout = self.imageNode.asyncLayout()
let applyImageLayout = makeImageLayout(TransformImageArguments(corners: ImageCorners(radius: cornerRadius), imageSize: dimensions.aspectFilled(imageSize), boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))
applyImageLayout()
}
let lineWidth: CGFloat = 1.5
let indicatorSize = CGSize(width: size.width - lineWidth * 4.0, height: size.height - lineWidth * 4.0)
let _ = self.storyIndicator.update(
transition: .immediate,
component: AnyComponent(AvatarStoryIndicatorComponent(
hasUnseen: true,
hasUnseenCloseFriendsItems: false,
colors: AvatarStoryIndicatorComponent.Colors(
unseenColors: theme.chatList.storyUnseenColors.array,
unseenCloseFriendsColors: theme.chatList.storyUnseenPrivateColors.array,
seenColors: theme.chatList.storySeenColors.array
),
activeLineWidth: lineWidth,
inactiveLineWidth: lineWidth,
counters: AvatarStoryIndicatorComponent.Counters(
totalCount: 1,
unseenCount: 1
),
progress: nil
)),
environment: {},
containerSize: indicatorSize
)
if let storyIndicatorView = self.storyIndicator.view {
storyIndicatorView.frame = CGRect(origin: CGPoint(x: bounds.midX - indicatorSize.width / 2.0, y: bounds.midY - indicatorSize.height / 2.0), size: indicatorSize)
}
}
override func didLoad() {
super.didLoad()
if let storyIndicatorView = self.storyIndicator.view {
if storyIndicatorView.superview == nil {
self.view.addSubview(storyIndicatorView)
}
}
}
override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
return CGSize(width: 36.0, height: 36.0)
}
}

View File

@ -52,6 +52,14 @@ public struct ChannelStatsMessageInteractions: Equatable {
public let messageId: MessageId public let messageId: MessageId
public let views: Int32 public let views: Int32
public let forwards: Int32 public let forwards: Int32
public let reactions: Int32
public init(messageId: MessageId, views: Int32, forwards: Int32, reactions: Int32) {
self.messageId = messageId
self.views = views
self.forwards = forwards
self.reactions = reactions
}
} }
public final class ChannelStats: Equatable { public final class ChannelStats: Equatable {
@ -1143,7 +1151,7 @@ extension ChannelStatsMessageInteractions {
init(apiMessageInteractionCounters: Api.MessageInteractionCounters, peerId: PeerId) { init(apiMessageInteractionCounters: Api.MessageInteractionCounters, peerId: PeerId) {
switch apiMessageInteractionCounters { switch apiMessageInteractionCounters {
case let .messageInteractionCounters(msgId, views, forwards): case let .messageInteractionCounters(msgId, views, forwards):
self = ChannelStatsMessageInteractions(messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: msgId), views: views, forwards: forwards) self = ChannelStatsMessageInteractions(messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: msgId), views: views, forwards: forwards, reactions: 0)
} }
} }
} }

View File

@ -17,6 +17,10 @@ public final class PresentationThemeGradientColors {
return (self.topColor, self.bottomColor) return (self.topColor, self.bottomColor)
} }
public var array: [UIColor] {
return [self.topColor, self.bottomColor]
}
public func withUpdated(topColor: UIColor? = nil, bottomColor: UIColor? = nil) -> PresentationThemeGradientColors { public func withUpdated(topColor: UIColor? = nil, bottomColor: UIColor? = nil) -> PresentationThemeGradientColors {
return PresentationThemeGradientColors(topColor: topColor ?? self.topColor, bottomColor: bottomColor ?? self.bottomColor) return PresentationThemeGradientColors(topColor: topColor ?? self.topColor, bottomColor: bottomColor ?? self.bottomColor)
} }

View File

@ -72,6 +72,9 @@ public enum PresentationResourceKey: Int32 {
case itemListTopicArrowIcon case itemListTopicArrowIcon
case itemListAddBoostsIcon case itemListAddBoostsIcon
case statsReactionsIcon
case statsForwardsIcon
case itemListVoiceCallIcon case itemListVoiceCallIcon
case itemListVideoCallIcon case itemListVideoCallIcon

View File

@ -329,4 +329,16 @@ public struct PresentationResourcesItemList {
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/TopicArrowIcon"), color: theme.list.itemSecondaryTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat List/TopicArrowIcon"), color: theme.list.itemSecondaryTextColor)
}) })
} }
public static func statsReactionsIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.statsReactionsIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chart/Reactions"), color: theme.list.itemSecondaryTextColor)
})
}
public static func statsForwardsIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.statsForwardsIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chart/Forwards"), color: theme.list.itemSecondaryTextColor)
})
}
} }

View File

@ -1123,7 +1123,7 @@ private final class CameraScreenComponent: CombinedComponent {
component: MultilineTextComponent( component: MultilineTextComponent(
text: .plain(NSAttributedString(string: durationString, font: Font.with(size: 21.0, design: .camera), textColor: controlsTintColor)), text: .plain(NSAttributedString(string: durationString, font: Font.with(size: 21.0, design: .camera), textColor: controlsTintColor)),
horizontalAlignment: .center, horizontalAlignment: .center,
textShadowColor: UIColor(rgb: 0x000000, alpha: 0.2) textShadowColor: controlsTintColor == .black ? .clear : UIColor(rgb: 0x000000, alpha: 0.2)
), ),
availableSize: context.availableSize, availableSize: context.availableSize,
transition: context.transition transition: context.transition

View File

@ -31,6 +31,7 @@ swift_library(
"//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon",
"//submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath", "//submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath",
"//submodules/Components/MultilineTextComponent", "//submodules/Components/MultilineTextComponent",
"//submodules/Components/BundleIconComponent",
"//submodules/ChatMessageBackground", "//submodules/ChatMessageBackground",
], ],
visibility = [ visibility = [

View File

@ -561,6 +561,7 @@ private final class PeerInfoInteraction {
let displayTopicsLimited: (TopicsLimitedReason) -> Void let displayTopicsLimited: (TopicsLimitedReason) -> Void
let openPeerMention: (String, ChatControllerInteractionNavigateToPeer) -> Void let openPeerMention: (String, ChatControllerInteractionNavigateToPeer) -> Void
let openBotApp: (AttachMenuBot) -> Void let openBotApp: (AttachMenuBot) -> Void
let openEditing: () -> Void
init( init(
openUsername: @escaping (String) -> Void, openUsername: @escaping (String) -> Void,
@ -614,7 +615,8 @@ private final class PeerInfoInteraction {
toggleForumTopics: @escaping (Bool) -> Void, toggleForumTopics: @escaping (Bool) -> Void,
displayTopicsLimited: @escaping (TopicsLimitedReason) -> Void, displayTopicsLimited: @escaping (TopicsLimitedReason) -> Void,
openPeerMention: @escaping (String, ChatControllerInteractionNavigateToPeer) -> Void, openPeerMention: @escaping (String, ChatControllerInteractionNavigateToPeer) -> Void,
openBotApp: @escaping (AttachMenuBot) -> Void openBotApp: @escaping (AttachMenuBot) -> Void,
openEditing: @escaping () -> Void
) { ) {
self.openUsername = openUsername self.openUsername = openUsername
self.openPhone = openPhone self.openPhone = openPhone
@ -668,6 +670,7 @@ private final class PeerInfoInteraction {
self.displayTopicsLimited = displayTopicsLimited self.displayTopicsLimited = displayTopicsLimited
self.openPeerMention = openPeerMention self.openPeerMention = openPeerMention
self.openBotApp = openBotApp self.openBotApp = openBotApp
self.openEditing = openEditing
} }
} }
@ -1208,6 +1211,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
let ItemAdmins = 6 let ItemAdmins = 6
let ItemMembers = 7 let ItemMembers = 7
let ItemMemberRequests = 8 let ItemMemberRequests = 8
let ItemEdit = 9
if let _ = data.threadData { if let _ = data.threadData {
let mainUsername: String let mainUsername: String
@ -1358,6 +1362,10 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
interaction.openParticipantsSection(.memberRequests) interaction.openParticipantsSection(.memberRequests)
})) }))
} }
items[.peerMembers]!.append(PeerInfoScreenDisclosureItem(id: ItemEdit, label: .none, text: presentationData.strings.Channel_Info_Settings, icon: UIImage(bundleImageName: "Chat/Info/SettingsIcon"), action: {
interaction.openEditing()
}))
} }
} }
} }
@ -2425,6 +2433,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}, },
openBotApp: { [weak self] bot in openBotApp: { [weak self] bot in
self?.openBotApp(bot) self?.openBotApp(bot)
},
openEditing: { [weak self] in
self?.headerNode.navigationButtonContainer.performAction?(.edit, nil, nil)
} }
) )

View File

@ -1,9 +1,9 @@
{ {
"info" : { "info" : {
"version" : 1, "author" : "xcode",
"author" : "xcode" "version" : 1
}, },
"properties" : { "properties" : {
"provides-namespace" : true "provides-namespace" : true
} }
} }

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "arrowshape_18.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,148 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 1.809143 2.532226 cm
0.000000 0.000000 0.000000 scn
1.381874 3.080939 m
1.812524 5.409000 3.194212 8.717773 7.569556 8.717773 c
7.569556 9.917774 l
7.569556 10.446452 l
7.569556 10.480809 l
7.569556 10.707834 l
7.569556 11.144928 7.569556 11.363475 7.657061 11.467960 c
7.733040 11.558682 7.847383 11.608219 7.965533 11.601600 c
8.101606 11.593976 8.261045 11.444503 8.579921 11.145556 c
8.745543 10.990285 l
8.770609 10.966786 l
13.102652 6.905496 l
13.265036 6.753260 13.346226 6.677144 13.376384 6.588246 c
13.402888 6.510118 13.402888 6.425430 13.376384 6.347302 c
13.346226 6.258403 13.265034 6.182286 13.102651 6.030052 c
8.770609 1.968762 l
8.745543 1.945263 l
8.579920 1.789990 l
8.261045 1.491045 8.101606 1.341572 7.965533 1.333949 c
7.847383 1.327330 7.733040 1.376866 7.657061 1.467588 c
7.569556 1.572074 7.569556 1.790621 7.569556 2.227714 c
7.569556 2.454739 l
7.569556 2.489097 l
7.569556 3.017775 l
7.569556 4.217774 l
7.144288 4.217774 6.744702 4.192091 6.369556 4.145205 c
4.554104 3.918311 3.310977 3.194876 2.499113 2.482739 c
2.362022 2.362489 2.237234 2.242566 2.124057 2.125404 c
2.050126 2.048869 1.981147 1.973513 1.916941 1.900021 c
1.888999 1.868037 1.861959 1.836406 1.835807 1.805183 c
1.802983 1.765995 1.771556 1.727451 1.741497 1.689663 c
1.527425 1.420543 1.420391 1.285985 1.360707 1.277870 c
1.306377 1.270482 1.259393 1.288921 1.224590 1.331290 c
1.186357 1.377833 1.198391 1.535853 1.222459 1.851893 c
1.228173 1.926921 1.234867 2.005210 1.242695 2.086436 c
1.243798 2.097883 1.244923 2.109387 1.246072 2.120950 c
1.257281 2.233779 1.270675 2.352077 1.286657 2.475003 c
1.311591 2.666784 1.342823 2.869831 1.381874 3.080939 c
h
0.592504 2.179043 m
0.592526 2.179102 0.593651 2.180346 0.595821 2.182594 c
0.593566 2.180108 0.592482 2.178984 0.592504 2.179043 c
h
6.369556 2.934145 m
6.369556 2.227714 l
6.369556 2.216033 6.369552 2.204231 6.369547 2.192325 c
6.369482 2.005756 6.369406 1.793388 6.385262 1.616055 c
6.399108 1.461200 6.439729 1.052163 6.737077 0.697114 c
7.056188 0.316081 7.536427 0.108026 8.032658 0.135827 c
8.495048 0.161733 8.821238 0.411856 8.943680 0.507667 c
9.083900 0.617389 9.238787 0.762698 9.374854 0.890351 c
9.383532 0.898493 9.392133 0.906562 9.400649 0.914546 c
13.923381 5.154607 l
13.928342 5.159257 13.933517 5.164101 13.938882 5.169123 c
14.005202 5.231203 14.100604 5.320504 14.180048 5.408552 c
14.274767 5.513531 14.422819 5.696626 14.512774 5.961792 c
14.624091 6.289929 14.624091 6.645618 14.512774 6.973756 c
14.422819 7.238921 14.274767 7.422017 14.180048 7.526996 c
14.100601 7.615047 14.005195 7.704352 13.938873 7.766432 c
13.933511 7.771451 13.928339 7.776292 13.923381 7.780941 c
9.400650 12.021002 l
9.392125 12.028994 9.383514 12.037071 9.374827 12.045221 c
9.238766 12.172870 9.083892 12.318167 8.943680 12.427882 c
8.821238 12.523692 8.495049 12.773815 8.032658 12.799721 c
7.536427 12.827522 7.056188 12.619467 6.737077 12.238434 c
6.439729 11.883385 6.399108 11.474348 6.385262 11.319493 c
6.369406 11.142159 6.369482 10.929791 6.369547 10.743222 c
6.369552 10.731316 6.369556 10.719515 6.369556 10.707834 c
6.369556 9.839663 l
3.993418 9.518946 2.436784 8.247826 1.476669 6.720201 c
0.412234 5.026595 0.112609 3.081284 0.025924 1.943016 c
0.025268 1.934407 0.024604 1.925713 0.023934 1.916945 c
0.013668 1.782581 0.002083 1.630936 0.000289 1.508040 c
-0.000665 1.442641 -0.000246 1.333831 0.017544 1.213016 c
0.030832 1.122771 0.076845 0.837997 0.297327 0.569592 c
0.596399 0.205513 1.055517 0.025331 1.522386 0.088812 c
1.882774 0.137815 2.117930 0.331811 2.178427 0.381719 c
2.178911 0.382119 l
2.270915 0.458017 2.344114 0.537148 2.388324 0.586662 c
2.472725 0.681190 2.571892 0.805902 2.664181 0.921966 c
2.680620 0.942637 l
3.203021 1.599372 4.310469 2.634761 6.369556 2.934145 c
h
f*
n
Q
endstream
endobj
3 0 obj
3854
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 18.000000 18.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000003944 00000 n
0000003967 00000 n
0000004140 00000 n
0000004214 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
4273
%%EOF

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "heart_18.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,95 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 2.024963 2.215471 cm
0.000000 0.000000 0.000000 scn
0.000000 8.477227 m
0.000000 11.090064 1.837903 13.009529 4.216708 13.009529 c
5.378695 13.009529 6.314748 12.504827 6.976930 11.731193 c
7.642573 12.506644 8.575254 13.009529 9.739605 13.009529 c
12.119733 13.009529 13.950001 11.088655 13.950001 8.477227 c
13.950001 5.445473 11.442898 2.625515 7.886870 0.335408 c
7.883046 0.332945 l
7.883036 0.332960 l
7.773217 0.263408 7.639669 0.186707 7.499969 0.125453 c
7.373253 0.069893 7.182288 0.000000 6.975000 0.000000 c
6.768435 0.000000 6.577350 0.071704 6.454765 0.125453 c
6.317346 0.185706 6.184213 0.261022 6.074458 0.328291 c
6.063049 0.335283 l
6.063130 0.335408 l
2.507103 2.625515 0.000000 5.445473 0.000000 8.477227 c
h
4.216708 11.809529 m
2.568532 11.809529 1.200000 10.497311 1.200000 8.477227 c
1.200000 6.107325 3.209978 3.601517 6.707050 1.348038 c
6.790931 1.296850 6.870368 1.253510 6.936634 1.224454 c
6.952608 1.217450 6.966116 1.212033 6.977225 1.207909 c
6.988396 1.211988 7.002014 1.217401 7.018100 1.224454 c
7.083025 1.252921 7.160221 1.295694 7.239110 1.345563 c
10.738551 3.599682 12.750001 6.106457 12.750001 8.477227 c
12.750001 10.498720 11.386456 11.809529 9.739605 11.809529 c
8.743175 11.809529 7.985357 11.273088 7.501738 10.386451 c
7.396244 10.193047 7.193245 10.073009 6.972942 10.073765 c
6.752639 10.074521 6.550468 10.195948 6.446304 10.390072 c
5.975964 11.266614 5.213501 11.809529 4.216708 11.809529 c
h
f*
n
Q
endstream
endobj
3 0 obj
1521
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 18.000000 18.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000001611 00000 n
0000001634 00000 n
0000001807 00000 n
0000001881 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1940
%%EOF

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "settings_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,290 @@
%PDF-1.7
1 0 obj
<< /Type /XObject
/Length 2 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 30.000000 30.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
1.000000 0.584314 0.000000 scn
0.000000 18.799999 m
0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c
1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c
5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c
18.799999 30.000000 l
22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c
27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c
30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c
30.000000 11.200001 l
30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c
28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c
24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c
11.200000 0.000000 l
7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c
2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c
0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c
0.000000 18.799999 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 6.000000 9.000000 cm
1.000000 1.000000 1.000000 scn
17.000078 10.000000 m
17.552362 10.000000 18.000078 10.447716 18.000078 11.000000 c
18.000078 11.552285 17.552362 12.000000 17.000078 12.000000 c
1.000078 12.000000 l
0.447794 12.000000 0.000078 11.552285 0.000078 11.000000 c
0.000078 10.447716 0.447794 10.000000 1.000078 10.000000 c
17.000078 10.000000 l
h
17.000000 0.000078 m
17.552284 0.000078 18.000000 0.447793 18.000000 1.000078 c
18.000000 1.552363 17.552284 2.000078 17.000000 2.000078 c
1.000000 2.000078 l
0.447716 2.000078 0.000000 1.552363 0.000000 1.000078 c
0.000000 0.447793 0.447716 0.000078 1.000000 0.000078 c
17.000000 0.000078 l
h
f*
n
Q
q
q
1.000000 0.000000 -0.000000 1.000000 6.000000 16.000000 cm
1.000000 0.584314 0.000000 scn
8.000000 4.000000 m
8.000000 1.790861 6.209139 0.000000 4.000000 0.000000 c
1.790861 0.000000 0.000000 1.790861 0.000000 4.000000 c
0.000000 6.209139 1.790861 8.000000 4.000000 8.000000 c
6.209139 8.000000 8.000000 6.209139 8.000000 4.000000 c
h
f
n
Q
14.000000 20.000000 m
14.000000 17.790861 12.209139 16.000000 10.000000 16.000000 c
7.790861 16.000000 6.000000 17.790861 6.000000 20.000000 c
6.000000 22.209139 7.790861 24.000000 10.000000 24.000000 c
12.209139 24.000000 14.000000 22.209139 14.000000 20.000000 c
h
W*
n
q
1.000000 0.000000 -0.000000 1.000000 6.000000 16.000000 cm
1.000000 1.000000 1.000000 scn
6.000000 4.000000 m
6.000000 2.895431 5.104569 2.000000 4.000000 2.000000 c
4.000000 -2.000000 l
7.313708 -2.000000 10.000000 0.686292 10.000000 4.000000 c
6.000000 4.000000 l
h
4.000000 2.000000 m
2.895431 2.000000 2.000000 2.895431 2.000000 4.000000 c
-2.000000 4.000000 l
-2.000000 0.686292 0.686292 -2.000000 4.000000 -2.000000 c
4.000000 2.000000 l
h
2.000000 4.000000 m
2.000000 5.104569 2.895431 6.000000 4.000000 6.000000 c
4.000000 10.000000 l
0.686292 10.000000 -2.000000 7.313708 -2.000000 4.000000 c
2.000000 4.000000 l
h
4.000000 6.000000 m
5.104569 6.000000 6.000000 5.104569 6.000000 4.000000 c
10.000000 4.000000 l
10.000000 7.313708 7.313708 10.000000 4.000000 10.000000 c
4.000000 6.000000 l
h
f
n
Q
Q
q
q
1.000000 0.000000 -0.000000 1.000000 16.000000 6.000000 cm
1.000000 0.584314 0.000000 scn
8.000000 4.000000 m
8.000000 1.790861 6.209139 0.000000 4.000000 0.000000 c
1.790861 0.000000 0.000000 1.790861 0.000000 4.000000 c
0.000000 6.209139 1.790861 8.000000 4.000000 8.000000 c
6.209139 8.000000 8.000000 6.209139 8.000000 4.000000 c
h
f
n
Q
24.000000 10.000000 m
24.000000 7.790861 22.209139 6.000000 20.000000 6.000000 c
17.790861 6.000000 16.000000 7.790861 16.000000 10.000000 c
16.000000 12.209139 17.790861 14.000000 20.000000 14.000000 c
22.209139 14.000000 24.000000 12.209139 24.000000 10.000000 c
h
W*
n
q
1.000000 0.000000 -0.000000 1.000000 16.000000 6.000000 cm
1.000000 1.000000 1.000000 scn
6.000000 4.000000 m
6.000000 2.895431 5.104569 2.000000 4.000000 2.000000 c
4.000000 -2.000000 l
7.313708 -2.000000 10.000000 0.686292 10.000000 4.000000 c
6.000000 4.000000 l
h
4.000000 2.000000 m
2.895431 2.000000 2.000000 2.895431 2.000000 4.000000 c
-2.000000 4.000000 l
-2.000000 0.686292 0.686292 -2.000000 4.000000 -2.000000 c
4.000000 2.000000 l
h
2.000000 4.000000 m
2.000000 5.104569 2.895431 6.000000 4.000000 6.000000 c
4.000000 10.000000 l
0.686292 10.000000 -2.000000 7.313708 -2.000000 4.000000 c
2.000000 4.000000 l
h
4.000000 6.000000 m
5.104569 6.000000 6.000000 5.104569 6.000000 4.000000 c
10.000000 4.000000 l
10.000000 7.313708 7.313708 10.000000 4.000000 10.000000 c
4.000000 6.000000 l
h
f
n
Q
Q
endstream
endobj
2 0 obj
4505
endobj
3 0 obj
<< /Type /XObject
/Length 4 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 30.000000 30.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.000000 0.000000 0.000000 scn
0.000000 18.799999 m
0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c
1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c
5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c
18.799999 30.000000 l
22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c
27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c
30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c
30.000000 11.200001 l
30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c
28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c
24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c
11.200000 0.000000 l
7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c
2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c
0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c
0.000000 18.799999 l
h
f
n
Q
endstream
endobj
4 0 obj
944
endobj
5 0 obj
<< /XObject << /X1 1 0 R >>
/ExtGState << /E1 << /SMask << /Type /Mask
/G 3 0 R
/S /Alpha
>>
/Type /ExtGState
>> >>
>>
endobj
6 0 obj
<< /Length 7 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
/E1 gs
/X1 Do
Q
endstream
endobj
7 0 obj
46
endobj
8 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 5 0 R
/Contents 6 0 R
/Parent 9 0 R
>>
endobj
9 0 obj
<< /Kids [ 8 0 R ]
/Count 1
/Type /Pages
>>
endobj
10 0 obj
<< /Pages 9 0 R
/Type /Catalog
>>
endobj
xref
0 11
0000000000 65535 f
0000000010 00000 n
0000004763 00000 n
0000004786 00000 n
0000005978 00000 n
0000006000 00000 n
0000006298 00000 n
0000006400 00000 n
0000006421 00000 n
0000006594 00000 n
0000006668 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 10 0 R
/Size 11
>>
startxref
6728
%%EOF

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "person_6.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,78 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.018066 0.500000 cm
0.000000 0.000000 0.000000 scn
2.980786 3.972965 m
3.816679 3.972965 4.494304 4.650590 4.494304 5.486483 c
4.494304 6.322375 3.816679 7.000000 2.980786 7.000000 c
2.144894 7.000000 1.467269 6.322375 1.467269 5.486483 c
1.467269 4.650590 2.144894 3.972965 2.980786 3.972965 c
h
5.734580 1.858224 m
5.255256 2.484169 4.423840 3.027039 2.981694 3.027039 c
1.539547 3.027039 0.708129 2.484169 0.228806 1.858224 c
-0.442749 0.981247 0.471706 0.000003 1.576275 0.000003 c
4.387107 0.000003 l
5.491676 0.000003 6.406134 0.981247 5.734580 1.858224 c
h
f*
n
Q
endstream
endobj
3 0 obj
639
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 6.000000 8.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000000729 00000 n
0000000751 00000 n
0000000922 00000 n
0000000996 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1055
%%EOF

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "lockedaudiototext.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@ -0,0 +1,202 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 3.834961 10.247070 cm
0.000000 0.000000 0.000000 scn
14.784782 9.993996 m
14.685381 10.249602 14.439255 10.417969 14.165000 10.417969 c
13.890745 10.417969 13.644619 10.249602 13.545217 9.993996 c
10.045217 0.993996 l
9.912102 0.651699 10.081676 0.266301 10.423973 0.133185 c
10.766270 0.000071 11.151668 0.169645 11.284783 0.511942 c
12.286572 3.087969 l
15.680867 3.087969 l
15.758190 3.902153 16.114431 4.635052 16.652981 5.190056 c
16.534782 5.493996 l
14.784782 9.993996 l
h
15.526207 4.417969 m
12.803794 4.417969 l
14.165000 7.918214 l
15.295218 5.011942 l
15.526207 4.417969 l
h
3.694774 8.723195 m
3.954473 8.982893 4.375527 8.982893 4.635226 8.723195 c
8.135226 5.223195 l
8.394924 4.963496 8.394924 4.542441 8.135226 4.282743 c
4.635226 0.782743 l
4.375527 0.523045 3.954473 0.523045 3.694774 0.782743 c
3.435075 1.042441 3.435075 1.463496 3.694774 1.723194 c
6.059548 4.087969 l
0.665000 4.087969 l
0.297731 4.087969 0.000000 4.385699 0.000000 4.752969 c
0.000000 5.120238 0.297731 5.417969 0.665000 5.417969 c
6.059548 5.417969 l
3.694774 7.782743 l
3.435075 8.042441 3.435075 8.463496 3.694774 8.723195 c
h
f*
n
Q
q
1.000000 0.000000 -0.000000 1.000000 21.000000 7.669922 cm
0.898039 0.949020 1.000000 scn
3.335000 1.330078 m
3.335000 0.962809 3.632731 0.665078 4.000000 0.665078 c
4.367270 0.665078 4.665000 0.962809 4.665000 1.330078 c
3.335000 1.330078 l
h
-0.665000 1.330078 m
-0.665000 0.962809 -0.367269 0.665078 0.000000 0.665078 c
0.367269 0.665078 0.665000 0.962809 0.665000 1.330078 c
-0.665000 1.330078 l
h
3.335000 5.330078 m
3.335000 1.330078 l
4.665000 1.330078 l
4.665000 5.330078 l
3.335000 5.330078 l
h
0.665000 1.330078 m
0.665000 5.330078 l
-0.665000 5.330078 l
-0.665000 1.330078 l
0.665000 1.330078 l
h
2.000000 6.665078 m
2.737300 6.665078 3.335000 6.067378 3.335000 5.330078 c
4.665000 5.330078 l
4.665000 6.801917 3.471839 7.995078 2.000000 7.995078 c
2.000000 6.665078 l
h
2.000000 7.995078 m
0.528161 7.995078 -0.665000 6.801917 -0.665000 5.330078 c
0.665000 5.330078 l
0.665000 6.067378 1.262700 6.665078 2.000000 6.665078 c
2.000000 7.995078 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 21.000000 7.669922 cm
0.000000 0.000000 0.000000 scn
3.335000 1.330078 m
3.335000 0.962809 3.632731 0.665078 4.000000 0.665078 c
4.367270 0.665078 4.665000 0.962809 4.665000 1.330078 c
3.335000 1.330078 l
h
-0.665000 1.330078 m
-0.665000 0.962809 -0.367269 0.665078 0.000000 0.665078 c
0.367269 0.665078 0.665000 0.962809 0.665000 1.330078 c
-0.665000 1.330078 l
h
3.335000 5.330078 m
3.335000 1.330078 l
4.665000 1.330078 l
4.665000 5.330078 l
3.335000 5.330078 l
h
0.665000 1.330078 m
0.665000 5.330078 l
-0.665000 5.330078 l
-0.665000 1.330078 l
0.665000 1.330078 l
h
2.000000 6.665078 m
2.737300 6.665078 3.335000 6.067378 3.335000 5.330078 c
4.665000 5.330078 l
4.665000 6.801917 3.471839 7.995078 2.000000 7.995078 c
2.000000 6.665078 l
h
2.000000 7.995078 m
0.528161 7.995078 -0.665000 6.801917 -0.665000 5.330078 c
0.665000 5.330078 l
0.665000 6.067378 1.262700 6.665078 2.000000 6.665078 c
2.000000 7.995078 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 19.500000 6.000000 cm
0.000000 0.000000 0.000000 scn
0.000000 3.000000 m
0.000000 3.931883 0.000000 4.397825 0.152241 4.765367 c
0.355229 5.255423 0.744577 5.644771 1.234633 5.847759 c
1.602175 6.000000 2.068117 6.000000 3.000000 6.000000 c
4.000000 6.000000 l
4.931883 6.000000 5.397825 6.000000 5.765367 5.847759 c
6.255423 5.644771 6.644771 5.255423 6.847759 4.765367 c
7.000000 4.397825 7.000000 3.931883 7.000000 3.000000 c
7.000000 3.000000 l
7.000000 2.068117 7.000000 1.602175 6.847759 1.234633 c
6.644771 0.744577 6.255423 0.355228 5.765367 0.152241 c
5.397825 0.000000 4.931883 0.000000 4.000000 0.000000 c
3.000000 0.000000 l
2.068117 0.000000 1.602175 0.000000 1.234633 0.152241 c
0.744577 0.355228 0.355229 0.744577 0.152241 1.234633 c
0.000000 1.602175 0.000000 2.068117 0.000000 3.000000 c
0.000000 3.000000 l
h
f
n
Q
endstream
endobj
3 0 obj
3999
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000004089 00000 n
0000004112 00000 n
0000004285 00000 n
0000004359 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
4418
%%EOF

View File

@ -3694,7 +3694,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self, let (id, statsDatacenterId) = messageIdAndStatsDatacenterId, let statsDatacenterId = statsDatacenterId else { guard let strongSelf = self, let (id, statsDatacenterId) = messageIdAndStatsDatacenterId, let statsDatacenterId = statsDatacenterId else {
return return
} }
strongSelf.push(messageStatsController(context: context, messageId: id, statsDatacenterId: statsDatacenterId)) strongSelf.push(messageStatsController(context: context, subject: .message(id: id), statsDatacenterId: statsDatacenterId))
}) })
}, delay: true) }, delay: true)
}, editMessageMedia: { [weak self] messageId, draw in }, editMessageMedia: { [weak self] messageId, draw in