@@ -6,13 +6,11 @@ import '../api/route/messages.dart';
66import  '../generated/l10n/zulip_localizations.dart' ;
77import  '../model/autocomplete.dart' ;
88import  '../model/emoji.dart' ;
9- import  '../model/store.dart' ;
109import  'color.dart' ;
11- import  'content.dart' ;
1210import  'dialog.dart' ;
1311import  'emoji.dart' ;
1412import  'inset_shadow.dart' ;
15- import  'profile .dart' ;
13+ import  'reaction_users_sheet .dart' ;
1614import  'store.dart' ;
1715import  'text.dart' ;
1816import  'theme.dart' ;
@@ -130,7 +128,7 @@ class ReactionChipsList extends StatelessWidget {
130128      context:  context,
131129      builder:  (BuildContext  context) =>  PerAccountStoreWidget (
132130        accountId:  store.accountId,
133-         child:  _ReactionUsersSheet (
131+         child:  ReactionUsersSheet (
134132          reactions:  reactions,
135133          initialSelectedReaction:  selectedReaction,
136134          store:  store,
@@ -155,252 +153,6 @@ class ReactionChipsList extends StatelessWidget {
155153  }
156154}
157155
158- class  _ReactionUsersSheet  extends  StatefulWidget  {
159-   const  _ReactionUsersSheet ({
160-     required  this .reactions,
161-     required  this .initialSelectedReaction,
162-     required  this .store,
163-   });
164- 
165-   final  Reactions  reactions;
166-   final  ReactionWithVotes  initialSelectedReaction;
167-   final  PerAccountStore  store;
168- 
169-   @override 
170-   State <_ReactionUsersSheet > createState () =>  _ReactionUsersSheetState ();
171- }
172- 
173- class  _ReactionUsersSheetState  extends  State <_ReactionUsersSheet > {
174-   late  ReactionWithVotes ?  _selectedReaction;
175- 
176-   /// Cache for emoji displays to avoid recomputing them 
177- final  Map <String , Widget > _emojiCache =  {};
178- 
179-   @override 
180-   void  initState () {
181-     super .initState ();
182-     _selectedReaction =  widget.initialSelectedReaction;
183-     _prepareEmojiCache ();
184-   }
185- 
186-   /// Pre-compute emoji displays for better performance 
187- void  _prepareEmojiCache () {
188-     for  (final  reaction in  widget.reactions.aggregated) {
189-       final  key =  '${reaction .reactionType }_${reaction .emojiCode }' ;
190-       if  (! _emojiCache.containsKey (key)) {
191-         final  emojiDisplay =  widget.store.emojiDisplayFor (
192-           emojiType:  reaction.reactionType,
193-           emojiCode:  reaction.emojiCode,
194-           emojiName:  reaction.emojiName,
195-         ).resolve (widget.store.userSettings);
196- 
197-         final  emoji =  switch  (emojiDisplay) {
198-           UnicodeEmojiDisplay () =>  _UnicodeEmoji (
199-             emojiDisplay:  emojiDisplay),
200-           ImageEmojiDisplay () =>  _ImageEmoji (
201-             emojiDisplay:  emojiDisplay, emojiName:  reaction.emojiName, selected:  false ),
202-           TextEmojiDisplay () =>  _TextEmoji (
203-             emojiDisplay:  emojiDisplay, selected:  false ),
204-         };
205- 
206-         _emojiCache[key] =  SizedBox (
207-           width:  20 ,
208-           height:  20 ,
209-           child:  emoji,
210-         );
211-       }
212-     }
213-   }
214- 
215-   Widget  _getEmojiWidget (ReactionWithVotes  reaction) {
216-     final  key =  '${reaction .reactionType }_${reaction .emojiCode }' ;
217-     return  _emojiCache[key]! ;
218-   }
219- 
220-   Widget  _buildEmojiButton (ReactionWithVotes  reaction) {
221-     final  isSelected =  _selectedReaction ==  reaction;
222-     final  reactionTheme =  EmojiReactionTheme .of (context);
223- 
224-     return  Material (
225-       color:  Colors .transparent,
226-       child:  InkWell (
227-         onTap:  () {
228-           setState (() {
229-             _selectedReaction =  reaction;
230-           });
231-         },
232-         child:  Padding (
233-           padding:  const  EdgeInsets .symmetric (horizontal:  3 , vertical:  4 ),
234-           child:  Column (
235-             mainAxisSize:  MainAxisSize .min,
236-             children:  [
237-               Container (
238-                 padding:  const  EdgeInsets .symmetric (horizontal:  12 , vertical:  6 ),
239-                 decoration:  BoxDecoration (
240-                   color:  isSelected ?  reactionTheme.bgSelected.withValues (alpha:  0.1 ) :  Colors .transparent,
241-                   borderRadius:  BorderRadius .circular (20 ),
242-                 ),
243-                 child:  Row (
244-                   mainAxisSize:  MainAxisSize .min,
245-                   children:  [
246-                     _getEmojiWidget (reaction),
247-                     const  SizedBox (width:  4 ),
248-                     Text (
249-                       reaction.userIds.length.toString (),
250-                       style:  TextStyle (
251-                         fontSize:  14 ,
252-                         fontWeight:  isSelected ?  FontWeight .bold :  FontWeight .normal,
253-                         color:  isSelected ?  reactionTheme.textSelected :  reactionTheme.textUnselected,
254-                       ),
255-                     ),
256-                   ],
257-                 ),
258-               ),
259-               AnimatedContainer (
260-                 duration:  const  Duration (milliseconds:  300 ),
261-                 margin:  const  EdgeInsets .only (top:  4 ),
262-                 height:  2 ,
263-                 width:  isSelected ?  20  :  0 ,
264-                 decoration:  BoxDecoration (
265-                   color:  isSelected ?  reactionTheme.textSelected :  Colors .transparent,
266-                   borderRadius:  BorderRadius .circular (1 ),
267-                 ),
268-               ),
269-             ],
270-           ),
271-         ),
272-       ),
273-     );
274-   }
275- 
276-   Widget  _buildAllButton () {
277-     final  reactionTheme =  EmojiReactionTheme .of (context);
278-     final  isSelected =  _selectedReaction ==  null ;
279- 
280-     return  Material (
281-       color:  Colors .transparent,
282-       child:  InkWell (
283-         onTap:  () {
284-           setState (() {
285-             _selectedReaction =  null ;
286-           });
287-         },
288-         child:  Padding (
289-           padding:  const  EdgeInsets .symmetric (horizontal:  3 , vertical:  4 ),
290-           child:  Column (
291-             mainAxisSize:  MainAxisSize .min,
292-             children:  [
293-               Container (
294-                 padding:  const  EdgeInsets .symmetric (horizontal:  12 , vertical:  6 ),
295-                 decoration:  BoxDecoration (
296-                   color:  isSelected ?  reactionTheme.bgSelected.withValues (alpha:  0.1 ) :  Colors .transparent,
297-                   borderRadius:  BorderRadius .circular (20 ),
298-                 ),
299-                 child:  Text (
300-                   'All ${widget .reactions .total }' ,
301-                   style:  TextStyle (
302-                     fontSize:  14 ,
303-                     fontWeight:  isSelected ?  FontWeight .bold :  FontWeight .normal,
304-                     color:  isSelected ?  reactionTheme.textSelected :  reactionTheme.textUnselected,
305-                   ),
306-                 ),
307-               ),
308-               AnimatedContainer (
309-                 duration:  const  Duration (milliseconds:  300 ),
310-                 margin:  const  EdgeInsets .only (top:  4 ),
311-                 height:  2 ,
312-                 width:  isSelected ?  20  :  0 ,
313-                 decoration:  BoxDecoration (
314-                   color:  isSelected ?  reactionTheme.textSelected :  Colors .transparent,
315-                   borderRadius:  BorderRadius .circular (1 ),
316-                 ),
317-               ),
318-             ],
319-           ),
320-         ),
321-       ),
322-     );
323-   }
324- 
325-   List <({String  name, Widget  emoji, int  userId})> _getUserNamesWithEmojis () {
326-     if  (_selectedReaction ==  null ) {
327-       // Show all users when "All" is selected 
328-       final  allUserReactions =  < ({String  name, Widget  emoji, int  userId})> [];
329- 
330-       for  (final  reaction in  widget.reactions.aggregated) {
331-         // Add each user-reaction combination separately 
332-         for  (final  userId in  reaction.userIds) {
333-           allUserReactions.add ((
334-             name:  widget.store.users[userId]? .fullName ??  '(unknown user)' ,
335-             emoji:  _getEmojiWidget (reaction),
336-             userId:  userId,
337-           ));
338-         }
339-       }
340- 
341-       // Sort by name to group the same user's reactions together 
342-       return  allUserReactions..sort ((a, b) =>  a.name.compareTo (b.name));
343-     } else  {
344-       // Show users for selected reaction 
345-       return  _selectedReaction! .userIds.map ((userId) =>  (
346-         name:  widget.store.users[userId]? .fullName ??  '(unknown user)' ,
347-         emoji:  _getEmojiWidget (_selectedReaction! ),
348-         userId:  userId,
349-       )).toList ()..sort ((a, b) =>  a.name.compareTo (b.name));
350-     }
351-   }
352- 
353-   @override 
354-   Widget  build (BuildContext  context) {
355-     final  users =  _getUserNamesWithEmojis ();
356- 
357-     return  SafeArea (
358-       child:  Column (
359-         mainAxisSize:  MainAxisSize .min,
360-         crossAxisAlignment:  CrossAxisAlignment .start,
361-         children:  [
362-           Padding (
363-             padding:  const  EdgeInsets .all (16 ),
364-             child:  SingleChildScrollView (
365-               scrollDirection:  Axis .horizontal,
366-               child:  Row (
367-                 children:  [
368-                   _buildAllButton (),
369-                   ...widget.reactions.aggregated.map ((reaction) =>  _buildEmojiButton (reaction)),
370-                 ],
371-               ),
372-             ),
373-           ),
374-           Flexible (
375-             child:  ListView .builder (
376-               shrinkWrap:  true ,
377-               itemCount:  users.length,
378-               itemBuilder:  (context, index) =>  InkWell (
379-                 onTap:  () =>  Navigator .push (context,
380-                   ProfilePage .buildRoute (context:  context,
381-                     userId:  users[index].userId)),
382-                 child:  ListTile (
383-                   leading:  Avatar (
384-                     size:  32 ,
385-                     borderRadius:  3 ,
386-                     userId:  users[index].userId,
387-                   ),
388-                   title:  Row (
389-                     children:  [
390-                       Expanded (child:  Text (users[index].name)),
391-                       users[index].emoji,
392-                     ],
393-                   ),
394-                 ),
395-               ),
396-             ),
397-           ),
398-         ],
399-       ),
400-     );
401-   }
402- }
403- 
404156class  ReactionChip  extends  StatelessWidget  {
405157  final  bool  showName;
406158  final  int  messageId;
0 commit comments