diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 1479972919..1f5cfbd08d 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -10498,7 +10498,13 @@ Sorry for the inconvenience."; "Stats.MessageReactionsTitle" = "REACTIONS"; +"MediaEditor.RemoveVideo" = "Remove Video"; + "Conversation.LaunchApp" = "LAUNCH APP"; "Message.AdSponsoredLabel" = "Sponsored"; "Message.AdRecommendedLabel" = "Recommended"; + +"Stats.StoryTitle" = "Story Statistics"; + +"Channel.Info.Settings" = "Channel Settings"; diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index e81ee30de1..d6c0d7db81 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -211,9 +211,9 @@ private enum ChatListRecentEntry: Comparable, Identifiable { if peer.unreadCount > 0 { badge = ContactsPeerItemBadge(count: peer.unreadCount, type: isMuted ? .inactive : .active) } - + 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, displayOrder: nameDisplayOrder, context: context, diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 68839ef501..91d5a7d2f4 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -355,7 +355,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL } } 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, title: title, image: image, @@ -569,7 +569,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL } 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, displayOrder: presentationData.nameDisplayOrder, context: context, @@ -608,7 +608,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL let status: ContactsPeerItemStatus = .none 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, displayOrder: presentationData.nameDisplayOrder, context: context, @@ -670,7 +670,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL let peerContent: ContactsPeerItemPeer = .peer(peer: contactEntry.peer, chatPeer: contactEntry.peer) 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, displayOrder: presentationData.nameDisplayOrder, context: context, @@ -889,7 +889,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL } 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, displayOrder: presentationData.nameDisplayOrder, context: context, @@ -928,7 +928,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL let status: ContactsPeerItemStatus = .none 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, displayOrder: presentationData.nameDisplayOrder, context: context, @@ -990,7 +990,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL let peerContent: ContactsPeerItemPeer = .peer(peer: contactEntry.peer, chatPeer: contactEntry.peer) 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, displayOrder: presentationData.nameDisplayOrder, context: context, @@ -1057,7 +1057,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL } } 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, title: title, image: image, diff --git a/submodules/ItemListUI/Sources/ItemListItem.swift b/submodules/ItemListUI/Sources/ItemListItem.swift index ca0b6987a4..7cede4afcb 100644 --- a/submodules/ItemListUI/Sources/ItemListItem.swift +++ b/submodules/ItemListUI/Sources/ItemListItem.swift @@ -161,12 +161,14 @@ public final class ItemListPresentationData: Equatable { public let fontSize: PresentationFontSize public let strings: PresentationStrings 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.fontSize = fontSize self.strings = strings self.nameDisplayOrder = nameDisplayOrder + self.dateTimeFormat = dateTimeFormat } public static func ==(lhs: ItemListPresentationData, rhs: ItemListPresentationData) -> Bool { @@ -182,6 +184,9 @@ public final class ItemListPresentationData: Equatable { if lhs.nameDisplayOrder != rhs.nameDisplayOrder { return false } + if lhs.dateTimeFormat != rhs.dateTimeFormat { + return false + } return true } } @@ -232,6 +237,6 @@ public extension PresentationFontSize { public extension ItemListPresentationData { 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) } } diff --git a/submodules/StatisticsUI/BUILD b/submodules/StatisticsUI/BUILD index 94f62712b7..05622529bd 100644 --- a/submodules/StatisticsUI/BUILD +++ b/submodules/StatisticsUI/BUILD @@ -13,6 +13,7 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", + "//submodules/ComponentFlow", "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", @@ -35,6 +36,7 @@ swift_library( "//submodules/PremiumUI:PremiumUI", "//submodules/InviteLinksUI:InviteLinksUI", "//submodules/ShareController:ShareController", + "//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index 4f8aab4445..3dbbc7ee31 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -27,7 +27,7 @@ private let initialBoostersDisplayedLimit: Int32 = 5 private final class ChannelStatsControllerArguments { let context: AccountContext let loadDetailedGraph: (StatsGraph, Int64) -> Signal - let openMessageStats: (MessageId) -> Void + let openPostStats: (EnginePeer, StatsPostItem) -> Void let contextAction: (MessageId, ASDisplayNode, ContextGesture?) -> Void let copyBoostLink: (String) -> Void let shareBoostLink: (String) -> Void @@ -37,10 +37,10 @@ private final class ChannelStatsControllerArguments { let createPrepaidGiveaway: (PrepaidGiveaway) -> Void let updateGiftsSelected: (Bool) -> Void - init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal, 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, 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.loadDetailedGraph = loadDetailedGraph - self.openMessageStats = openMessage + self.openPostStats = openPostStats self.contextAction = contextAction self.copyBoostLink = copyBoostLink self.shareBoostLink = shareBoostLink @@ -75,6 +75,45 @@ private enum StatsSection: Int32 { 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 { case overviewTitle(PresentationTheme, String, String) case overview(PresentationTheme, ChannelStats) @@ -116,7 +155,7 @@ private enum StatsEntry: ItemListNodeEntry { case instantPageInteractionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType) 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) @@ -242,7 +281,7 @@ private enum StatsEntry: ItemListNodeEntry { return 25 case .postsTitle: return 26 - case let .post(index, _, _, _, _, _): + case let .post(index, _, _, _, _, _, _): return 27 + index case .boostLevel: return 2000 @@ -445,8 +484,8 @@ private enum StatsEntry: ItemListNodeEntry { } else { return false } - case let .post(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsMessage, lhsInteractions): - if case let .post(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsMessage, rhsInteractions) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsMessage.id == rhsMessage.id, lhsInteractions == rhsInteractions { + case let .post(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsPost, lhsInteractions): + 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 } else { return false @@ -610,12 +649,14 @@ private enum StatsEntry: ItemListNodeEntry { } }) }, sectionId: self.section, style: .blocks) - case let .post(_, _, _, _, message, interactions): - return StatsMessageItem(context: arguments.context, presentationData: presentationData, message: message, views: interactions.views, forwards: interactions.forwards, sectionId: self.section, style: .blocks, action: { - arguments.openMessageStats(message.id) - }, contextAction: { node, gesture in - arguments.contextAction(message.id, node, gesture) - }) + case let .post(_, _, _, _, peer, post, interactions): + 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.openPostStats(EnginePeer(peer), post) + }, contextAction: !post.isStory ? { node, gesture in + if case let .message(message) = post { + arguments.contextAction(message.id, node, gesture) + } + } : nil) 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 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] = [] 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)) } - 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)) var index: Int32 = 0 - for message in messages { - if let interactions = interactions[message.id] { - entries.append(.post(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, message, interactions)) - index += 1 + for post in posts { + switch post { + case let .message(message): + 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 }) - var openMessageStatsImpl: ((MessageId) -> Void)? + var openPostStatsImpl: ((EnginePeer, StatsPostItem) -> Void)? var contextActionImpl: ((MessageId, ASDisplayNode, ContextGesture?) -> Void)? let actionsDisposable = DisposableSet() let dataPromise = Promise(nil) let messagesPromise = Promise(nil) + let storiesPromise = Promise() + let datacenterId: Int32 = statsDatacenterId ?? 0 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 in return statsContext.loadDetailedGraph(graph, x: x) - }, openMessage: { messageId in - openMessageStatsImpl?(messageId) + }, openPostStats: { peer, item in + openPostStatsImpl?(peer, item) }, contextAction: { messageId, node, gesture in contextActionImpl?(messageId, node, gesture) }, copyBoostLink: { link in @@ -1138,6 +1202,16 @@ public func channelStatsController(context: AccountContext, updatedPresentationD } 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 = .single(false) |> then(.single(true) |> delay(2.0, queue: Queue.mainQueue())) let previousData = Atomic(value: nil) @@ -1149,13 +1223,14 @@ public func channelStatsController(context: AccountContext, updatedPresentationD context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)), dataPromise.get(), messagesPromise.get(), + storiesPromise.get(), boostData, boostsContext.state, giftsContext.state, longLoadingSignal ) |> 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) var emptyStateItem: ItemListControllerEmptyStateItem? 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 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)) } |> afterDisposed { actionsDisposable.dispose() let _ = statsContext.state + let _ = storyList.state } let controller = ItemListController(context: context, state: signal) @@ -1206,8 +1282,15 @@ public func channelStatsController(context: AccountContext, updatedPresentationD controller.didDisappear = { [weak controller] _ in controller?.clearItemNodesHighlight(animated: true) } - openMessageStatsImpl = { [weak controller] messageId in - controller?.push(messageStatsController(context: context, messageId: messageId, statsDatacenterId: statsDatacenterId)) + openPostStatsImpl = { [weak controller] peer, post in + 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 guard let controller = controller, let sourceNode = sourceNode as? ContextExtractedContentContainingNode else { diff --git a/submodules/StatisticsUI/Sources/MessageStatsController.swift b/submodules/StatisticsUI/Sources/MessageStatsController.swift index 0e2758ef6f..5bab1e8de0 100644 --- a/submodules/StatisticsUI/Sources/MessageStatsController.swift +++ b/submodules/StatisticsUI/Sources/MessageStatsController.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import Display import SwiftSignalKit +import AsyncDisplayKit import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -35,7 +36,7 @@ private enum StatsSection: Int32 { private enum StatsEntry: ItemListNodeEntry { case overviewTitle(PresentationTheme, String) - case overview(PresentationTheme, MessageStats, Int32?) + case overview(PresentationTheme, PostStats, Int32?) case interactionsTitle(PresentationTheme, String) case interactionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType) @@ -89,8 +90,14 @@ private enum StatsEntry: ItemListNodeEntry { return false } case let .overview(lhsTheme, lhsStats, lhsPublicShares): - if case let .overview(rhsTheme, rhsStats, rhsPublicShares) = rhs, lhsTheme === rhsTheme, lhsStats == rhsStats, lhsPublicShares == rhsPublicShares { - return true + if case let .overview(rhsTheme, rhsStats, rhsPublicShares) = rhs, lhsTheme === rhsTheme, lhsPublicShares == rhsPublicShares { + 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 { 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] = [] if let data = data { @@ -212,44 +219,98 @@ private func messageStatsControllerEntries(data: MessageStats?, messages: Search 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)? let actionsDisposable = DisposableSet() - let dataPromise = Promise(nil) + let dataPromise = Promise(nil) let messagesPromise = Promise<(SearchMessagesResult, SearchMessagesState)?>(nil) let datacenterId: Int32 = statsDatacenterId ?? 0 - let statsContext = MessageStatsContext(postbox: context.account.postbox, network: context.account.network, datacenterId: datacenterId, messageId: messageId) - let dataSignal: Signal = statsContext.state - |> map { state in - return state.stats + let anyStatsContext: Any + let dataSignal: Signal + var loadDetailedGraphImpl: ((StatsGraph, Int64) -> Signal)? + 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 in - return statsContext.loadDetailedGraph(graph, x: x) + return loadDetailedGraphImpl?(graph, x) ?? .single(nil) }, openMessage: { messageId in navigateToMessageImpl?(messageId) }) let longLoadingSignal: Signal = .single(false) |> then(.single(true) |> delay(2.0, queue: Queue.mainQueue())) - let previousData = Atomic(value: nil) + let previousData = Atomic(value: nil) - let searchSignal = context.engine.messages.searchMessages(location: .publicForwards(messageId: messageId, datacenterId: Int(datacenterId)), query: "", state: nil) - |> map(Optional.init) - |> afterNext { result in - if let result = result { - for message in result.0.messages { - if let peer = message.peers[message.id.peerId], let peerReference = PeerReference(peer) { - let _ = context.engine.peers.updatedRemotePeer(peer: peerReference).start() + if case let .message(id) = subject { + let searchSignal = context.engine.messages.searchMessages(location: .publicForwards(messageId: id, datacenterId: Int(datacenterId)), query: "", state: nil) + |> map(Optional.init) + |> afterNext { result in + if let result = result { + for message in result.0.messages { + 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) |> 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) return (controllerState, (listState, arguments)) } |> afterDisposed { actionsDisposable.dispose() - let _ = statsContext.state + let _ = anyStatsContext } 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?.clearItemNodesHighlight(animated: true) } diff --git a/submodules/StatisticsUI/Sources/MessageStatsOverviewItem.swift b/submodules/StatisticsUI/Sources/MessageStatsOverviewItem.swift index 22a0549e82..d3f0869598 100644 --- a/submodules/StatisticsUI/Sources/MessageStatsOverviewItem.swift +++ b/submodules/StatisticsUI/Sources/MessageStatsOverviewItem.swift @@ -10,12 +10,12 @@ import PresentationDataUtils class MessageStatsOverviewItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData - let stats: MessageStats + let stats: PostStats let publicShares: Int32? let sectionId: ItemListSectionId 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.stats = stats self.publicShares = publicShares diff --git a/submodules/StatisticsUI/Sources/StatsMessageItem.swift b/submodules/StatisticsUI/Sources/StatsMessageItem.swift index 4987f30446..9fd3dd08a8 100644 --- a/submodules/StatisticsUI/Sources/StatsMessageItem.swift +++ b/submodules/StatisticsUI/Sources/StatsMessageItem.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import Display import AsyncDisplayKit +import ComponentFlow import SwiftSignalKit import Postbox import TelegramCore @@ -11,23 +12,28 @@ import TelegramStringFormatting import ItemListUI import PresentationDataUtils import PhotoResources +import AvatarStoryIndicatorComponent public class StatsMessageItem: ListViewItem, ItemListItem { let context: AccountContext let presentationData: ItemListPresentationData - let message: Message + let peer: Peer + let item: StatsPostItem let views: Int32 + let reactions: Int32 let forwards: Int32 public let sectionId: ItemListSectionId let style: ItemListStyle let action: (() -> 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.presentationData = presentationData - self.message = message + self.peer = peer + self.item = item self.views = views + self.reactions = reactions self.forwards = forwards self.sectionId = sectionId self.style = style @@ -79,7 +85,7 @@ public class StatsMessageItem: ListViewItem, ItemListItem { private let badgeFont = Font.regular(15.0) -public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode { +final class StatsMessageItemNode: ListViewItemNode, ItemListItemNode { private let backgroundNode: ASDisplayNode private let topStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode @@ -96,9 +102,14 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode { private var nonExtractedRect: CGRect? let contentImageNode: TransformImageNode + var storyIndicator: ComponentView? let titleNode: TextNode let labelNode: TextNode let viewsNode: TextNode + + let reactionsIconNode: ASImageNode + let reactionsNode: TextNode + let forwardsIconNode: ASImageNode let forwardsNode: TextNode private let activateArea: AccessibilityAreaNode @@ -152,6 +163,15 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode { self.forwardsNode = TextNode() 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.isLayerBacked = true @@ -172,6 +192,9 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode { self.offsetContainerNode.addSubnode(self.labelNode) self.countersContainerNode.addSubnode(self.viewsNode) 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.addSubnode(self.activateArea) @@ -200,8 +223,8 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode { 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.offsetContainerNode.layer, offset: CGPoint(x: isExtracted ? 12.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 ? 16.0 : 0.0, y: 0.0)) transition.updateAlpha(node: strongSelf.extractedBackgroundImageNode, alpha: isExtracted ? 1.0 : 0.0, completion: { _ in if !isExtracted { @@ -215,6 +238,7 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode { let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeLabelLayout = TextNode.asyncLayout(self.labelNode) let makeViewsLayout = TextNode.asyncLayout(self.viewsNode) + let makeReactionsLayout = TextNode.asyncLayout(self.reactionsNode) let makeForwardsLayout = TextNode.asyncLayout(self.forwardsNode) let currentItem = self.item @@ -236,67 +260,100 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode { let leftInset = 16.0 + params.leftInset let rightInset = 16.0 + params.rightInset 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 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? - for media in item.message.media { - if let image = media as? TelegramMediaImage { - contentImageMedia = image - break - } else if let file = media as? TelegramMediaFile { - if file.isVideo && !file.isInstantVideo { - contentImageMedia = file - break - } - } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { - if let image = content.image { + let timestamp: Int32 + + switch item.item { + case let .message(message): + let contentKind: MessageContentKind + 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) + text = !message.text.isEmpty ? message.text : stringForMediaKind(contentKind, strings: item.presentationData.strings).0.string + + for media in message.media { + if let image = media as? TelegramMediaImage { contentImageMedia = image break - } else if let file = content.file { + } else if let file = media as? TelegramMediaFile { if file.isVideo && !file.isInstantVideo { contentImageMedia = file 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 { - totalLeftInset += 48.0 + totalLeftInset += 46.0 } var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? if let contentImageMedia = contentImageMedia { if let currentContentImageMedia = currentContentImageMedia, contentImageMedia.isSemanticallyEqual(to: currentContentImageMedia) { } else { - if let image = contentImageMedia as? TelegramMediaImage { - updateImageSignal = mediaGridMessagePhoto(account: item.context.account, userLocation: .peer(item.message.id.peerId), photoReference: .message(message: MessageReference(item.message), media: image)) - } else if let file = contentImageMedia as? TelegramMediaFile { - updateImageSignal = mediaGridMessageVideo(postbox: item.context.account.postbox, userLocation: .peer(item.message.id.peerId), videoReference: .message(message: MessageReference(item.message), media: file), autoFetchFullSizeThumbnail: true) + switch item.item { + case let .message(message): + if let image = contentImageMedia as? TelegramMediaImage { + 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 (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 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 if let strongSelf = self { + let themeUpdated = strongSelf.item?.presentationData.theme !== item.presentationData.theme 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 extractedRect = CGRect(origin: CGPoint(), size: layout.contentSize).insetBy(dx: 16.0 + params.leftInset, dy: 0.0) strongSelf.extractedRect = extractedRect @@ -357,13 +420,21 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode { 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 { 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() if let updateImageSignal = updateImageSignal { @@ -384,6 +455,7 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode { let _ = labelApply() let _ = viewsApply() let _ = forwardsApply() + let _ = reactionsApply() switch item.style { case .plain: @@ -427,7 +499,7 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode { let bottomStripeInset: CGFloat switch neighbors.bottom { case .sameSection(false): - bottomStripeInset = leftInset + bottomStripeInset = totalLeftInset strongSelf.bottomStripeNode.isHidden = false default: 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)) } - 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 - 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 let labelFrame = CGRect(origin: CGPoint(x: totalLeftInset, y: titleFrame.maxY + titleSpacing), size: labelLayout.size) 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 - let forwardsFrame = CGRect(origin: CGPoint(x: params.width - rightInset - forwardsLayout.size.width, y: titleFrame.maxY + titleSpacing), size: forwardsLayout.size) - strongSelf.forwardsNode.frame = forwardsFrame - + let iconSpacing: CGFloat = 3.0 - UIScreenPixel + + 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)) + + 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 + 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 + } } }) } diff --git a/submodules/StatisticsUI/Sources/StoryIconNode.swift b/submodules/StatisticsUI/Sources/StoryIconNode.swift new file mode 100644 index 0000000000..de2f823d41 --- /dev/null +++ b/submodules/StatisticsUI/Sources/StoryIconNode.swift @@ -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() + + 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) + } +} diff --git a/submodules/TelegramCore/Sources/Statistics/PeerStatistics.swift b/submodules/TelegramCore/Sources/Statistics/PeerStatistics.swift index 38dc52b287..9a4b1ffd35 100644 --- a/submodules/TelegramCore/Sources/Statistics/PeerStatistics.swift +++ b/submodules/TelegramCore/Sources/Statistics/PeerStatistics.swift @@ -52,6 +52,14 @@ public struct ChannelStatsMessageInteractions: Equatable { public let messageId: MessageId public let views: 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 { @@ -1143,7 +1151,7 @@ extension ChannelStatsMessageInteractions { init(apiMessageInteractionCounters: Api.MessageInteractionCounters, peerId: PeerId) { switch apiMessageInteractionCounters { 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) } } } diff --git a/submodules/TelegramPresentationData/Sources/PresentationTheme.swift b/submodules/TelegramPresentationData/Sources/PresentationTheme.swift index 064c18d7b4..fb7f905772 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationTheme.swift @@ -17,6 +17,10 @@ public final class PresentationThemeGradientColors { return (self.topColor, self.bottomColor) } + public var array: [UIColor] { + return [self.topColor, self.bottomColor] + } + public func withUpdated(topColor: UIColor? = nil, bottomColor: UIColor? = nil) -> PresentationThemeGradientColors { return PresentationThemeGradientColors(topColor: topColor ?? self.topColor, bottomColor: bottomColor ?? self.bottomColor) } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 91719b2439..8fd981bdd9 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -72,6 +72,9 @@ public enum PresentationResourceKey: Int32 { case itemListTopicArrowIcon case itemListAddBoostsIcon + case statsReactionsIcon + case statsForwardsIcon + case itemListVoiceCallIcon case itemListVideoCallIcon diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift index 3949ad4c0c..c25c954ed6 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift @@ -329,4 +329,16 @@ public struct PresentationResourcesItemList { 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) + }) + } } diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index ef03a99709..a81d01a6fd 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -1123,7 +1123,7 @@ private final class CameraScreenComponent: CombinedComponent { component: MultilineTextComponent( text: .plain(NSAttributedString(string: durationString, font: Font.with(size: 21.0, design: .camera), textColor: controlsTintColor)), horizontalAlignment: .center, - textShadowColor: UIColor(rgb: 0x000000, alpha: 0.2) + textShadowColor: controlsTintColor == .black ? .clear : UIColor(rgb: 0x000000, alpha: 0.2) ), availableSize: context.availableSize, transition: context.transition diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/BUILD index cb366f3473..8b096329df 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/BUILD @@ -31,6 +31,7 @@ swift_library( "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", "//submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath", "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BundleIconComponent", "//submodules/ChatMessageBackground", ], visibility = [ diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index c770e0661f..6a9253ddde 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -561,6 +561,7 @@ private final class PeerInfoInteraction { let displayTopicsLimited: (TopicsLimitedReason) -> Void let openPeerMention: (String, ChatControllerInteractionNavigateToPeer) -> Void let openBotApp: (AttachMenuBot) -> Void + let openEditing: () -> Void init( openUsername: @escaping (String) -> Void, @@ -614,7 +615,8 @@ private final class PeerInfoInteraction { toggleForumTopics: @escaping (Bool) -> Void, displayTopicsLimited: @escaping (TopicsLimitedReason) -> Void, openPeerMention: @escaping (String, ChatControllerInteractionNavigateToPeer) -> Void, - openBotApp: @escaping (AttachMenuBot) -> Void + openBotApp: @escaping (AttachMenuBot) -> Void, + openEditing: @escaping () -> Void ) { self.openUsername = openUsername self.openPhone = openPhone @@ -668,6 +670,7 @@ private final class PeerInfoInteraction { self.displayTopicsLimited = displayTopicsLimited self.openPeerMention = openPeerMention self.openBotApp = openBotApp + self.openEditing = openEditing } } @@ -1208,6 +1211,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese let ItemAdmins = 6 let ItemMembers = 7 let ItemMemberRequests = 8 + let ItemEdit = 9 if let _ = data.threadData { let mainUsername: String @@ -1358,6 +1362,10 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese 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 self?.openBotApp(bot) + }, + openEditing: { [weak self] in + self?.headerNode.navigationButtonContainer.performAction?(.edit, nil, nil) } ) diff --git a/submodules/TelegramUI/Images.xcassets/Chart/Contents.json b/submodules/TelegramUI/Images.xcassets/Chart/Contents.json index 38f0c81fc2..6e965652df 100644 --- a/submodules/TelegramUI/Images.xcassets/Chart/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chart/Contents.json @@ -1,9 +1,9 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 }, "properties" : { "provides-namespace" : true } -} \ No newline at end of file +} diff --git a/submodules/TelegramUI/Images.xcassets/Chart/Forwards.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chart/Forwards.imageset/Contents.json new file mode 100644 index 0000000000..427e056077 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chart/Forwards.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "arrowshape_18.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chart/Forwards.imageset/arrowshape_18.pdf b/submodules/TelegramUI/Images.xcassets/Chart/Forwards.imageset/arrowshape_18.pdf new file mode 100644 index 0000000000..7b29edbc94 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chart/Forwards.imageset/arrowshape_18.pdf @@ -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 \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chart/Reactions.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chart/Reactions.imageset/Contents.json new file mode 100644 index 0000000000..5d62e00060 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chart/Reactions.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "heart_18.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chart/Reactions.imageset/heart_18.pdf b/submodules/TelegramUI/Images.xcassets/Chart/Reactions.imageset/heart_18.pdf new file mode 100644 index 0000000000..7e1b15c187 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chart/Reactions.imageset/heart_18.pdf @@ -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 \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Info/SettingsIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Info/SettingsIcon.imageset/Contents.json new file mode 100644 index 0000000000..c991b6cd92 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Info/SettingsIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "settings_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Info/SettingsIcon.imageset/settings_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Info/SettingsIcon.imageset/settings_30.pdf new file mode 100644 index 0000000000..843c03d75b --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Info/SettingsIcon.imageset/settings_30.pdf @@ -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 \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/Subscriber.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/Subscriber.imageset/Contents.json new file mode 100644 index 0000000000..a6b78fde32 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/Subscriber.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "person_6.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/Subscriber.imageset/person_6.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/Subscriber.imageset/person_6.pdf new file mode 100644 index 0000000000..9c664dc743 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/Subscriber.imageset/person_6.pdf @@ -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 \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/TranscriptionLocked.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/TranscriptionLocked.imageset/Contents.json new file mode 100644 index 0000000000..ec75795a90 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/TranscriptionLocked.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "lockedaudiototext.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/TranscriptionLocked.imageset/lockedaudiototext.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/TranscriptionLocked.imageset/lockedaudiototext.pdf new file mode 100644 index 0000000000..1fadb33c7b --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/TranscriptionLocked.imageset/lockedaudiototext.pdf @@ -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 \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 4c8baf9ea4..d4972dae3d 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -3694,7 +3694,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self, let (id, statsDatacenterId) = messageIdAndStatsDatacenterId, let statsDatacenterId = statsDatacenterId else { return } - strongSelf.push(messageStatsController(context: context, messageId: id, statsDatacenterId: statsDatacenterId)) + strongSelf.push(messageStatsController(context: context, subject: .message(id: id), statsDatacenterId: statsDatacenterId)) }) }, delay: true) }, editMessageMedia: { [weak self] messageId, draw in