@@ -43,9 +43,11 @@ final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode {
4343
4444 private let titleNode : ImmediateTextNode
4545 private let buttonNode : HighlightTrackingButtonNode
46- private var iconLayers : [ InlineStickerItemLayer ] = [ ]
46+ private var iconLayers : [ AnyHashable : InlineStickerItemLayer ] = [ : ]
4747
4848 private var isSelected : Bool = false
49+ private var icons : [ ProfileGiftsContext . State . StarGift ] = [ ]
50+ private var titleWidth : CGFloat ?
4951
5052 init ( pressed: @escaping ( ) -> Void ) {
5153 self . pressed = pressed
@@ -67,27 +69,53 @@ final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode {
6769 self . pressed ( )
6870 }
6971
70- func updateText( context: AccountContext , title: String , icons: [ TelegramMediaFile ] = [ ] , isSelected: Bool , presentationData: PresentationData ) {
72+ func updateText( context: AccountContext , title: String , icons: [ ProfileGiftsContext . State . StarGift ] = [ ] , isSelected: Bool , presentationData: PresentationData ) {
7173 self . isSelected = isSelected
7274 self . titleNode. attributedText = NSAttributedString ( string: title, font: Font . medium ( 14.0 ) , textColor: isSelected ? presentationData. theme. list. itemAccentColor : presentationData. theme. list. itemSecondaryTextColor)
75+ self . icons = icons
7376
7477 if !icons. isEmpty {
75- if self . iconLayers. isEmpty {
76- for icon in icons {
77- let iconSize = CGSize ( width: 18.0 , height: 18.0 )
78+ var validIds = Set < AnyHashable > ( )
79+ var index = 0
80+ for icon in icons {
81+ let id : AnyHashable
82+ if let reference = icon. reference {
83+ id = reference
84+ } else {
85+ id = index
86+ }
87+ validIds. insert ( id)
88+
89+ let iconSize = CGSize ( width: 18.0 , height: 18.0 )
90+ if let _ = self . iconLayers [ id] {
91+
92+ } else {
93+ var file : TelegramMediaFile ?
94+ switch icon. gift {
95+ case let . generic( gift) :
96+ file = gift. file
97+ case let . unique( gift) :
98+ for attribute in gift. attributes {
99+ if case let . model( _, fileValue, _) = attribute {
100+ file = fileValue
101+ }
102+ }
103+ }
104+ guard let file else {
105+ continue
106+ }
78107
79108 let emoji = ChatTextInputTextCustomEmojiAttribute (
80109 interactivelySelectedFromPackId: nil ,
81- fileId: icon . fileId. id,
82- file: icon
110+ fileId: file . fileId. id,
111+ file: file
83112 )
84-
85113 let animationLayer = InlineStickerItemLayer (
86114 context: . account( context) ,
87115 userLocation: . other,
88116 attemptSynchronousLoad: false ,
89117 emoji: emoji,
90- file: icon ,
118+ file: file ,
91119 cache: context. animationCache,
92120 renderer: context. animationRenderer,
93121 unique: true ,
@@ -96,12 +124,30 @@ final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode {
96124 loopCount: 1
97125 )
98126 animationLayer. isVisibleForAnimations = true
99- self . iconLayers. append ( animationLayer)
127+ self . iconLayers [ id ] = animationLayer
100128 self . layer. addSublayer ( animationLayer)
129+
130+ animationLayer. animateAlpha ( from: 0.0 , to: 1.0 , duration: 0.2 )
131+ animationLayer. animateScale ( from: 0.01 , to: 1.0 , duration: 0.2 )
132+ }
133+ index += 1
134+ }
135+
136+ var removeIds : [ AnyHashable ] = [ ]
137+ for (id, layer) in self . iconLayers {
138+ if !validIds. contains ( id) {
139+ removeIds. append ( id)
140+ layer. animateScale ( from: 1.0 , to: 0.01 , duration: 0.25 , removeOnCompletion: false )
141+ layer. animateAlpha ( from: 1.0 , to: 0.0 , duration: 0.25 , removeOnCompletion: false , completion: { _ in
142+ layer. removeFromSuperlayer ( )
143+ } )
101144 }
102145 }
146+ for id in removeIds {
147+ self . iconLayers. removeValue ( forKey: id)
148+ }
103149 } else {
104- for layer in self . iconLayers {
150+ for (_ , layer) in self . iconLayers {
105151 layer. removeFromSuperlayer ( )
106152 }
107153 self . iconLayers. removeAll ( )
@@ -115,24 +161,54 @@ final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode {
115161 }
116162
117163 func updateLayout( height: CGFloat ) -> CGFloat {
118- var totalWidth : CGFloat = 0.0
119164 let titleSize = self . titleNode. updateLayout ( CGSize ( width: 200.0 , height: . greatestFiniteMagnitude) )
165+ let iconSize = CGSize ( width: 18.0 , height: 18.0 )
166+ let spacing : CGFloat = 1.0
167+
120168 self . titleNode. frame = CGRect ( origin: CGPoint ( x: 0.0 , y: floor ( ( height - titleSize. height) / 2.0 ) ) , size: titleSize)
121- totalWidth = titleSize. width
169+ self . titleWidth = titleSize. width
122170
171+ var totalWidth = titleSize. width
123172 if !self . iconLayers. isEmpty {
124173 totalWidth += 2.0
125- let iconSize = CGSize ( width: 18.0 , height: 18.0 )
126- let spacing : CGFloat = 1.0
127- for iconlayer in self . iconLayers {
128- iconlayer. frame = CGRect ( origin: CGPoint ( x: totalWidth, y: 15.0 ) , size: iconSize)
129- totalWidth += iconSize. width + spacing
130- }
174+ totalWidth += ( iconSize. width + spacing) * CGFloat( self . iconLayers. count)
131175 totalWidth -= spacing
132176 }
177+
178+ self . layoutIcons ( transition: . animated( duration: 0.3 , curve: . spring) )
179+
133180 return totalWidth
134181 }
135182
183+ func layoutIcons( transition: ContainedViewLayoutTransition ) {
184+ guard let titleWidth = self . titleWidth else {
185+ return
186+ }
187+ let iconSize = CGSize ( width: 18.0 , height: 18.0 )
188+ let spacing : CGFloat = 1.0
189+
190+ var origin = CGPoint ( x: titleWidth + 2.0 , y: 15.0 )
191+
192+ var index = 0
193+ for icon in self . icons {
194+ let id : AnyHashable
195+ if let reference = icon. reference {
196+ id = reference
197+ } else {
198+ id = index
199+ }
200+ if let layer = self . iconLayers [ id] {
201+ var iconTransition = transition
202+ if layer. frame. width. isZero {
203+ iconTransition = . immediate
204+ }
205+ iconTransition. updateFrame ( layer: layer, frame: CGRect ( origin: origin, size: iconSize) )
206+ }
207+ origin. x += iconSize. width + spacing
208+ index += 1
209+ }
210+ }
211+
136212 func updateArea( size: CGSize , sideInset: CGFloat ) {
137213 self . buttonNode. frame = CGRect ( origin: CGPoint ( x: - sideInset, y: 0.0 ) , size: CGSize ( width: size. width + sideInset * 2.0 , height: size. height) )
138214 }
@@ -141,7 +217,7 @@ final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode {
141217struct PeerInfoPaneSpecifier : Equatable {
142218 var key : PeerInfoPaneKey
143219 var title : String
144- var icons : [ TelegramMediaFile ]
220+ var icons : [ ProfileGiftsContext . State . StarGift ]
145221}
146222
147223private func interpolateFrame( from fromValue: CGRect , to toValue: CGRect , t: CGFloat ) -> CGRect {
@@ -1189,7 +1265,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
11891265
11901266 self . tabsContainerNode. update ( size: CGSize ( width: size. width - sideInset * 2.0 , height: tabsHeight) , presentationData: presentationData, paneList: availablePanes. map { key in
11911267 let title : String
1192- var icons : [ TelegramMediaFile ] = [ ]
1268+ var icons : [ ProfileGiftsContext . State . StarGift ] = [ ]
11931269 switch key {
11941270 case . stories:
11951271 title = presentationData. strings. PeerInfo_PaneStories
@@ -1223,19 +1299,9 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
12231299 title = presentationData. strings. PeerInfo_SavedMessagesTabTitle
12241300 case . gifts:
12251301 title = presentationData. strings. PeerInfo_PaneGifts
1226- icons = data? . profileGiftsContext? . currentState? . gifts. prefix ( 3 ) . compactMap { gift in
1227- switch gift. gift {
1228- case let . generic( gift) :
1229- return gift. file
1230- case let . unique( gift) :
1231- for attribute in gift. attributes {
1232- if case let . model( _, file, _) = attribute {
1233- return file
1234- }
1235- }
1236- return nil
1237- }
1238- } ?? [ ]
1302+ if let gifts = data? . profileGiftsContext? . currentState? . gifts. prefix ( 3 ) {
1303+ icons = Array ( gifts)
1304+ }
12391305 }
12401306 return PeerInfoPaneSpecifier ( key: key, title: title, icons: icons)
12411307 } , selectedPane: self . currentPaneKey, disableSwitching: disableTabSwitching, transitionFraction: self . transitionFraction, transition: transition)
0 commit comments