💄 Optimize attachment list
This commit is contained in:
		| @@ -281,11 +281,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> { | ||||
|                 Expanded( | ||||
|                   child: InfiniteList( | ||||
|                     reverse: true, | ||||
|                     padding: const EdgeInsets.only( | ||||
|                       left: 12, | ||||
|                       right: 12, | ||||
|                       top: 12, | ||||
|                     ), | ||||
|                     padding: const EdgeInsets.only(top: 12), | ||||
|                     hasReachedMax: _messageController.isAllLoaded, | ||||
|                     itemCount: _messageController.messages.length, | ||||
|                     isLoading: _messageController.isLoading, | ||||
| @@ -311,23 +307,20 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> { | ||||
|  | ||||
|                       return Align( | ||||
|                         alignment: Alignment.centerLeft, | ||||
|                         child: Container( | ||||
|                           constraints: BoxConstraints(maxWidth: 480), | ||||
|                           child: ChatMessage( | ||||
|                             data: message, | ||||
|                             isMerged: canMerge, | ||||
|                             hasMerged: canMergePrevious, | ||||
|                             isPending: _messageController.unconfirmedMessages.contains(message.uuid), | ||||
|                             onReply: (value) { | ||||
|                               _inputGlobalKey.currentState?.setReply(value); | ||||
|                             }, | ||||
|                             onEdit: (value) { | ||||
|                               _inputGlobalKey.currentState?.setEdit(value); | ||||
|                             }, | ||||
|                             onDelete: (value) { | ||||
|                               _inputGlobalKey.currentState?.deleteMessage(value); | ||||
|                             }, | ||||
|                           ), | ||||
|                         child: ChatMessage( | ||||
|                           data: message, | ||||
|                           isMerged: canMerge, | ||||
|                           hasMerged: canMergePrevious, | ||||
|                           isPending: _messageController.unconfirmedMessages.contains(message.uuid), | ||||
|                           onReply: (value) { | ||||
|                             _inputGlobalKey.currentState?.setReply(value); | ||||
|                           }, | ||||
|                           onEdit: (value) { | ||||
|                             _inputGlobalKey.currentState?.setEdit(value); | ||||
|                           }, | ||||
|                           onDelete: (value) { | ||||
|                             _inputGlobalKey.currentState?.deleteMessage(value); | ||||
|                           }, | ||||
|                         ), | ||||
|                       ); | ||||
|                     }, | ||||
|   | ||||
| @@ -153,9 +153,14 @@ class _HomeDashUpdateWidget extends StatelessWidget { | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _HomeDashSpecialDayWidget extends StatelessWidget { | ||||
| class _HomeDashSpecialDayWidget extends StatefulWidget { | ||||
|   const _HomeDashSpecialDayWidget(); | ||||
|  | ||||
|   @override | ||||
|   State<_HomeDashSpecialDayWidget> createState() => _HomeDashSpecialDayWidgetState(); | ||||
| } | ||||
|  | ||||
| class _HomeDashSpecialDayWidgetState extends State<_HomeDashSpecialDayWidget> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final ua = context.watch<UserProvider>(); | ||||
| @@ -165,21 +170,20 @@ class _HomeDashSpecialDayWidget extends StatelessWidget { | ||||
|  | ||||
|     if (days.isNotEmpty) { | ||||
|       return Column( | ||||
|           spacing: 8, | ||||
|           children: days.map((ele) { | ||||
|             return Card( | ||||
|               child: ListTile( | ||||
|                 leading: Text(kSpecialDaysSymbol[ele] ?? '🎉').fontSize(24), | ||||
|                 title: Text('celebrate$ele').tr(args: [ua.user?.nick ?? 'user']), | ||||
|                 subtitle: Text( | ||||
|                   DateFormat('y/M/d').format(DateTime.now().copyWith( | ||||
|                     month: kSpecialDays[ele]?.$1, | ||||
|                     day: kSpecialDays[ele]?.$2, | ||||
|                   )), | ||||
|                 ), | ||||
|               ), | ||||
|             ).padding(bottom: 8); | ||||
|           }).toList()); | ||||
|         return Card( | ||||
|           child: ListTile( | ||||
|             leading: Text(kSpecialDaysSymbol[ele] ?? '🎉').fontSize(24), | ||||
|             title: Text('celebrate$ele').tr(args: [ua.user?.nick ?? 'user']), | ||||
|             subtitle: Text( | ||||
|               DateFormat('y/M/d').format(DateTime.now().copyWith( | ||||
|                 month: kSpecialDays[ele]?.$1, | ||||
|                 day: kSpecialDays[ele]?.$2, | ||||
|               )), | ||||
|             ), | ||||
|           ), | ||||
|         ).padding(bottom: 8); | ||||
|       }).toList()); | ||||
|     } | ||||
|  | ||||
|     final nextOne = dayz.getNextSpecialDay(); | ||||
| @@ -204,6 +208,9 @@ class _HomeDashSpecialDayWidget extends StatelessWidget { | ||||
|                 separatorType: SeparatorType.symbol, | ||||
|                 decoration: BoxDecoration(), | ||||
|                 padding: EdgeInsets.zero, | ||||
|                 onDone: () { | ||||
|                   setState(() {}); | ||||
|                 }, | ||||
|               ), | ||||
|               const Gap(12), | ||||
|               Expanded( | ||||
|   | ||||
| @@ -106,76 +106,38 @@ class _AttachmentListState extends State<AttachmentList> { | ||||
|         } | ||||
|  | ||||
|         if (widget.gridded) { | ||||
|           return Padding( | ||||
|             padding: widget.padding ?? EdgeInsets.zero, | ||||
|             child: Container( | ||||
|               decoration: BoxDecoration( | ||||
|                 color: backgroundColor, | ||||
|                 border: Border( | ||||
|                   top: borderSide, | ||||
|                   bottom: borderSide, | ||||
|                 ), | ||||
|                 borderRadius: AttachmentList.kDefaultRadius, | ||||
|               ), | ||||
|               child: ClipRRect( | ||||
|                 borderRadius: AttachmentList.kDefaultRadius, | ||||
|                 child: StaggeredGrid.count( | ||||
|                   crossAxisCount: math.min(widget.data.length, 2), | ||||
|                   crossAxisSpacing: 4, | ||||
|                   mainAxisSpacing: 4, | ||||
|                   children: widget.data | ||||
|                       .mapIndexed( | ||||
|                         (idx, ele) => GestureDetector( | ||||
|                           child: Container( | ||||
|                             constraints: constraints, | ||||
|                             child: AttachmentItem( | ||||
|                               data: ele, | ||||
|                               heroTag: heroTags[idx], | ||||
|                               fit: BoxFit.cover, | ||||
|                             ), | ||||
|                           ), | ||||
|                           onTap: () { | ||||
|                             if (widget.data[idx]!.mediaType != SnMediaType.image) return; | ||||
|                             context.pushTransparentRoute( | ||||
|                               AttachmentZoomView( | ||||
|                                 data: widget.data.where((ele) => ele != null).cast(), | ||||
|                                 initialIndex: idx, | ||||
|                                 heroTags: heroTags, | ||||
|                               ), | ||||
|                               backgroundColor: Colors.black.withOpacity(0.7), | ||||
|                               rootNavigator: true, | ||||
|                             ); | ||||
|                           }, | ||||
|                         ), | ||||
|                       ) | ||||
|                       .toList(), | ||||
|                 ), | ||||
|           return Container( | ||||
|             margin: widget.padding ?? EdgeInsets.zero, | ||||
|             decoration: BoxDecoration( | ||||
|               color: backgroundColor, | ||||
|               border: Border( | ||||
|                 top: borderSide, | ||||
|                 bottom: borderSide, | ||||
|               ), | ||||
|               borderRadius: AttachmentList.kDefaultRadius, | ||||
|             ), | ||||
|           ); | ||||
|         } | ||||
|  | ||||
|         return AspectRatio( | ||||
|           aspectRatio: (widget.data.firstOrNull?.data['ratio'] ?? 1).toDouble(), | ||||
|           child: Container( | ||||
|             constraints: BoxConstraints(maxHeight: constraints.maxHeight), | ||||
|             child: ScrollConfiguration( | ||||
|               behavior: _AttachmentListScrollBehavior(), | ||||
|               child: ListView.separated( | ||||
|                 shrinkWrap: true, | ||||
|                 itemCount: widget.data.length, | ||||
|                 itemBuilder: (context, idx) { | ||||
|                   return Container( | ||||
|                     constraints: constraints, | ||||
|                     child: AspectRatio( | ||||
|                       aspectRatio: (widget.data[idx]?.data['ratio'] ?? 1).toDouble(), | ||||
|                       child: GestureDetector( | ||||
|             child: ClipRRect( | ||||
|               borderRadius: AttachmentList.kDefaultRadius, | ||||
|               child: StaggeredGrid.count( | ||||
|                 crossAxisCount: math.min(widget.data.length, 2), | ||||
|                 crossAxisSpacing: 4, | ||||
|                 mainAxisSpacing: 4, | ||||
|                 children: widget.data | ||||
|                     .mapIndexed( | ||||
|                       (idx, ele) => GestureDetector( | ||||
|                         child: Container( | ||||
|                           constraints: constraints, | ||||
|                           child: AttachmentItem( | ||||
|                             data: ele, | ||||
|                             heroTag: heroTags[idx], | ||||
|                             fit: BoxFit.cover, | ||||
|                           ), | ||||
|                         ), | ||||
|                         onTap: () { | ||||
|                           if (widget.data[idx]?.mediaType != SnMediaType.image) return; | ||||
|                           if (widget.data[idx]!.mediaType != SnMediaType.image) return; | ||||
|                           context.pushTransparentRoute( | ||||
|                             AttachmentZoomView( | ||||
|                               data: | ||||
|                                   widget.data.where((ele) => ele != null && ele.mediaType == SnMediaType.image).cast(), | ||||
|                               data: widget.data.where((ele) => ele != null).cast(), | ||||
|                               initialIndex: idx, | ||||
|                               heroTags: heroTags, | ||||
|                             ), | ||||
| @@ -183,44 +145,76 @@ class _AttachmentListState extends State<AttachmentList> { | ||||
|                             rootNavigator: true, | ||||
|                           ); | ||||
|                         }, | ||||
|                         child: Stack( | ||||
|                           fit: StackFit.expand, | ||||
|                           children: [ | ||||
|                             Container( | ||||
|                               decoration: BoxDecoration( | ||||
|                                 color: backgroundColor, | ||||
|                                 border: Border( | ||||
|                                   top: borderSide, | ||||
|                                   bottom: borderSide, | ||||
|                                 ), | ||||
|                                 borderRadius: AttachmentList.kDefaultRadius, | ||||
|                       ), | ||||
|                     ) | ||||
|                     .toList(), | ||||
|               ), | ||||
|             ), | ||||
|           ); | ||||
|         } | ||||
|  | ||||
|         return Container( | ||||
|           constraints: BoxConstraints(maxHeight: constraints.maxHeight), | ||||
|           child: ScrollConfiguration( | ||||
|             behavior: _AttachmentListScrollBehavior(), | ||||
|             child: ListView.separated( | ||||
|               padding: widget.padding, | ||||
|               shrinkWrap: true, | ||||
|               itemCount: widget.data.length, | ||||
|               itemBuilder: (context, idx) { | ||||
|                 return Container( | ||||
|                   constraints: constraints.copyWith(maxWidth: widget.maxWidth), | ||||
|                   child: AspectRatio( | ||||
|                     aspectRatio: (widget.data[idx]?.data['ratio'] ?? 1).toDouble(), | ||||
|                     child: GestureDetector( | ||||
|                       onTap: () { | ||||
|                         if (widget.data[idx]?.mediaType != SnMediaType.image) return; | ||||
|                         context.pushTransparentRoute( | ||||
|                           AttachmentZoomView( | ||||
|                             data: widget.data.where((ele) => ele != null && ele.mediaType == SnMediaType.image).cast(), | ||||
|                             initialIndex: idx, | ||||
|                             heroTags: heroTags, | ||||
|                           ), | ||||
|                           backgroundColor: Colors.black.withOpacity(0.7), | ||||
|                           rootNavigator: true, | ||||
|                         ); | ||||
|                       }, | ||||
|                       child: Stack( | ||||
|                         fit: StackFit.expand, | ||||
|                         children: [ | ||||
|                           Container( | ||||
|                             decoration: BoxDecoration( | ||||
|                               color: backgroundColor, | ||||
|                               border: Border( | ||||
|                                 top: borderSide, | ||||
|                                 bottom: borderSide, | ||||
|                               ), | ||||
|                               child: ClipRRect( | ||||
|                                 borderRadius: AttachmentList.kDefaultRadius, | ||||
|                                 child: AttachmentItem( | ||||
|                                   data: widget.data[idx], | ||||
|                                   heroTag: heroTags[idx], | ||||
|                                 ), | ||||
|                               borderRadius: AttachmentList.kDefaultRadius, | ||||
|                             ), | ||||
|                             child: ClipRRect( | ||||
|                               borderRadius: AttachmentList.kDefaultRadius, | ||||
|                               child: AttachmentItem( | ||||
|                                 data: widget.data[idx], | ||||
|                                 heroTag: heroTags[idx], | ||||
|                               ), | ||||
|                             ), | ||||
|                             Positioned( | ||||
|                               right: 8, | ||||
|                               bottom: 8, | ||||
|                               child: Chip( | ||||
|                                 label: Text('${idx + 1}/${widget.data.length}'), | ||||
|                               ), | ||||
|                           ), | ||||
|                           Positioned( | ||||
|                             right: 8, | ||||
|                             bottom: 8, | ||||
|                             child: Chip( | ||||
|                               label: Text('${idx + 1}/${widget.data.length}'), | ||||
|                             ), | ||||
|                           ], | ||||
|                         ), | ||||
|                           ), | ||||
|                         ], | ||||
|                       ), | ||||
|                     ), | ||||
|                   ); | ||||
|                 }, | ||||
|                 separatorBuilder: (context, index) => const Gap(8), | ||||
|                 padding: widget.padding, | ||||
|                 physics: const BouncingScrollPhysics(), | ||||
|                 scrollDirection: Axis.horizontal, | ||||
|               ), | ||||
|                   ), | ||||
|                 ); | ||||
|               }, | ||||
|               separatorBuilder: (context, index) => const Gap(8), | ||||
|               physics: const BouncingScrollPhysics(), | ||||
|               scrollDirection: Axis.horizontal, | ||||
|             ), | ||||
|           ), | ||||
|         ); | ||||
|   | ||||
| @@ -231,7 +231,7 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> { | ||||
|                           children: [ | ||||
|                             IgnorePointer( | ||||
|                               child: AccountImage( | ||||
|                                 content: account!.avatar, | ||||
|                                 content: account?.avatar, | ||||
|                                 radius: 19, | ||||
|                               ), | ||||
|                             ), | ||||
| @@ -246,7 +246,7 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> { | ||||
|                                       style: Theme.of(context).textTheme.bodySmall, | ||||
|                                     ), | ||||
|                                     Text( | ||||
|                                       account.nick, | ||||
|                                       account?.nick ?? 'unknown'.tr(), | ||||
|                                       style: Theme.of(context).textTheme.bodyMedium, | ||||
|                                     ), | ||||
|                                   ], | ||||
|   | ||||
| @@ -24,6 +24,7 @@ class ChatMessage extends StatelessWidget { | ||||
|   final Function(SnChatMessage)? onReply; | ||||
|   final Function(SnChatMessage)? onEdit; | ||||
|   final Function(SnChatMessage)? onDelete; | ||||
|   final EdgeInsets padding; | ||||
|  | ||||
|   const ChatMessage({ | ||||
|     super.key, | ||||
| @@ -35,6 +36,7 @@ class ChatMessage extends StatelessWidget { | ||||
|     this.onReply, | ||||
|     this.onEdit, | ||||
|     this.onDelete, | ||||
|     this.padding = const EdgeInsets.only(left: 12, right: 12), | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
| @@ -87,83 +89,89 @@ class ChatMessage extends StatelessWidget { | ||||
|         child: Column( | ||||
|           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|           children: [ | ||||
|             Row( | ||||
|               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|               children: [ | ||||
|                 if (!isMerged && !isCompact) | ||||
|                   AccountImage( | ||||
|                     content: user?.avatar, | ||||
|                   ) | ||||
|                 else if (isMerged) | ||||
|                   const Gap(40), | ||||
|                 const Gap(8), | ||||
|                 Expanded( | ||||
|                   child: Column( | ||||
|                     crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                     children: [ | ||||
|                       if (!isMerged) | ||||
|                         Row( | ||||
|                           crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                           children: [ | ||||
|                             if (isCompact) | ||||
|                               AccountImage( | ||||
|                                 content: user?.avatar, | ||||
|                                 radius: 12, | ||||
|                               ).padding(right: 8), | ||||
|                             Text( | ||||
|                               (data.sender.nick?.isNotEmpty ?? false) ? data.sender.nick! : user?.nick ?? 'unknown', | ||||
|                             ).bold(), | ||||
|                             const Gap(8), | ||||
|                             Text( | ||||
|                               dateFormatter.format(data.createdAt.toLocal()), | ||||
|                             ).fontSize(13), | ||||
|                           ], | ||||
|                         ), | ||||
|                       if (isCompact) const Gap(4), | ||||
|                       if (data.preload?.quoteEvent != null) | ||||
|                         StyledWidget(Container( | ||||
|                           decoration: BoxDecoration( | ||||
|                             borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|                             border: Border.all( | ||||
|                               color: Theme.of(context).dividerColor, | ||||
|                               width: 1, | ||||
|             Padding( | ||||
|               padding: isCompact ? EdgeInsets.zero : padding, | ||||
|               child: Row( | ||||
|                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                 children: [ | ||||
|                   if (!isMerged && !isCompact) | ||||
|                     AccountImage( | ||||
|                       content: user?.avatar, | ||||
|                     ) | ||||
|                   else if (isMerged) | ||||
|                     const Gap(40), | ||||
|                   const Gap(8), | ||||
|                   Expanded( | ||||
|                     child: Container( | ||||
|                       constraints: BoxConstraints(maxWidth: 480), | ||||
|                       child: Column( | ||||
|                         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                         children: [ | ||||
|                           if (!isMerged) | ||||
|                             Row( | ||||
|                               crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                               children: [ | ||||
|                                 if (isCompact) | ||||
|                                   AccountImage( | ||||
|                                     content: user?.avatar, | ||||
|                                     radius: 12, | ||||
|                                   ).padding(right: 8), | ||||
|                                 Text( | ||||
|                                   (data.sender.nick?.isNotEmpty ?? false) ? data.sender.nick! : user?.nick ?? 'unknown', | ||||
|                                 ).bold(), | ||||
|                                 const Gap(8), | ||||
|                                 Text( | ||||
|                                   dateFormatter.format(data.createdAt.toLocal()), | ||||
|                                 ).fontSize(13), | ||||
|                               ], | ||||
|                             ), | ||||
|                           ), | ||||
|                           padding: const EdgeInsets.only( | ||||
|                             left: 4, | ||||
|                             right: 4, | ||||
|                             top: 8, | ||||
|                             bottom: 6, | ||||
|                           ), | ||||
|                           child: ChatMessage( | ||||
|                             data: data.preload!.quoteEvent!, | ||||
|                             isCompact: true, | ||||
|                             onReply: onReply, | ||||
|                             onEdit: onEdit, | ||||
|                             onDelete: onDelete, | ||||
|                           ), | ||||
|                         )).padding(bottom: 4, top: 4), | ||||
|                       switch (data.type) { | ||||
|                         'messages.new' => _ChatMessageText(data: data), | ||||
|                         _ => _ChatMessageSystemNotify(data: data), | ||||
|                       }, | ||||
|                     ], | ||||
|                   ), | ||||
|                 ) | ||||
|               ], | ||||
|             ).opacity(isPending ? 0.5 : 1), | ||||
|                           if (isCompact) const Gap(8), | ||||
|                           if (data.preload?.quoteEvent != null) | ||||
|                             StyledWidget(Container( | ||||
|                               decoration: BoxDecoration( | ||||
|                                 borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|                                 border: Border.all( | ||||
|                                   color: Theme.of(context).dividerColor, | ||||
|                                   width: 1, | ||||
|                                 ), | ||||
|                               ), | ||||
|                               padding: const EdgeInsets.only( | ||||
|                                 left: 4, | ||||
|                                 right: 4, | ||||
|                                 top: 8, | ||||
|                                 bottom: 6, | ||||
|                               ), | ||||
|                               child: ChatMessage( | ||||
|                                 data: data.preload!.quoteEvent!, | ||||
|                                 isCompact: true, | ||||
|                                 onReply: onReply, | ||||
|                                 onEdit: onEdit, | ||||
|                                 onDelete: onDelete, | ||||
|                               ), | ||||
|                             )).padding(bottom: 4, top: 4), | ||||
|                           switch (data.type) { | ||||
|                             'messages.new' => _ChatMessageText(data: data), | ||||
|                             _ => _ChatMessageSystemNotify(data: data), | ||||
|                           }, | ||||
|                         ], | ||||
|                       ), | ||||
|                     ), | ||||
|                   ) | ||||
|                 ], | ||||
|               ).opacity(isPending ? 0.5 : 1), | ||||
|             ), | ||||
|             if (data.body['text'] != null && data.type == 'messages.new' && (data.body['text']?.isNotEmpty ?? false)) | ||||
|               LinkPreviewWidget(text: data.body['text']!), | ||||
|             if (data.preload?.attachments?.isNotEmpty ?? false) | ||||
|               AttachmentList( | ||||
|                 data: data.preload!.attachments!, | ||||
|                 bordered: true, | ||||
|                 // gridded: true, | ||||
|                 maxHeight: 560, | ||||
|                 maxWidth: 480, | ||||
|                 minWidth: 480, | ||||
|                 padding: const EdgeInsets.only(top: 8), | ||||
|                 padding: padding.copyWith(top: 8), | ||||
|               ), | ||||
|             if (!hasMerged && !isCompact) const Gap(12) else if (!isCompact) const Gap(6), | ||||
|             if (!hasMerged && !isCompact) const Gap(12) else if (!isCompact) const Gap(8), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|   | ||||
		Reference in New Issue
	
	Block a user