From 256659d2e6e5af5fa13b9616ec50396467c8d6a8 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 25 May 2024 19:36:10 +0400 Subject: [PATCH 1/5] Stars fixes --- .../Sources/GiftAvatarComponent.swift | 95 +++++++++++++++++- .../Stars/StarsTransactionsScreen/BUILD | 1 + .../Sources/StarsTransactionScreen.swift | 29 +++++- .../StarsTransactionsListPanelComponent.swift | 67 +++++++++--- .../Sources/StarsTransactionsScreen.swift | 6 +- .../Sources/StarsTransferScreen.swift | 2 +- .../Stars/StarLarge.imageset/Contents.json | 2 +- .../{Star20.pdf => Star20 (3).pdf} | Bin 17487 -> 19311 bytes .../Stars/StarMedium.imageset/Contents.json | 2 +- .../{star_18.pdf => star_18 (3).pdf} | Bin 17373 -> 19197 bytes .../Stars/StarSmall.imageset/Contents.json | 2 +- .../{star_16 (2).pdf => star_16 (3).pdf} | Bin 15774 -> 19140 bytes 12 files changed, 183 insertions(+), 23 deletions(-) rename submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/{Star20.pdf => Star20 (3).pdf} (87%) rename submodules/TelegramUI/Images.xcassets/Premium/Stars/StarMedium.imageset/{star_18.pdf => star_18 (3).pdf} (87%) rename submodules/TelegramUI/Images.xcassets/Premium/Stars/StarSmall.imageset/{star_16 (2).pdf => star_16 (3).pdf} (74%) diff --git a/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/GiftAvatarComponent.swift b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/GiftAvatarComponent.swift index dc127f1f20..d53649dbcc 100644 --- a/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/GiftAvatarComponent.swift +++ b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/GiftAvatarComponent.swift @@ -22,6 +22,7 @@ public final class GiftAvatarComponent: Component { let theme: PresentationTheme let peers: [EnginePeer] let photo: TelegramMediaWebFile? + let starsPeer: StarsContext.State.Transaction.Peer? let isVisible: Bool let hasIdleAnimations: Bool let hasScaleAnimation: Bool @@ -29,11 +30,24 @@ public final class GiftAvatarComponent: Component { let color: UIColor? let offset: CGFloat? - public init(context: AccountContext, theme: PresentationTheme, peers: [EnginePeer], photo: TelegramMediaWebFile? = nil, isVisible: Bool, hasIdleAnimations: Bool, hasScaleAnimation: Bool = true, avatarSize: CGFloat = 100.0, color: UIColor? = nil, offset: CGFloat? = nil) { + public init( + context: AccountContext, + theme: PresentationTheme, + peers: [EnginePeer], + photo: TelegramMediaWebFile? = nil, + starsPeer: StarsContext.State.Transaction.Peer? = nil, + isVisible: Bool, + hasIdleAnimations: Bool, + hasScaleAnimation: Bool = true, + avatarSize: CGFloat = 100.0, + color: UIColor? = nil, + offset: CGFloat? = nil + ) { self.context = context self.theme = theme self.peers = peers self.photo = photo + self.starsPeer = starsPeer self.isVisible = isVisible self.hasIdleAnimations = hasIdleAnimations self.hasScaleAnimation = hasScaleAnimation @@ -76,6 +90,9 @@ public final class GiftAvatarComponent: Component { private var mergedAvatarsNode: MergedAvatarsNode? private var imageNode: TransformImageNode? + private var iconBackgroundView: UIImageView? + private var iconView: UIImageView? + private let badgeBackground = ComponentView() private let badge = ComponentView() @@ -321,6 +338,82 @@ public final class GiftAvatarComponent: Component { imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: imageSize.width / 2.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))() self.avatarNode.isHidden = true + } else if let starsPeer = component.starsPeer { + let iconBackgroundView: UIImageView + let iconView: UIImageView + if let currentBackground = self.iconBackgroundView, let current = self.iconView { + iconBackgroundView = currentBackground + iconView = current + } else { + iconBackgroundView = UIImageView() + iconView = UIImageView() + + self.addSubview(iconBackgroundView) + self.addSubview(iconView) + + self.iconBackgroundView = iconBackgroundView + self.iconView = iconView + + let size = CGSize(width: component.avatarSize, height: component.avatarSize) + var iconInset: CGFloat = 9.0 + var iconOffset: CGFloat = 0.0 + + switch starsPeer { + case .appStore: + iconBackgroundView.image = generateGradientFilledCircleImage( + diameter: size.width, + colors: [ + UIColor(rgb: 0x2a9ef1).cgColor, + UIColor(rgb: 0x72d5fd).cgColor + ], + direction: .mirroredDiagonal + ) + iconView.image = UIImage(bundleImageName: "Premium/Stars/Apple") + case .playMarket: + iconBackgroundView.image = generateGradientFilledCircleImage( + diameter: size.width, + colors: [ + UIColor(rgb: 0x54cb68).cgColor, + UIColor(rgb: 0xa0de7e).cgColor + ], + direction: .mirroredDiagonal + ) + iconView.image = UIImage(bundleImageName: "Premium/Stars/Google") + case .fragment: + iconBackgroundView.image = generateFilledCircleImage(diameter: size.width, color: UIColor(rgb: 0x1b1f24)) + iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment") + iconOffset = 5.0 + case .premiumBot: + iconInset = 15.0 + iconBackgroundView.image = generateGradientFilledCircleImage( + diameter: size.width, + colors: [ + UIColor(rgb: 0x6b93ff).cgColor, + UIColor(rgb: 0x6b93ff).cgColor, + UIColor(rgb: 0x8d77ff).cgColor, + UIColor(rgb: 0xb56eec).cgColor, + UIColor(rgb: 0xb56eec).cgColor + ], + direction: .mirroredDiagonal + ) + iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white) + case .peer, .unsupported: + iconInset = 15.0 + iconBackgroundView.image = generateGradientFilledCircleImage( + diameter: size.width, + colors: [ + UIColor(rgb: 0xb1b1b1).cgColor, + UIColor(rgb: 0xcdcdcd).cgColor + ], + direction: .mirroredDiagonal + ) + iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white) + } + + let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - size.width) / 2.0), y: 113.0 - size.height / 2.0), size: size) + iconBackgroundView.frame = imageFrame + iconView.frame = imageFrame.insetBy(dx: iconInset, dy: iconInset).offsetBy(dx: 0.0, dy: iconOffset) + } } else if component.peers.count > 1 { let avatarSize = CGSize(width: 60.0, height: 60.0) diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/BUILD index 3a16bf04b3..a8c55ae20a 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/BUILD +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/BUILD @@ -38,6 +38,7 @@ swift_library( "//submodules/Components/SolidRoundedButtonComponent", "//submodules/TelegramUI/Components/AnimatedTextComponent", "//submodules/AvatarNode", + "//submodules/PhotoResources", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift index 249f5fbca8..ed1b5bb082 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift @@ -170,6 +170,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { let date: Int32 let via: String? let toPeer: EnginePeer? + let transactionPeer: StarsContext.State.Transaction.Peer? let photo: TelegramMediaWebFile? var delayedCloseOnOpenPeer = true @@ -205,6 +206,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { } else { toPeer = nil } + transactionPeer = transaction.peer photo = transaction.photo case let .receipt(receipt): titleText = receipt.invoiceMedia.title @@ -218,6 +220,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { } else { toPeer = nil } + transactionPeer = nil photo = receipt.invoiceMedia.photo delayedCloseOnOpenPeer = false } @@ -252,6 +255,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { theme: theme, peers: toPeer.flatMap { [$0] } ?? [], photo: photo, + starsPeer: transactionPeer, isVisible: true, hasIdleAnimations: true, hasScaleAnimation: false, @@ -1090,11 +1094,30 @@ private final class TransactionCellComponent: Component { containerSize: CGSize(width: availableSize.width, height: availableSize.height) ) + func brokenLine(_ string: String) -> String { + let middleIndex = string.index(string.startIndex, offsetBy: string.count / 2) + var newString = string + newString.insert("\n", at: middleIndex) + return newString + } + + let text: String + if availableSize.width > 230.0 { + text = component.transactionId + } else { + text = brokenLine(component.transactionId) + } + let textSize = self.text.update( transition: .immediate, component: AnyComponent( MultilineTextComponent( - text: .plain(NSAttributedString(string: component.transactionId, font: Font.monospace(15.0), textColor: component.textColor, paragraphAlignment: .left)), + text: .plain(NSAttributedString( + string: text, + font: Font.monospace(15.0), + textColor: component.textColor, + paragraphAlignment: .left + )), maximumNumberOfLines: 0, lineSpacing: 0.2 ) @@ -1103,9 +1126,9 @@ private final class TransactionCellComponent: Component { containerSize: CGSize(width: availableSize.width - buttonSize.width - spacing, height: availableSize.height) ) - let size = CGSize(width: textSize.width + spacing + buttonSize.width, height: textSize.height) + let size = CGSize(width: availableSize.width, height: textSize.height) - let buttonFrame = CGRect(origin: CGPoint(x: textSize.width + spacing, y: floorToScreenPixels((size.height - buttonSize.height) / 2.0)), size: buttonSize) + let buttonFrame = CGRect(origin: CGPoint(x: availableSize.width - buttonSize.width - 2.0, y: floorToScreenPixels((size.height - buttonSize.height) / 2.0)), size: buttonSize) if let buttonView = self.button.view { if buttonView.superview == nil { self.addSubview(buttonView) diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift index d080c4e5da..98f3ec0fbf 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift @@ -14,6 +14,7 @@ import ListActionItemComponent import TelegramStringFormatting import AvatarNode import BundleIconComponent +import PhotoResources final class StarsTransactionsListPanelComponent: Component { typealias EnvironmentType = StarsTransactionsPanelEnvironment @@ -150,7 +151,7 @@ final class StarsTransactionsListPanelComponent: Component { if #available(iOS 13.0, *) { self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false } - self.scrollView.showsVerticalScrollIndicator = true + self.scrollView.showsVerticalScrollIndicator = false self.scrollView.showsHorizontalScrollIndicator = false self.scrollView.alwaysBounceHorizontal = false self.scrollView.scrollsToTop = false @@ -215,8 +216,13 @@ final class StarsTransactionsListPanelComponent: Component { let itemDate: String switch item.transaction.peer { case let .peer(peer): - itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast) - itemSubtitle = item.transaction.title + if let title = item.transaction.title { + itemTitle = title + itemSubtitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast) + } else { + itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast) + itemSubtitle = nil + } case .appStore: itemTitle = environment.strings.Stars_Intro_Transaction_AppleTopUp_Title itemSubtitle = environment.strings.Stars_Intro_Transaction_AppleTopUp_Subtitle @@ -286,7 +292,7 @@ final class StarsTransactionsListPanelComponent: Component { theme: environment.theme, title: AnyComponent(VStack(titleComponents, alignment: .left, spacing: 2.0)), contentInsets: UIEdgeInsets(top: 9.0, left: environment.containerInsets.left, bottom: 8.0, right: environment.containerInsets.right), - leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(AvatarComponent(context: component.context, theme: environment.theme, peer: item.transaction.peer))), false), + leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(AvatarComponent(context: component.context, theme: environment.theme, peer: item.transaction.peer, photo: item.transaction.photo))), false), icon: nil, accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: "label", component: AnyComponent(LabelComponent(text: itemLabel))), insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))), action: { [weak self] _ in @@ -442,11 +448,13 @@ private final class AvatarComponent: Component { let context: AccountContext let theme: PresentationTheme let peer: StarsContext.State.Transaction.Peer + let photo: TelegramMediaWebFile? - init(context: AccountContext, theme: PresentationTheme, peer: StarsContext.State.Transaction.Peer) { + init(context: AccountContext, theme: PresentationTheme, peer: StarsContext.State.Transaction.Peer, photo: TelegramMediaWebFile?) { self.context = context self.theme = theme self.peer = peer + self.photo = photo } static func ==(lhs: AvatarComponent, rhs: AvatarComponent) -> Bool { @@ -459,6 +467,9 @@ private final class AvatarComponent: Component { if lhs.peer != rhs.peer { return false } + if lhs.photo != rhs.photo { + return false + } return true } @@ -466,6 +477,9 @@ private final class AvatarComponent: Component { private let avatarNode: AvatarNode private let backgroundView = UIImageView() private let iconView = UIImageView() + private var imageNode: TransformImageNode? + + private let fetchDisposable = MetaDisposable() private var component: AvatarComponent? private weak var state: EmptyComponentState? @@ -486,6 +500,10 @@ private final class AvatarComponent: Component { fatalError("init(coder:) has not been implemented") } + deinit { + self.fetchDisposable.dispose() + } + func update(component: AvatarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { self.component = component self.state = state @@ -496,15 +514,36 @@ private final class AvatarComponent: Component { switch component.peer { case let .peer(peer): - self.avatarNode.setPeer( - context: component.context, - theme: component.theme, - peer: peer, - synchronousLoad: true - ) - self.backgroundView.isHidden = true - self.iconView.isHidden = true - self.avatarNode.isHidden = false + if let photo = component.photo { + let imageNode: TransformImageNode + if let current = self.imageNode { + imageNode = current + } else { + imageNode = TransformImageNode() + self.addSubview(imageNode.view) + self.imageNode = imageNode + + imageNode.setSignal(chatWebFileImage(account: component.context.account, file: photo)) + self.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: component.context.account, userLocation: .other, image: photo).startStrict()) + } + + imageNode.frame = CGRect(origin: .zero, size: size) + imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: size.width / 2.0), imageSize: size, boundingSize: size, intrinsicInsets: UIEdgeInsets()))() + + self.backgroundView.isHidden = true + self.iconView.isHidden = true + self.avatarNode.isHidden = true + } else { + self.avatarNode.setPeer( + context: component.context, + theme: component.theme, + peer: peer, + synchronousLoad: true + ) + self.backgroundView.isHidden = true + self.iconView.isHidden = true + self.avatarNode.isHidden = false + } case .appStore: self.backgroundView.image = generateGradientFilledCircleImage( diameter: size.width, diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index d28c3f34ce..cb03ef8dc6 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -725,7 +725,7 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer { let resultController = UndoOverlayController( presentationData: presentationData, content: .image( - image: UIImage(bundleImageName: "Premium/Stars/StarMedium")!, + image: UIImage(bundleImageName: "Premium/Stars/StarLarge")!, title: presentationData.strings.Stars_Intro_PurchasedTitle, text: presentationData.strings.Stars_Intro_PurchasedText(presentationData.strings.Stars_Intro_PurchasedText_Stars(Int32(stars))).string, round: false, @@ -740,6 +740,10 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer { } self.starsContext.load() + + Queue.mainQueue().after(0.5, { + self.starsContext.loadMore() + }) } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift index 027842be37..1e1ea46f37 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift @@ -341,7 +341,7 @@ private final class SheetContent: CombinedComponent { transition: .immediate ) let balanceIcon = balanceIcon.update( - component: BundleIconComponent(name: "Premium/Stars/StarMedium", tintColor: nil), + component: BundleIconComponent(name: "Premium/Stars/StarLarge", tintColor: nil), availableSize: context.availableSize, transition: .immediate ) diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/Contents.json index 653a52e723..46aa1a67a6 100644 --- a/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Star20.pdf", + "filename" : "Star20 (3).pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/Star20.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/Star20 (3).pdf similarity index 87% rename from submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/Star20.pdf rename to submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/Star20 (3).pdf index ad28ef79f1f5d23fee06bf5f786d737f2f9f918e..11a9fc5e3bbf4d9da0b793bba6c66c1eaeee39a4 100644 GIT binary patch delta 1248 zcmX@#!T5d}UEn3$^5Neg0hg+>WJwN7q}-D^oUn`jPd;Dgh5yPn#p( zH`j1+shoTA?A;OTZ{Nz^#oej@U$MLX^QZlv|8GBj_kI2L^~aw(AKSC9_+QkY-+O)~ z>;Kp(mYIFpDt~_8?(p~fYIpztbi<~6{gXdc)i&QI+>)_Wb$|7x#pupk^0o-ma zTWM%|Guryr@wn9B^D>KAQq`X*C%+UskSWB;e<5e%dDF5*Y@LidmuLn=v7Gt9TH|zZ zdEZXuGL!pKmtD=nFSl=Hm3X<=qzH2Jf$krv1c;K0!j&a6sR eFf!!=dcL?Mv8be?C^Zcj8RjO&T&k+B{%!zw`RWG% delta 534 zcmaDqjq!X3jXjAoPVG|MN~8@Nnf<}N2}sAm8M3I;HS?qpvzX>B++IhPC0 z#$*&H=W$JT^blss<(jdQsl`A|lLbBX7>zeOcv>*l8(JDGKms$53rriD85x?P3z>ra z1p*3iRX}AJLM9kySr{3htFo{}7+*43)>}ghm@*k!OqR4QpUlB7 zKiN}DUC>z1+{DDt%tFD~LeJRJz|e5=L3XXlZ}m7P-xl4)Xu3I9dp_gj*POhQXWClV zS1|fc@$Hi}=V|#~+ZD&&;ilr+bw`bHQ|TmOmR`f01e=EX2cLf@_aFZj{c`G(pp9K` z+V{)t-?v}x|KH#JpU?mAYoGsr-TC9kosaFQe_j*&=U?*I)AK$@pHjNB{90wLh1|VA z|DT?Iy!`pI$_nM@&tLz%;avQ_Be*7Kdg|*x3@=vKe_Iut6cM$~qWDFe<Q@G-WnhiuMEowl6D>w6@`d0+X*Yp+)y3!S*oIPJwrt{!bcw-J^>S?RR?e>`LxQS&Qg`qzk>TVkZBUa9zr2 zJ#S_0dd17>ek|APtEO_kka?@+CMh4f8NKI__$cbVq*o2<7NF#B%XZgRg#&HJK(O7Ps) zUKgg)3*rgVlOK5hVBt{RbHB#QsOJfzscP41tC;5R4QIj*GA_{$sb@ZsuJSC_{ZZ@n zZ^6g6?cDf?^UOt0vxLxhUZ*22$bZvbv}M*hx43t)$E7xQvCN(yyQZ3Hwh80bGm0B? zZ)QfUoptw78_%H-r>NiW>Z-b@KWGwb@ok!R@0mzj=%ia8E*vUfHScmuQfIY4*KWQW zweB}h9=6=f#A~*PqehU?cv}ZU{c6!qe65Ek2j}F@`rwy$ecQDQb_z=8eWQbu4!QRB z$i6cFdLb%yvEk}g$vdi77g>vhA6{aybfW$u;m;Sht_PpN>N-MX%$GkEGF zcU5~en1$)Bvto3vZQk%zz>aZU)SCPcsZn$9E_$86qJ5WCk)4DW~rxTGjGF&D(jPs-xrGPF=IPyn$M6wD1w5W<#l zVH+C-{fNom>(1@noOSHD8gtwd8wlwFauw4v|y|^Gz3})0t$ItVA{~k(8LH$$lSur6hp`Y z7&IsvEetKt&9X4E1iA-Rm9Zg`Su!9wLkkm2bSo`P%`7H2IvZ)3z(PYmII}8M!N`;g b=-c9w#G;alqSQ2C1eqCGa;d7i`nv%DXR!5o delta 548 zcmex6mGN#n$t7G0AfWG)nwMUZp`HM#R|k|<1{ zLJr&rgbg`hQ`mF3GPu&X@+QZ-i%A-qDHtf^CuITMYGiE0m713V;&2(7Pk!WD$7nh^ z(QVD-ldjy8f7tLaT25AV50EglgzGRdgKID{nB41L#ArUrL&U+*0;nPg>idAiqSU++ z1tUYKn4KL$4@eARw~dX0zISFyu|l*0(4-I#Q!yhHxq!s<)MB6^ljnQrF&b|^=V8HE zZ(t5|2?!|Uae-+=GeZNQHdG-K17ma{Qv-7hAtMa4%ng7+gKCz!1;Y4}$*a9Iv_P%| m`&2(TvnmxBgj_)X6_+Fyl~fd^rg0gXS(q4esj9mAy8!^^&9PO`b1d zIXPd;5iJZJPd>(GSZ`;?Ra{b(nwZN~!I&CyI#0xrXV3ZYGm@Qg`{lzO9-Vs3eL;g` ziLZ*sk?+CtSRy)Yzh2vNx@_m2>E-47_sN&fzqkM9{knU4|NqJM-|p`fpSyqVKbg4y zws&i~JGLBBoI0u0{@wP|?fU-y<@WMz&p$POKmIted#U8jZJXEWaIXJbFP9K7q3Qgz zEP?OyynD|0pV)208?a66vxC&zsNWl}D&;?y3D~2Xv|iOy@wx4$O9Bp2BD&_0{cFP~ zYA+Ot%9^G1yVSutlA%k@`_0~4R;)`eJTefSFWkEH`{*l&r>OLT zsfKA^0sC2{qZ0&lx+RUXBo5xGXmQcx+Gp5lv~F%qPiAE^zmaoZ2EXb;))MIzp-jy6 zEwd$_p4T;&I4m%0!Rm9j0)w`m4w>}BhM6&KhNzS9&P7}D1s@je>a;eKU`adDbI|@- zV8P2G|1S(Z{)z$treX|>3IeSJ&U38ro{`yS-pAXSFr!ViZOY^V??kg3Qyzv}I5!@+ zG%2I3b@QbiZehwiKPOml^4Vu6H2=>$o+JFtsJ^D9b~lf6<8n#vw2Yps%pHMJD)PB* zYBBsbc5t2B>DS-kaxOw>s_TIgPR>8vJj(nVPEN{l+#GR1A^i+*wfGHhh1@G|AG9+} zy}*}r?%%=}ug%sw8g(C5d-nUiCJx1-%o83 zl{~qSL4>J3Xjw##Lht>`<;T>7+hrYA8rb~#xT)rHT40if`j0(Jw>>C4RKSpEviWp_ zjM;_8xUzo-K7YPHyX5Y^-1zuh{@cgjn!jyeVF6_#XflT9ItzF%GqW^BF0g`AyVi8bK z!3gY0G$$KR4zw0w0veTWZ6a=jD8E2K6Ofpm3N*@i@+NCtCL`0$53L0m>rG9KOce}3 zKp~F{OdFb-ngAVvB4lP@4h#h}Axog~C_?5I2EaH%6Ea5EXkln<1Xtw@R&HovWDG1r zQFK}unW7tJVPcH#Rtr-@!^uvzhT34ig2PQeII}8M!N`;gToNW0l~fd^rg0fsn3@}L Ksj9mAy8!^U??~7H delta 479 zcmX>ym2qD6hGiO)x4H5&nomCNDm!_CrpDxBnwpd43^*r?XcbKs5a*bDUNmCzFJ0@& zbz)qTL$$Rg9}sf_iW^UE&}5k`t#1re%Q1PLxbWot;_8zfB-tnL1PkhZ+R(NP?Ik zB?=10hDHVm4Q7)=+>1huK?;F#K%noOpI4HaS5mBCXo4gfkXQs1R4_D$iXmGH5`&m+ zW22z&otaXs5UpTnHd)%kgvroia-fGWlcD+KG!I=SL(9!mJp>s849(5W6bwK>A&(18 z8=6>J7@-N78W^CfGBvaSx)nvEsgbGW Date: Sat, 25 May 2024 21:31:39 +0400 Subject: [PATCH 2/5] Various fixes --- .../Sources/HashtagSearchControllerNode.swift | 22 ++++++++++++++++--- ...ChatInlineSearchResultsListComponent.swift | 10 ++++++--- .../ChatInterfaceStateContextMenus.swift | 4 +++- .../Sources/ChatTagSearchInputPanelNode.swift | 10 ++++++++- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift index 3dec889a25..6012db359d 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift @@ -54,7 +54,8 @@ final class HashtagSearchControllerNode: ASDisplayNode { self.shimmerNode.allowsGroupOpacity = true self.recentListNode = HashtagSearchRecentListNode(context: context) - + self.recentListNode.alpha = 0.0 + let navigationController = controller.navigationController as? NavigationController if let peer { self.currentController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .inline(navigationController)) @@ -150,6 +151,10 @@ final class HashtagSearchControllerNode: ASDisplayNode { } self.searchContentNode.query = query self.updateSearchQuery(query) + + Queue.mainQueue().after(0.4) { + let _ = addRecentHashtagSearchQuery(engine: context.engine, string: query).startStandalone() + } } self.currentController?.isSelectingMessagesUpdated = { [weak self] isSelecting in @@ -303,9 +308,20 @@ final class HashtagSearchControllerNode: ASDisplayNode { self.insertSubnode(self.recentListNode, aboveSubnode: self.shimmerNode) } - self.recentListNode.frame = CGRect(origin: .zero, size: layout.size) + transition.updateFrame(node: self.recentListNode, frame: CGRect(origin: .zero, size: layout.size)) self.recentListNode.updateLayout(layout: ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: insets.top - 35.0, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right), safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition) - self.recentListNode.isHidden = !self.query.isEmpty + + let recentTransition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) + if self.query.isEmpty { + recentTransition.updateAlpha(node: self.recentListNode, alpha: 1.0) + } else if self.recentListNode.alpha > 0.0 { + Queue.mainQueue().after(0.1, { + if !self.query.isEmpty { + recentTransition.updateAlpha(node: self.recentListNode, alpha: 0.0) + } + }) + } + if !self.hasValidLayout { self.hasValidLayout = true diff --git a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift index d8a51a421e..13552cf68f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift @@ -768,10 +768,14 @@ public final class ChatInlineSearchResultsListComponent: Component { } let renderedPeer: EngineRenderedPeer - if let effectiveAuthor { + if let effectiveAuthor, !component.showEmptyResults { renderedPeer = EngineRenderedPeer(peer: effectiveAuthor) } else { - renderedPeer = EngineRenderedPeer(peerId: message.id.peerId, peers: [:], associatedMedia: [:]) + var peers: [EnginePeer.Id: EnginePeer] = [:] + if let peer = message.peers[message.id.peerId] { + peers[message.id.peerId] = EnginePeer(peer) + } + renderedPeer = EngineRenderedPeer(peerId: message.id.peerId, peers: peers, associatedMedia: [:]) } return ChatListItem( @@ -800,7 +804,7 @@ public final class ChatInlineSearchResultsListComponent: Component { inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, - displayAsMessage: component.peerId != component.context.account.peerId, + displayAsMessage: component.peerId != component.context.account.peerId && !component.showEmptyResults, hasFailedMessages: false, forumTopicData: nil, topForumTopicItems: [], diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 4e523d99df..932855e496 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -281,10 +281,12 @@ private func canViewReadStats(message: Message, participantCount: Int?, isMessag } func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, accountPeerId: PeerId) -> Bool { + if case let .customChatContents(contents) = chatPresentationInterfaceState.subject, case .hashTagSearch = contents.kind { + return false + } if case .customChatContents = chatPresentationInterfaceState.chatLocation { return true } - guard let peer = chatPresentationInterfaceState.renderedPeer?.peer else { return false } diff --git a/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift index ae0a6a3767..d1d04db7e9 100644 --- a/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift @@ -228,7 +228,15 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { if let currentId = results.currentId, let index = results.messageIndices.firstIndex(where: { $0.id == currentId }) { canChangeListMode = true - if params.interfaceState.displayHistoryFilterAsList || self.alwaysShowTotalMessagesCount { + if self.alwaysShowTotalMessagesCount { + let value = presentationStringsFormattedNumber(Int32(displayTotalCount), params.interfaceState.dateTimeFormat.groupingSeparator) + let suffix = params.interfaceState.strings.Chat_BottomSearchPanel_MessageCount(Int32(displayTotalCount)) + resultsTextString = [AnimatedTextComponent.Item( + id: "text", + isUnbreakable: true, + content: .text(params.interfaceState.strings.Chat_BottomSearchPanel_MessageCountFormat(value, suffix).string) + )] + } else if params.interfaceState.displayHistoryFilterAsList { resultsTextString = extractAnimatedTextString(string: params.interfaceState.strings.Chat_BottomSearchPanel_MessageCountFormat( ".", "." From 9a935566cc52c42717bf49909d50f7dce8299e02 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 26 May 2024 00:34:38 +0400 Subject: [PATCH 3/5] requestMessageUpdate for albums --- .../Sources/ChatHistoryListNode.swift | 79 ++++++++++++------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 64a2f5080d..e8a249a632 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -4260,37 +4260,58 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto loop: for i in 0 ..< historyView.filteredEntries.count { switch historyView.filteredEntries[i] { - case let .MessageEntry(message, presentationData, read, location, selection, attributes): - if message.id == id { - let index = historyView.filteredEntries.count - 1 - i - let item: ListViewItem - switch self.mode { - case .bubbles: - item = ChatMessageItemImpl(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders) - case let .list(_, _, _, displayHeaders, hintLinks, isGlobalSearch): - let displayHeader: Bool - switch displayHeaders { - case .none: - displayHeader = false - case .all: - displayHeader = true - case .allButLast: - displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != historyView.lastHeaderId - } - item = ListMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch) + case let .MessageEntry(message, presentationData, read, location, selection, attributes): + if message.id == id { + let index = historyView.filteredEntries.count - 1 - i + let item: ListViewItem + switch self.mode { + case .bubbles: + item = ChatMessageItemImpl(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders) + case let .list(_, _, _, displayHeaders, hintLinks, isGlobalSearch): + let displayHeader: Bool + switch displayHeaders { + case .none: + displayHeader = false + case .all: + displayHeader = true + case .allButLast: + displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != historyView.lastHeaderId } - let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil) - - var scrollToItem: ListViewScrollToItem? - if scroll { - scrollToItem = ListViewScrollToItem(index: index, position: .center(.top), animated: true, curve: .Spring(duration: 0.4), directionHint: .Down, displayLink: true) - } - - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: scrollToItem, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) - break loop + item = ListMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch) } - default: - break + let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil) + + var scrollToItem: ListViewScrollToItem? + if scroll { + scrollToItem = ListViewScrollToItem(index: index, position: .center(.top), animated: true, curve: .Spring(duration: 0.4), directionHint: .Down, displayLink: true) + } + + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: scrollToItem, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + break loop + } + case let .MessageGroupEntry(_, messages, presentationData): + if messages.contains(where: { $0.0.id == id }) { + let index = historyView.filteredEntries.count - 1 - i + let item: ListViewItem + switch self.mode { + case .bubbles: + item = ChatMessageItemImpl(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .group(messages: messages), disableDate: disableFloatingDateHeaders) + case .list: + assertionFailure() + item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false) + } + let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil) + + var scrollToItem: ListViewScrollToItem? + if scroll { + scrollToItem = ListViewScrollToItem(index: index, position: .center(.top), animated: true, curve: .Spring(duration: 0.4), directionHint: .Down, displayLink: true) + } + + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: scrollToItem, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + break loop + } + default: + break } } } From 61ef217ad125d5859d7ff454d3fc5ac1f5c2fa32 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 26 May 2024 12:59:23 +0400 Subject: [PATCH 4/5] Various fixes --- .../Sources/HashtagSearchGlobalChatContents.swift | 2 +- .../Sources/ChatMessageItemImpl.swift | 9 +++++++-- .../TelegramUI/Sources/ChatHistoryListNode.swift | 13 ++++++++++--- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift index f7acade603..e8133c9c28 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift @@ -94,7 +94,7 @@ final class HashtagSearchGlobalChatContents: ChatCustomContentsProtocol { } func loadMore() { - guard self.historyViewDisposable == nil, let currentSearchState = self.currentSearchState else { + guard self.historyViewDisposable == nil, let currentSearchState = self.currentSearchState, currentSearchState.main.nextRate != nil else { return } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift index 7bb83c771d..4c7a445884 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift @@ -75,11 +75,16 @@ private func messagesShouldBeMerged(accountPeerId: PeerId, _ lhs: Message, _ rhs } } + var sameChat = true + if lhs.id.peerId != rhs.id.peerId { + sameChat = false + } + var sameThread = true if let lhsPeer = lhs.peers[lhs.id.peerId], let rhsPeer = rhs.peers[rhs.id.peerId], arePeersEqual(lhsPeer, rhsPeer), let channel = lhsPeer as? TelegramChannel, channel.flags.contains(.isForum), lhs.threadId != rhs.threadId { sameThread = false } - + var sameAuthor = false if lhsEffectiveAuthor?.id == rhsEffectiveAuthor?.id && lhs.effectivelyIncoming(accountPeerId) == rhs.effectivelyIncoming(accountPeerId) { sameAuthor = true @@ -124,7 +129,7 @@ private func messagesShouldBeMerged(accountPeerId: PeerId, _ lhs: Message, _ rhs } } - if abs(lhsEffectiveTimestamp - rhsEffectiveTimestamp) < Int32(10 * 60) && sameAuthor && sameThread { + if abs(lhsEffectiveTimestamp - rhsEffectiveTimestamp) < Int32(10 * 60) && sameChat && sameAuthor && sameThread { if let channel = lhs.peers[lhs.id.peerId] as? TelegramChannel, case .group = channel.info, lhsEffectiveAuthor?.id == channel.id, !lhs.effectivelyIncoming(accountPeerId) { return .none } diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index e8a249a632..36b396b27e 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -3035,10 +3035,17 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto if self.chatHistoryLocationValue == historyView.locationInput { self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .upperBound, anchorIndex: .upperBound, count: historyMessageCount, highlight: false), id: self.takeNextHistoryLocationId()) } - } else if mathesLast && historyView.originalView.earlierId != nil { + } else if mathesLast { let locationInput: ChatHistoryLocation = .Navigation(index: .message(firstEntry.index), anchorIndex: .message(firstEntry.index), count: historyMessageCount, highlight: false) - if self.chatHistoryLocationValue?.content != locationInput { - self.chatHistoryLocationValue = ChatHistoryLocationInput(content: locationInput, id: self.takeNextHistoryLocationId()) + if historyView.originalView.earlierId != nil { + if self.chatHistoryLocationValue?.content != locationInput { + self.chatHistoryLocationValue = ChatHistoryLocationInput(content: locationInput, id: self.takeNextHistoryLocationId()) + } + } else if case let .customChatContents(customChatContents) = self.subject, case .hashTagSearch = customChatContents.kind { + if self.chatHistoryLocationValue?.content != locationInput { + self.chatHistoryLocationValue = ChatHistoryLocationInput(content: locationInput, id: self.takeNextHistoryLocationId()) + customChatContents.loadMore() + } } } } From 560c07af6b604c7a52b2dee938d9476411bf1035 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 26 May 2024 13:33:34 +0400 Subject: [PATCH 5/5] Fix build --- .../Sources/HashtagSearchGlobalChatContents.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift index e8133c9c28..11e88d64c9 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift @@ -94,7 +94,7 @@ final class HashtagSearchGlobalChatContents: ChatCustomContentsProtocol { } func loadMore() { - guard self.historyViewDisposable == nil, let currentSearchState = self.currentSearchState, currentSearchState.main.nextRate != nil else { + guard self.historyViewDisposable == nil, let currentSearchState = self.currentSearchState, let currentHistoryView = self.sourceHistoryView, currentHistoryView.holeEarlier else { return } @@ -114,7 +114,7 @@ final class HashtagSearchGlobalChatContents: ChatCustomContentsProtocol { let updateType: ViewUpdateType = .FillHole - let historyView = MessageHistoryView(tag: nil, namespaces: .just(Set([Namespaces.Message.Cloud])), entries: result.0.messages.reversed().map { MessageHistoryEntry(message: $0, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)) }, holeEarlier: false, holeLater: false, isLoading: false) + let historyView = MessageHistoryView(tag: nil, namespaces: .just(Set([Namespaces.Message.Cloud])), entries: result.0.messages.reversed().map { MessageHistoryEntry(message: $0, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)) }, holeEarlier: !result.0.completed, holeLater: false, isLoading: false) self.sourceHistoryView = historyView self.updateHistoryView(updateType: updateType)