From 17a203d6b52486137a992432a0702312787a5d1a Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 16 Nov 2023 18:03:14 +0400 Subject: [PATCH 1/5] Channel recommendation improvements --- .../AvatarNode/Sources/AvatarBadgeView.swift | 138 +++++++++++++----- submodules/Display/Source/UIKitUtils.swift | 6 + ...essageJoinedChannelBubbleContentNode.swift | 47 +++++- 3 files changed, 151 insertions(+), 40 deletions(-) diff --git a/submodules/AvatarNode/Sources/AvatarBadgeView.swift b/submodules/AvatarNode/Sources/AvatarBadgeView.swift index 5bd34a1468..7bc5ac487f 100644 --- a/submodules/AvatarNode/Sources/AvatarBadgeView.swift +++ b/submodules/AvatarNode/Sources/AvatarBadgeView.swift @@ -29,6 +29,9 @@ public final class AvatarBadgeView: UIImageView { private struct Parameters: Equatable { var size: CGSize var text: String + var hasTimeoutIcon: Bool + var useSolidColor: Bool + var strokeColor: UIColor? } private var originalContent: OriginalContent? @@ -50,8 +53,8 @@ public final class AvatarBadgeView: UIImageView { } } - public func update(size: CGSize, text: String) { - let parameters = Parameters(size: size, text: text) + public func update(size: CGSize, text: String, hasTimeoutIcon: Bool = true, useSolidColor: Bool = false, strokeColor: UIColor? = nil) { + let parameters = Parameters(size: size, text: text, hasTimeoutIcon: hasTimeoutIcon, useSolidColor: useSolidColor, strokeColor: strokeColor) if self.parameters != parameters || !self.hasContent { self.parameters = parameters self.update() @@ -192,24 +195,89 @@ public final class AvatarBadgeView: UIImageView { return } - self.image = generateImage(parameters.size, rotatedContext: { size, context in + var solidColor: UIColor? + if parameters.useSolidColor { + let context = DrawingContext(size: CGSize(width: 1.0, height: 1.0), scale: 1.0, clear: false)! + context.withFlippedContext({ context in + if let cgImage = blurredImage.cgImage { + context.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0)) + } + }) + solidColor = context.colorAt(.zero) + } + + var badgeSize = parameters.size + + let strokeWidth: CGFloat = 1.0 + UIScreenPixel + var size = parameters.size + var offset: CGPoint = .zero + if parameters.strokeColor != nil { + offset = CGPoint(x: strokeWidth / 2.0, y: strokeWidth / 2.0) + badgeSize.width += strokeWidth + badgeSize.height += strokeWidth + size.width += strokeWidth * 2.0 + size.height += strokeWidth * 2.0 + } + + self.image = generateImage(size, rotatedContext: { size, context in UIGraphicsPushContext(context) context.clear(CGRect(origin: CGPoint(), size: size)) - context.setBlendMode(.copy) - context.setFillColor(UIColor.black.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) - - blurredImage.draw(in: CGRect(origin: CGPoint(), size: size), blendMode: .sourceIn, alpha: 1.0) - - context.setBlendMode(.normal) - let textColor: UIColor - if isLightImage { - textColor = UIColor(white: 0.7, alpha: 1.0) - } else { + if parameters.useSolidColor { textColor = .white + } else { + if isLightImage { + textColor = UIColor(white: 0.7, alpha: 1.0) + } else { + textColor = .white + } + } + + if var solidColor { + func adjustedBackgroundColor(backgroundColor: UIColor, textColor: UIColor) -> UIColor { + let minContrastRatio: CGFloat = 4.5 + if backgroundColor.contrastRatio(with: textColor) < minContrastRatio { + var hue: CGFloat = 0 + var saturation: CGFloat = 0 + var brightness: CGFloat = 0 + var alpha: CGFloat = 0 + backgroundColor.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) + return UIColor(hue: hue, saturation: saturation, brightness: brightness * 0.9, alpha: alpha) + } else { + return backgroundColor + } + } + solidColor = adjustedBackgroundColor(backgroundColor: solidColor, textColor: textColor) + + if let strokeColor = parameters.strokeColor { + context.setStrokeColor(strokeColor.cgColor) + context.setLineWidth(strokeWidth) + } + + context.setFillColor(solidColor.cgColor) + } else { + context.setBlendMode(.copy) + context.setFillColor(UIColor.black.cgColor) + } + if badgeSize.width != badgeSize.height { + let path = UIBezierPath(roundedRect: CGRect(origin: offset, size: badgeSize), cornerRadius: badgeSize.height / 2.0) + context.addPath(path.cgPath) + if let _ = parameters.strokeColor { + context.drawPath(using: .fillStroke) + } else { + context.fillPath() + } + } else { + context.fillEllipse(in: CGRect(origin: offset, size: badgeSize)) + } + + if let _ = solidColor { + + } else { + blurredImage.draw(in: CGRect(origin: CGPoint(), size: badgeSize), blendMode: .sourceIn, alpha: 1.0) + context.setBlendMode(.normal) } var fontSize: CGFloat = floor(parameters.size.height * 0.48) @@ -225,28 +293,30 @@ public final class AvatarBadgeView: UIImageView { } } - let lineWidth: CGFloat = 1.5 - let lineInset: CGFloat = 2.0 - let lineRadius: CGFloat = size.width * 0.5 - lineInset - lineWidth * 0.5 - context.setLineWidth(lineWidth) - context.setStrokeColor(textColor.cgColor) - context.setLineCap(.round) - - context.addArc(center: CGPoint(x: size.width * 0.5, y: size.height * 0.5), radius: lineRadius, startAngle: CGFloat.pi * 0.5, endAngle: -CGFloat.pi * 0.5, clockwise: false) - context.strokePath() - - let sectionAngle: CGFloat = CGFloat.pi / 11.0 - - for i in 0 ..< 10 { - if i % 2 == 0 { - continue - } + if parameters.hasTimeoutIcon { + let lineWidth: CGFloat = 1.5 + let lineInset: CGFloat = 2.0 + let lineRadius: CGFloat = size.width * 0.5 - lineInset - lineWidth * 0.5 + context.setLineWidth(lineWidth) + context.setStrokeColor(textColor.cgColor) + context.setLineCap(.round) - let startAngle = CGFloat.pi * 0.5 - CGFloat(i) * sectionAngle - sectionAngle * 0.15 - let endAngle = startAngle - sectionAngle * 0.75 - - context.addArc(center: CGPoint(x: size.width * 0.5, y: size.height * 0.5), radius: lineRadius, startAngle: startAngle, endAngle: endAngle, clockwise: true) + context.addArc(center: CGPoint(x: size.width * 0.5, y: size.height * 0.5), radius: lineRadius, startAngle: CGFloat.pi * 0.5, endAngle: -CGFloat.pi * 0.5, clockwise: false) context.strokePath() + + let sectionAngle: CGFloat = CGFloat.pi / 11.0 + + for i in 0 ..< 10 { + if i % 2 == 0 { + continue + } + + let startAngle = CGFloat.pi * 0.5 - CGFloat(i) * sectionAngle - sectionAngle * 0.15 + let endAngle = startAngle - sectionAngle * 0.75 + + context.addArc(center: CGPoint(x: size.width * 0.5, y: size.height * 0.5), radius: lineRadius, startAngle: startAngle, endAngle: endAngle, clockwise: true) + context.strokePath() + } } /*if isLightImage { diff --git a/submodules/Display/Source/UIKitUtils.swift b/submodules/Display/Source/UIKitUtils.swift index 6d68a70581..22697ca1dd 100644 --- a/submodules/Display/Source/UIKitUtils.swift +++ b/submodules/Display/Source/UIKitUtils.swift @@ -172,6 +172,12 @@ public extension UIColor { } } + func contrastRatio(with other: UIColor) -> CGFloat { + let l1 = self.lightness + let l2 = other.lightness + return (max(l1, l2) + 0.05) / (min(l1, l2) + 0.05) + } + var brightness: CGFloat { var hue: CGFloat = 0.0 var saturation: CGFloat = 0.0 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/Sources/ChatMessageJoinedChannelBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/Sources/ChatMessageJoinedChannelBubbleContentNode.swift index 7fb2acd2c1..0b146cc0a1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/Sources/ChatMessageJoinedChannelBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/Sources/ChatMessageJoinedChannelBubbleContentNode.swift @@ -22,6 +22,7 @@ import ChatMessageItemCommon import RoundedRectWithTailPath import AvatarNode import MultilineTextComponent +import BundleIconComponent import ChatMessageBackground private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: EngineMessage, accountPeerId: EnginePeer.Id) -> NSAttributedString? { @@ -545,6 +546,8 @@ private final class ChannelItemComponent: Component { private let title = ComponentView() private let subtitle = ComponentView() private let avatarNode: AvatarNode + private let avatarBadge: AvatarBadgeView + private let subtitleIcon = ComponentView() private var component: ChannelItemComponent? private weak var state: EmptyComponentState? @@ -553,12 +556,17 @@ private final class ChannelItemComponent: Component { self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0)) self.avatarNode.isUserInteractionEnabled = false + self.avatarBadge = AvatarBadgeView(frame: CGRect()) + self.containerButton = HighlightTrackingButton() super.init(frame: frame) self.addSubview(self.containerButton) self.addSubnode(self.avatarNode) + self.avatarNode.view.addSubview(self.avatarBadge) + + self.avatarNode.badgeView = self.avatarBadge self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) } @@ -581,25 +589,38 @@ private final class ChannelItemComponent: Component { let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: component.peer.compactDisplayTitle, font: Font.regular(11.0), textColor: component.theme.chat.message.incoming.primaryTextColor)) + text: .plain(NSAttributedString(string: component.peer.compactDisplayTitle, font: Font.regular(11.0), textColor: component.theme.chat.message.incoming.primaryTextColor)), + horizontalAlignment: .center, + maximumNumberOfLines: 2 )), environment: {}, - containerSize: CGSize(width: itemSize.width - 20.0, height: 100.0) + containerSize: CGSize(width: itemSize.width - 16.0, height: 100.0) ) let subtitleSize = self.subtitle.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: component.subtitle, font: Font.regular(10.0), textColor: component.theme.chat.message.incoming.secondaryTextColor)) + text: .plain(NSAttributedString(string: component.subtitle, font: Font.with(size: 9.0, design: .round, weight: .bold), textColor: .white)) )), environment: {}, containerSize: CGSize(width: itemSize.width - 6.0, height: 100.0) ) + let subtitleIconSize = self.subtitleIcon.update( + transition: .immediate, + component: AnyComponent(BundleIconComponent(name: "Chat/Message/Subscriber", tintColor: .white)), + environment: {}, + containerSize: CGSize(width: itemSize.width - 6.0, height: 100.0) + ) + let avatarSize = CGSize(width: 60.0, height: 60.0) let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((itemSize.width - avatarSize.width) / 2.0), y: 0.0), size: avatarSize) let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((itemSize.width - titleSize.width) / 2.0), y: avatarFrame.maxY + 4.0), size: titleSize) - let subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((itemSize.width - subtitleSize.width) / 2.0), y: titleFrame.maxY + 1.0), size: subtitleSize) + + let subtitleSpacing: CGFloat = 1.0 + UIScreenPixel + let subtitleTotalWidth = subtitleIconSize.width + subtitleSize.width + subtitleSpacing + let subtitleIconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((itemSize.width - subtitleTotalWidth) / 2.0) + 1.0 - UIScreenPixel, y: avatarFrame.maxY - subtitleSize.height + 1.0 - UIScreenPixel), size: subtitleIconSize) + let subtitleFrame = CGRect(origin: CGPoint(x: subtitleIconFrame.maxX + subtitleSpacing, y: avatarFrame.maxY - subtitleSize.height - UIScreenPixel), size: subtitleSize) self.avatarNode.frame = avatarFrame self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: component.peer) @@ -607,18 +628,32 @@ private final class ChannelItemComponent: Component { if let titleView = self.title.view { if titleView.superview == nil { titleView.isUserInteractionEnabled = false - self.containerButton.addSubview(titleView) + self.addSubview(titleView) } titleView.frame = titleFrame } if let subtitleView = self.subtitle.view { if subtitleView.superview == nil { subtitleView.isUserInteractionEnabled = false - self.containerButton.addSubview(subtitleView) + self.addSubview(subtitleView) } subtitleView.frame = subtitleFrame } + if let subtitleIconView = self.subtitleIcon.view { + if subtitleIconView.superview == nil { + subtitleIconView.isUserInteractionEnabled = false + self.addSubview(subtitleIconView) + } + subtitleIconView.frame = subtitleIconFrame + } + let strokeWidth: CGFloat = 1.0 + UIScreenPixel + let avatarBadgeSize = CGSize(width: subtitleSize.width + 4.0 + 4.0 + 6.0, height: 15.0) + self.avatarBadge.update(size: avatarBadgeSize, text: "", hasTimeoutIcon: false, useSolidColor: true, strokeColor: component.theme.chat.message.incoming.bubble.withoutWallpaper.fill.first!) + + let avatarBadgeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((avatarFrame.width - avatarBadgeSize.width) / 2.0), y: avatarFrame.height - avatarBadgeSize.height + 2.0), size: avatarBadgeSize).insetBy(dx: -strokeWidth, dy: -strokeWidth) + self.avatarBadge.frame = avatarBadgeFrame + self.containerButton.frame = CGRect(origin: .zero, size: itemSize) return itemSize From e8e2d419c54b101b207b1fd2b0b5c82e2744ac5f Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 17 Nov 2023 03:13:55 +0400 Subject: [PATCH 2/5] Add support for app ads --- .../Telegram-iOS/en.lproj/Localizable.strings | 5 ++ submodules/TelegramApi/Sources/Api0.swift | 2 +- submodules/TelegramApi/Sources/Api20.swift | 48 +++++++++++-------- .../Sources/ApiUtils/AdMessageAttribute.swift | 5 +- .../TelegramEngine/Messages/AdMessages.swift | 38 ++++++++++++--- .../Messages/AttachMenuBots.swift | 17 +++++-- .../ChatMessageWebpageBubbleContentNode.swift | 19 ++++++-- .../TelegramUI/Sources/ChatController.swift | 7 +++ 8 files changed, 106 insertions(+), 35 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 734130ef43..1479972919 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -10497,3 +10497,8 @@ Sorry for the inconvenience."; "Stats.StoryReactionsByEmotionTitle" = "STORIES REACTIONS BY EMOTION"; "Stats.MessageReactionsTitle" = "REACTIONS"; + +"Conversation.LaunchApp" = "LAUNCH APP"; + +"Message.AdSponsoredLabel" = "Sponsored"; +"Message.AdRecommendedLabel" = "Recommended"; diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index ec4ac332d5..58a77f493a 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -797,7 +797,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-651419003] = { return Api.SendMessageAction.parse_speakingInGroupCallAction($0) } dict[-1239335713] = { return Api.ShippingOption.parse_shippingOption($0) } dict[-2010155333] = { return Api.SimpleWebViewResult.parse_simpleWebViewResultUrl($0) } - dict[-626000021] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) } + dict[-313293833] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) } dict[1035529315] = { return Api.SponsoredWebPage.parse_sponsoredWebPage($0) } dict[-884757282] = { return Api.StatsAbsValueAndPrev.parse_statsAbsValueAndPrev($0) } dict[-1237848657] = { return Api.StatsDateRangeDays.parse_statsDateRangeDays($0) } diff --git a/submodules/TelegramApi/Sources/Api20.swift b/submodules/TelegramApi/Sources/Api20.swift index 0e768dc594..d81da01ecb 100644 --- a/submodules/TelegramApi/Sources/Api20.swift +++ b/submodules/TelegramApi/Sources/Api20.swift @@ -434,13 +434,13 @@ public extension Api { } public extension Api { indirect enum SponsoredMessage: TypeConstructorDescription { - case sponsoredMessage(flags: Int32, randomId: Buffer, fromId: Api.Peer?, chatInvite: Api.ChatInvite?, chatInviteHash: String?, channelPost: Int32?, startParam: String?, webpage: Api.SponsoredWebPage?, message: String, entities: [Api.MessageEntity]?, sponsorInfo: String?, additionalInfo: String?) + case sponsoredMessage(flags: Int32, randomId: Buffer, fromId: Api.Peer?, chatInvite: Api.ChatInvite?, chatInviteHash: String?, channelPost: Int32?, startParam: String?, webpage: Api.SponsoredWebPage?, app: Api.BotApp?, message: String, entities: [Api.MessageEntity]?, buttonText: String?, sponsorInfo: String?, additionalInfo: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .sponsoredMessage(let flags, let randomId, let fromId, let chatInvite, let chatInviteHash, let channelPost, let startParam, let webpage, let message, let entities, let sponsorInfo, let additionalInfo): + case .sponsoredMessage(let flags, let randomId, let fromId, let chatInvite, let chatInviteHash, let channelPost, let startParam, let webpage, let app, let message, let entities, let buttonText, let sponsorInfo, let additionalInfo): if boxed { - buffer.appendInt32(-626000021) + buffer.appendInt32(-313293833) } serializeInt32(flags, buffer: buffer, boxed: false) serializeBytes(randomId, buffer: buffer, boxed: false) @@ -450,12 +450,14 @@ public extension Api { if Int(flags) & Int(1 << 2) != 0 {serializeInt32(channelPost!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeString(startParam!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 9) != 0 {webpage!.serialize(buffer, true)} + if Int(flags) & Int(1 << 10) != 0 {app!.serialize(buffer, true)} serializeString(message, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) buffer.appendInt32(Int32(entities!.count)) for item in entities! { item.serialize(buffer, true) }} + if Int(flags) & Int(1 << 11) != 0 {serializeString(buttonText!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 7) != 0 {serializeString(sponsorInfo!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 8) != 0 {serializeString(additionalInfo!, buffer: buffer, boxed: false)} break @@ -464,8 +466,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .sponsoredMessage(let flags, let randomId, let fromId, let chatInvite, let chatInviteHash, let channelPost, let startParam, let webpage, let message, let entities, let sponsorInfo, let additionalInfo): - return ("sponsoredMessage", [("flags", flags as Any), ("randomId", randomId as Any), ("fromId", fromId as Any), ("chatInvite", chatInvite as Any), ("chatInviteHash", chatInviteHash as Any), ("channelPost", channelPost as Any), ("startParam", startParam as Any), ("webpage", webpage as Any), ("message", message as Any), ("entities", entities as Any), ("sponsorInfo", sponsorInfo as Any), ("additionalInfo", additionalInfo as Any)]) + case .sponsoredMessage(let flags, let randomId, let fromId, let chatInvite, let chatInviteHash, let channelPost, let startParam, let webpage, let app, let message, let entities, let buttonText, let sponsorInfo, let additionalInfo): + return ("sponsoredMessage", [("flags", flags as Any), ("randomId", randomId as Any), ("fromId", fromId as Any), ("chatInvite", chatInvite as Any), ("chatInviteHash", chatInviteHash as Any), ("channelPost", channelPost as Any), ("startParam", startParam as Any), ("webpage", webpage as Any), ("app", app as Any), ("message", message as Any), ("entities", entities as Any), ("buttonText", buttonText as Any), ("sponsorInfo", sponsorInfo as Any), ("additionalInfo", additionalInfo as Any)]) } } @@ -492,16 +494,22 @@ public extension Api { if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { _8 = Api.parse(reader, signature: signature) as? Api.SponsoredWebPage } } - var _9: String? - _9 = parseString(reader) - var _10: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { - _10 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + var _9: Api.BotApp? + if Int(_1!) & Int(1 << 10) != 0 {if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.BotApp + } } + var _10: String? + _10 = parseString(reader) + var _11: [Api.MessageEntity]? + if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { + _11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } } - var _11: String? - if Int(_1!) & Int(1 << 7) != 0 {_11 = parseString(reader) } var _12: String? - if Int(_1!) & Int(1 << 8) != 0 {_12 = parseString(reader) } + if Int(_1!) & Int(1 << 11) != 0 {_12 = parseString(reader) } + var _13: String? + if Int(_1!) & Int(1 << 7) != 0 {_13 = parseString(reader) } + var _14: String? + if Int(_1!) & Int(1 << 8) != 0 {_14 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil @@ -510,12 +518,14 @@ public extension Api { let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 9) == 0) || _8 != nil - let _c9 = _9 != nil - let _c10 = (Int(_1!) & Int(1 << 1) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 7) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 8) == 0) || _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.SponsoredMessage.sponsoredMessage(flags: _1!, randomId: _2!, fromId: _3, chatInvite: _4, chatInviteHash: _5, channelPost: _6, startParam: _7, webpage: _8, message: _9!, entities: _10, sponsorInfo: _11, additionalInfo: _12) + let _c9 = (Int(_1!) & Int(1 << 10) == 0) || _9 != nil + let _c10 = _10 != nil + let _c11 = (Int(_1!) & Int(1 << 1) == 0) || _11 != nil + let _c12 = (Int(_1!) & Int(1 << 11) == 0) || _12 != nil + let _c13 = (Int(_1!) & Int(1 << 7) == 0) || _13 != nil + let _c14 = (Int(_1!) & Int(1 << 8) == 0) || _14 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 { + return Api.SponsoredMessage.sponsoredMessage(flags: _1!, randomId: _2!, fromId: _3, chatInvite: _4, chatInviteHash: _5, channelPost: _6, startParam: _7, webpage: _8, app: _9, message: _10!, entities: _11, buttonText: _12, sponsorInfo: _13, additionalInfo: _14) } else { return nil diff --git a/submodules/TelegramCore/Sources/ApiUtils/AdMessageAttribute.swift b/submodules/TelegramCore/Sources/ApiUtils/AdMessageAttribute.swift index 09498984de..290a0d0238 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/AdMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/AdMessageAttribute.swift @@ -11,20 +11,23 @@ public final class AdMessageAttribute: MessageAttribute { case peer(id: EnginePeer.Id, message: EngineMessage.Id?, startParam: String?) case join(title: String, joinHash: String) case webPage(title: String, url: String) + case botApp(peerId: EnginePeer.Id, app: BotApp, startParam: String?) } public let opaqueId: Data public let messageType: MessageType public let displayAvatar: Bool public let target: MessageTarget + public let buttonText: String? public let sponsorInfo: String? public let additionalInfo: String? - public init(opaqueId: Data, messageType: MessageType, displayAvatar: Bool, target: MessageTarget, sponsorInfo: String?, additionalInfo: String?) { + public init(opaqueId: Data, messageType: MessageType, displayAvatar: Bool, target: MessageTarget, buttonText: String?, sponsorInfo: String?, additionalInfo: String?) { self.opaqueId = opaqueId self.messageType = messageType self.displayAvatar = displayAvatar self.target = target + self.buttonText = buttonText self.sponsorInfo = sponsorInfo self.additionalInfo = additionalInfo } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift index acef2bc671..f32426c7b3 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift @@ -15,6 +15,7 @@ private class AdMessagesHistoryContextImpl { case target case messageId case startParam + case buttonText case sponsorInfo case additionalInfo } @@ -33,6 +34,7 @@ private class AdMessagesHistoryContextImpl { case peer case invite case webPage + case botApp } struct Invite: Equatable, Codable { @@ -78,11 +80,14 @@ private class AdMessagesHistoryContextImpl { case peer(PeerId) case invite(Invite) case webPage(WebPage) + case botApp(PeerId, BotApp) init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - if let peer = try container.decodeIfPresent(Int64.self, forKey: .peer) { + if let botApp = try container.decodeIfPresent(BotApp.self, forKey: .botApp), let peer = try container.decodeIfPresent(Int64.self, forKey: .peer) { + self = .botApp(PeerId(peer), botApp) + } else if let peer = try container.decodeIfPresent(Int64.self, forKey: .peer) { self = .peer(PeerId(peer)) } else if let invite = try container.decodeIfPresent(Invite.self, forKey: .invite) { self = .invite(invite) @@ -103,6 +108,9 @@ private class AdMessagesHistoryContextImpl { try container.encode(invite, forKey: .invite) case let .webPage(webPage): try container.encode(webPage, forKey: .webPage) + case let .botApp(peerId, botApp): + try container.encode(peerId.toInt64(), forKey: .peer) + try container.encode(botApp, forKey: .botApp) } } } @@ -116,6 +124,7 @@ private class AdMessagesHistoryContextImpl { public let target: Target public let messageId: MessageId? public let startParam: String? + public let buttonText: String? public let sponsorInfo: String? public let additionalInfo: String? @@ -129,6 +138,7 @@ private class AdMessagesHistoryContextImpl { target: Target, messageId: MessageId?, startParam: String?, + buttonText: String?, sponsorInfo: String?, additionalInfo: String? ) { @@ -141,6 +151,7 @@ private class AdMessagesHistoryContextImpl { self.target = target self.messageId = messageId self.startParam = startParam + self.buttonText = buttonText self.sponsorInfo = sponsorInfo self.additionalInfo = additionalInfo } @@ -169,6 +180,7 @@ private class AdMessagesHistoryContextImpl { self.target = try container.decode(Target.self, forKey: .target) self.messageId = try container.decodeIfPresent(MessageId.self, forKey: .messageId) self.startParam = try container.decodeIfPresent(String.self, forKey: .startParam) + self.buttonText = try container.decodeIfPresent(String.self, forKey: .buttonText) self.sponsorInfo = try container.decodeIfPresent(String.self, forKey: .sponsorInfo) self.additionalInfo = try container.decodeIfPresent(String.self, forKey: .additionalInfo) @@ -193,6 +205,7 @@ private class AdMessagesHistoryContextImpl { try container.encode(self.target, forKey: .target) try container.encodeIfPresent(self.messageId, forKey: .messageId) try container.encodeIfPresent(self.startParam, forKey: .startParam) + try container.encodeIfPresent(self.buttonText, forKey: .buttonText) try container.encodeIfPresent(self.sponsorInfo, forKey: .sponsorInfo) try container.encodeIfPresent(self.additionalInfo, forKey: .additionalInfo) @@ -228,6 +241,9 @@ private class AdMessagesHistoryContextImpl { if lhs.startParam != rhs.startParam { return false } + if lhs.buttonText != rhs.buttonText { + return false + } if lhs.sponsorInfo != rhs.sponsorInfo { return false } @@ -248,6 +264,8 @@ private class AdMessagesHistoryContextImpl { target = .join(title: invite.title, joinHash: invite.joinHash) case let .webPage(webPage): target = .webPage(title: webPage.title, url: webPage.url) + case let .botApp(peerId, botApp): + target = .botApp(peerId: peerId, app: botApp, startParam: self.startParam) } let mappedMessageType: AdMessageAttribute.MessageType switch self.messageType { @@ -256,7 +274,7 @@ private class AdMessagesHistoryContextImpl { case .recommended: mappedMessageType = .recommended } - attributes.append(AdMessageAttribute(opaqueId: self.opaqueId, messageType: mappedMessageType, displayAvatar: self.displayAvatar, target: target, sponsorInfo: self.sponsorInfo, additionalInfo: self.additionalInfo)) + attributes.append(AdMessageAttribute(opaqueId: self.opaqueId, messageType: mappedMessageType, displayAvatar: self.displayAvatar, target: target, buttonText: self.buttonText, sponsorInfo: self.sponsorInfo, additionalInfo: self.additionalInfo)) if !self.textEntities.isEmpty { let attribute = TextEntitiesMessageAttribute(entities: self.textEntities) attributes.append(attribute) @@ -270,7 +288,7 @@ private class AdMessagesHistoryContextImpl { let author: Peer switch self.target { - case let .peer(peerId): + case let .peer(peerId), let .botApp(peerId, _): if let peer = transaction.getPeer(peerId) { author = peer } else { @@ -523,7 +541,7 @@ private class AdMessagesHistoryContextImpl { for message in messages { switch message { - case let .sponsoredMessage(flags, randomId, fromId, chatInvite, chatInviteHash, channelPost, startParam, webPage, message, entities, sponsorInfo, additionalInfo): + case let .sponsoredMessage(flags, randomId, fromId, chatInvite, chatInviteHash, channelPost, startParam, webPage, botApp, message, entities, buttonText, sponsorInfo, additionalInfo): var parsedEntities: [MessageTextEntity] = [] if let entities = entities { parsedEntities = messageTextEntitiesFromApiEntities(entities) @@ -534,7 +552,11 @@ private class AdMessagesHistoryContextImpl { var target: CachedMessage.Target? if let fromId = fromId { - target = .peer(fromId.peerId) + if let botApp = botApp, let app = BotApp(apiBotApp: botApp) { + target = .botApp(fromId.peerId, app) + } else { + target = .peer(fromId.peerId) + } } else if let webPage = webPage { switch webPage { case let .sponsoredWebPage(_, url, siteName, photo): @@ -574,7 +596,10 @@ private class AdMessagesHistoryContextImpl { )) } } - } + } +// else if let botApp = app.flatMap({ BotApp(apiBotApp: $0) }) { +// target = .botApp(botApp) +// } var messageId: MessageId? if let fromId = fromId, let channelPost = channelPost { @@ -592,6 +617,7 @@ private class AdMessagesHistoryContextImpl { target: target, messageId: messageId, startParam: startParam, + buttonText: buttonText, sponsorInfo: sponsorInfo, additionalInfo: additionalInfo )) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift index da7da6772d..a97fa9d3a6 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift @@ -767,12 +767,23 @@ func _internal_getBotApp(account: Account, reference: BotAppReference) -> Signal appFlags.insert(.hasSettings) } return .single(BotApp(id: id, accessHash: accessHash, shortName: shortName, title: title, description: description, photo: telegramMediaImageFromApiPhoto(photo), document: document.flatMap(telegramMediaFileFromApiDocument), hash: hash, flags: appFlags)) - case .botAppNotModified: - return .complete() - } + case .botAppNotModified: + return .complete() + } } } } |> castError(GetBotAppError.self) |> switchToLatest } + +extension BotApp { + convenience init?(apiBotApp: Api.BotApp) { + switch apiBotApp { + case let .botApp(_, id, accessHash, shortName, title, description, photo, document, hash): + self.init(id: id, accessHash: accessHash, shortName: shortName, title: title, description: description, photo: telegramMediaImageFromApiPhoto(photo), document: document.flatMap(telegramMediaFileFromApiDocument), hash: hash, flags: []) + case .botAppNotModified: + return nil + } + } +} diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift index e0d9da8e53..0ef76df20d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -479,9 +479,12 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent } } } else if let adAttribute = item.message.adAttribute { - //TODO:localize - //Recommended? - title = "Sponsored" + switch adAttribute.messageType { + case .sponsored: + title = item.presentationData.strings.Message_AdSponsoredLabel + case .recommended: + title = item.presentationData.strings.Message_AdRecommendedLabel + } subtitle = item.message.author.flatMap { NSAttributedString(string: EnginePeer($0).compactDisplayTitle, font: titleFont) } @@ -500,8 +503,14 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent } } - if let author = item.message.author as? TelegramUser, author.botInfo != nil { - actionTitle = item.presentationData.strings.Conversation_ViewBot + if let buttonText = adAttribute.buttonText { + actionTitle = buttonText.uppercased() + } else if let author = item.message.author as? TelegramUser, author.botInfo != nil { + if case .botApp = adAttribute.target { + actionTitle = item.presentationData.strings.Conversation_LaunchApp + } else { + actionTitle = item.presentationData.strings.Conversation_ViewBot + } } else if let author = item.message.author as? TelegramChannel, case .group = author.info { if case let .peer(_, messageId, _) = adAttribute.target, messageId != nil { actionTitle = item.presentationData.strings.Conversation_ViewPost diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 35c1572211..4c8baf9ea4 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4102,6 +4102,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.controllerInteraction?.openJoinLink(joinHash) case let .webPage(_, url): self.controllerInteraction?.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: false, external: false)) + case let .botApp(peerId, botApp, startParam): + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in + if let self, let peer { + self.presentBotApp(botApp: botApp, botPeer: peer, payload: startParam) + } + }) } }, openRequestedPeerSelection: { [weak self] messageId, peerType, buttonId in guard let self else { From b10556fdd095d9b7804b44859bf4e73e5e647568 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 17 Nov 2023 03:15:03 +0400 Subject: [PATCH 3/5] Various fixes --- submodules/AvatarNode/Sources/AvatarBadgeView.swift | 11 ++++++++++- .../Sources/State/AccountStateManagementUtils.swift | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/submodules/AvatarNode/Sources/AvatarBadgeView.swift b/submodules/AvatarNode/Sources/AvatarBadgeView.swift index 7bc5ac487f..d226f743be 100644 --- a/submodules/AvatarNode/Sources/AvatarBadgeView.swift +++ b/submodules/AvatarNode/Sources/AvatarBadgeView.swift @@ -244,7 +244,16 @@ public final class AvatarBadgeView: UIImageView { var brightness: CGFloat = 0 var alpha: CGFloat = 0 backgroundColor.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) - return UIColor(hue: hue, saturation: saturation, brightness: brightness * 0.9, alpha: alpha) + + if brightness > 0.5 { + brightness = max(brightness - 0.2, 0) + saturation = min(saturation + 0.2, 1) + } else { + brightness = min(brightness + 0.3, 1) + saturation = max(saturation - 0.2, 0) + } + + return UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha) } else { return backgroundColor } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 16c2564b91..7334d848d9 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -3344,7 +3344,7 @@ func replayFinalState( var langPackDifferences: [String: [Api.LangPackDifference]] = [:] var pollLangPacks = Set() var updatedThemes: [Int64: TelegramTheme] = [:] - var updatedWallpapers: [PeerId: TelegramWallpaper] = [:] + var updatedWallpapers: [PeerId: TelegramWallpaper?] = [:] var delayNotificatonsUntil: Int32? var peerActivityTimestamps: [PeerId: Int32] = [:] var syncChatListFilters = false @@ -4382,7 +4382,7 @@ func replayFinalState( case let .UpdateTheme(theme): updatedThemes[theme.id] = theme case let .UpdateWallpaper(peerId, wallpaper): - updatedWallpapers[peerId] = wallpaper + updatedWallpapers.updateValue(wallpaper, forKey: peerId) case .SyncChatListFilters: syncChatListFilters = true case let .UpdateChatListFilterOrder(order): From 1fced7409873cb8151630d4985cc8940d8196f99 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 17 Nov 2023 03:47:38 +0400 Subject: [PATCH 4/5] Fix decoding --- .../TelegramEngine/Messages/AttachMenuBots.swift | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift index a97fa9d3a6..2649a4b3aa 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift @@ -666,19 +666,8 @@ public final class BotApp: Equatable, Codable { self.shortName = try container.decode(String.self, forKey: .shortName) self.title = try container.decode(String.self, forKey: .title) self.description = try container.decode(String.self, forKey: .description) - - if let data = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: .photo) { - self.photo = TelegramMediaImage(decoder: PostboxDecoder(buffer: MemoryBuffer(data: data.data))) - } else { - self.photo = nil - } - - if let data = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: .document) { - self.document = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: data.data))) - } else { - self.document = nil - } - + self.photo = try container.decodeIfPresent(TelegramMediaImage.self, forKey: .photo) + self.document = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .document) self.hash = try container.decode(Int64.self, forKey: .hash) self.flags = Flags(rawValue: try container.decode(Int32.self, forKey: .flags)) } From 13baadc3e78d317e6201a637d7830136399e682e Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 17 Nov 2023 07:22:25 +0400 Subject: [PATCH 5/5] Statistics improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 6 + .../Sources/ChatListSearchListPaneNode.swift | 4 +- .../Sources/Node/ChatListNode.swift | 16 +- .../ItemListUI/Sources/ItemListItem.swift | 9 +- submodules/StatisticsUI/BUILD | 2 + .../Sources/ChannelStatsController.swift | 135 ++++++-- .../Sources/MessageStatsController.swift | 121 ++++++-- .../Sources/MessageStatsOverviewItem.swift | 4 +- .../Sources/StatsMessageItem.swift | 254 ++++++++++++--- .../StatisticsUI/Sources/StoryIconNode.swift | 106 +++++++ .../Sources/Statistics/PeerStatistics.swift | 10 +- .../Sources/PresentationTheme.swift | 4 + .../Resources/PresentationResourceKey.swift | 3 + .../PresentationResourcesItemList.swift | 12 + .../CameraScreen/Sources/CameraScreen.swift | 2 +- .../BUILD | 1 + .../Sources/PeerInfoScreen.swift | 13 +- .../Images.xcassets/Chart/Contents.json | 6 +- .../Chart/Forwards.imageset/Contents.json | 12 + .../Chart/Forwards.imageset/arrowshape_18.pdf | 148 +++++++++ .../Chart/Reactions.imageset/Contents.json | 12 + .../Chart/Reactions.imageset/heart_18.pdf | 95 ++++++ .../Info/SettingsIcon.imageset/Contents.json | 12 + .../SettingsIcon.imageset/settings_30.pdf | 290 ++++++++++++++++++ .../Message/Subscriber.imageset/Contents.json | 12 + .../Message/Subscriber.imageset/person_6.pdf | 78 +++++ .../Contents.json | 15 + .../lockedaudiototext.pdf | 202 ++++++++++++ .../TelegramUI/Sources/ChatController.swift | 2 +- 29 files changed, 1467 insertions(+), 119 deletions(-) create mode 100644 submodules/StatisticsUI/Sources/StoryIconNode.swift create mode 100644 submodules/TelegramUI/Images.xcassets/Chart/Forwards.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chart/Forwards.imageset/arrowshape_18.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chart/Reactions.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chart/Reactions.imageset/heart_18.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Info/SettingsIcon.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Info/SettingsIcon.imageset/settings_30.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/Subscriber.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/Subscriber.imageset/person_6.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/TranscriptionLocked.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/TranscriptionLocked.imageset/lockedaudiototext.pdf 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