@@ -18,6 +18,8 @@ import InlineStickerLayer
1818
1919
2020
21+ private let largeReactionHoldDuration : Double = 0.8
22+
2123public final class ContextAddReactionsListView : View , StickerFramesCollector {
2224
2325
@@ -43,7 +45,7 @@ public final class ContextAddReactionsListView : View, StickerFramesCollector {
4345
4446 private let parentWindow : Window ?
4547
46- required init ( frame frameRect : NSRect , context : any AccountContextProtocol , reaction : ContextReaction , add : @escaping ( MessageReaction . Reaction , Bool , NSRect ? ) -> Void , theme : TelegramPresentationTheme , message : Message ? , parentWindow : Window ? = nil ) {
48+ required init ( frame frameRect : NSRect , context : any AccountContextProtocol , reaction : ContextReaction , add : @escaping ( MessageReaction . Reaction , Bool , NSRect ? ) -> Void , addLarge : ( ( MessageReaction . Reaction , NSRect ? ) -> Void ) ? , theme : TelegramPresentationTheme , message : Message ? , parentWindow : Window ? = nil ) {
4749
4850 let size : NSSize = reaction . isSelected ? NSMakeSize ( 25 , 24 ) : NSMakeSize ( frameRect . width , 30 )
4951 let rect = CGRect ( origin : . zero , size : size )
@@ -152,13 +154,32 @@ public final class ContextAddReactionsListView : View, StickerFramesCollector {
152154 }
153155
154156 set ( handler : { control in
155- if let window = control . window {
157+ if let window = control . window , ( control as? ReactionView ) ? . largeInvoked != true {
156158 let wrect = control . convert ( control . frame . size . bounds , to : nil )
157159 let srect = window . convertToScreen ( wrect )
158160 add ( reaction . value , true , getWindow ( ) . convertFromScreen ( srect ) )
159161 }
160162 } , for : . Click )
161163
164+ if let addLarge , ! reaction . isSelected , reaction . value != . stars {
165+ self . handleLongEvent = false
166+ self . largeHoldEnabled = true
167+ set ( handler : { control in
168+ ( control as? ReactionView ) ? . startLargeHold ( completion : { control in
169+ if let window = control . window {
170+ let wrect = control . convert ( control . frame . size . bounds , to : nil )
171+ let srect = window . convertToScreen ( wrect )
172+ NSHapticFeedbackManager . defaultPerformer . perform ( . levelChange , performanceTime : . default )
173+ addLarge ( reaction . value , getWindow ( ) . convertFromScreen ( srect ) )
174+ AppMenu . closeAll ( )
175+ }
176+ } )
177+ } , for : . Down )
178+ set ( handler : { control in
179+ ( control as? ReactionView ) ? . cancelLargeHold ( )
180+ } , for : . Up )
181+ }
182+
162183 if case . stars = reaction . value , let message {
163184 self . set ( handler : { _ in
164185 showModal ( with : Star_ReactionsController ( context : context , source : . message ( . init ( message ) ) ) , for : getWindow ( ) )
@@ -210,6 +231,40 @@ public final class ContextAddReactionsListView : View, StickerFramesCollector {
210231 return context . isLite ( . emoji )
211232 }
212233
234+ private var largeHoldTimer : SwiftSignalKit . Timer ?
235+ private var largeHoldEnabled : Bool = false
236+ fileprivate private( set) var largeInvoked : Bool = false
237+
238+ fileprivate func startLargeHold ( completion : @escaping ( ReactionView ) -> Void ) {
239+ largeHoldTimer ? . invalidate ( )
240+ self . layer ? . zPosition = 1
241+ self . layer ? . animateScaleCenter ( from : 1 , to : 1.5 , duration : largeReactionHoldDuration , removeOnCompletion : false )
242+ largeHoldTimer = SwiftSignalKit . Timer ( timeout : largeReactionHoldDuration , repeat : false , completion : { [ weak self ] in
243+ guard let self else {
244+ return
245+ }
246+ self . largeHoldTimer = nil
247+ if self . mouseInside ( ) {
248+ self . largeInvoked = true
249+ completion ( self )
250+ } else {
251+ self . cancelLargeHold ( )
252+ }
253+ } , queue : . mainQueue ( ) )
254+ largeHoldTimer ? . start ( )
255+ }
256+
257+ fileprivate func cancelLargeHold ( ) {
258+ guard ! largeInvoked else {
259+ return
260+ }
261+ largeHoldTimer ? . invalidate ( )
262+ largeHoldTimer = nil
263+ let current = self . layer ? . presentation ( ) ? . value ( forKeyPath : " transform.scale " ) as? CGFloat ?? 1
264+ self . layer ? . animateScaleCenter ( from : current , to : 1 , duration : 0.2 , removeOnCompletion : true )
265+ self . layer ? . zPosition = 0
266+ }
267+
213268 private func apply ( _ data : Data , key : String , policy : LottiePlayPolicy ) {
214269 let animation = LottieAnimation ( compressed : data , key : LottieAnimationEntryKey ( key : . bundle ( " reaction_ \( reaction . value ) _ \( key ) " ) , size : player . frame . size ) , type : . lottie , cachePurpose : . none , playPolicy : policy , maximumFps : 60 , runOnQueue : Queue ( ) , metalSupport : false )
215270 player . set ( animation , reset : true , saveContext : true , animated : false )
@@ -258,9 +313,9 @@ public final class ContextAddReactionsListView : View, StickerFramesCollector {
258313 break
259314 }
260315
261- if previous == . Hover , state == . Highlight {
316+ if ! largeHoldEnabled , previous == . Hover , state == . Highlight {
262317 self . layer ? . animateScaleCenter ( from : 1 , to : 0.8 , duration : 0.2 , removeOnCompletion : false )
263- } else if state == . Hover && previous == . Highlight {
318+ } else if ! largeHoldEnabled , state == . Hover , previous == . Highlight {
264319 self . layer ? . animateScaleCenter ( from : 0.8 , to : 1 , duration : 0.2 , removeOnCompletion : true )
265320 }
266321 previous = state
@@ -358,7 +413,7 @@ public final class ContextAddReactionsListView : View, StickerFramesCollector {
358413 private let presentation : TelegramPresentationTheme
359414 private let hasBubble : Bool
360415 private let aboveText : TextViewLayout ?
361- public required init ( frame frameRect : NSRect , context : any AccountContextProtocol , list : [ ContextReaction ] , add : @escaping ( MessageReaction . Reaction , Bool , NSRect ? ) -> Void , radiusLayer : CGFloat ? = 15 , revealReactions : ( ( ContextAddReactionsListView & StickerFramesCollector ) -> Void ) ? = nil , presentation : TelegramPresentationTheme = theme , hasBubble : Bool = true , aboveText : TextViewLayout ? = nil , message : Message ? = nil , parentWindow : Window ? = nil ) {
416+ public required init ( frame frameRect : NSRect , context : any AccountContextProtocol , list : [ ContextReaction ] , add : @escaping ( MessageReaction . Reaction , Bool , NSRect ? ) -> Void , addLarge : ( ( MessageReaction . Reaction , NSRect ? ) -> Void ) ? = nil , radiusLayer : CGFloat ? = 15 , revealReactions : ( ( ContextAddReactionsListView & StickerFramesCollector ) -> Void ) ? = nil , presentation : TelegramPresentationTheme = theme , hasBubble : Bool = true , aboveText : TextViewLayout ? = nil , message : Message ? = nil , parentWindow : Window ? = nil ) {
362417 self . list = list
363418 self . showMore = ShowMore ( frame : NSMakeRect ( 0 , 0 , 34 , 34 ) , theme : presentation )
364419 self . revealReactions = revealReactions
@@ -464,7 +519,7 @@ public final class ContextAddReactionsListView : View, StickerFramesCollector {
464519 for reaction in list {
465520 let add : ( ContextReaction ) -> Void = { reaction in
466521 let itemSize = size . bounds
467- let reaction = ReactionView ( frame : NSMakeRect ( x , y , itemSize . width , itemSize . height ) , context : context , reaction : reaction , add : add , theme : presentation , message : message , parentWindow : parentWindow )
522+ let reaction = ReactionView ( frame : NSMakeRect ( x , y , itemSize . width , itemSize . height ) , context : context , reaction : reaction , add : add , addLarge : addLarge , theme : presentation , message : message , parentWindow : parentWindow )
468523
469524 self . documentView . addSubview ( reaction )
470525 x += size . width + 4
0 commit comments