♻️ Better attachment list & zoom view
This commit is contained in:
		| @@ -351,5 +351,7 @@ | |||||||
|   "friendRequestAccept": "Accept", |   "friendRequestAccept": "Accept", | ||||||
|   "friendRequestDecline": "Decline", |   "friendRequestDecline": "Decline", | ||||||
|   "subscribe": "Subscribe", |   "subscribe": "Subscribe", | ||||||
|   "unsubscribe": "Unsubscribe" |   "unsubscribe": "Unsubscribe", | ||||||
|  |   "attachmentUploadBy": "Upload by", | ||||||
|  |   "attachmentShotOn": "Shot on {}" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -351,5 +351,7 @@ | |||||||
|   "friendRequestAccept": "接受", |   "friendRequestAccept": "接受", | ||||||
|   "friendRequestDecline": "拒绝", |   "friendRequestDecline": "拒绝", | ||||||
|   "subscribe": "订阅", |   "subscribe": "订阅", | ||||||
|   "unsubscribe": "取消订阅" |   "unsubscribe": "取消订阅", | ||||||
|  |   "attachmentUploadBy": "上传者", | ||||||
|  |   "attachmentShotOn": "由 {} 拍摄" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -81,9 +81,9 @@ class SolianApp extends StatelessWidget { | |||||||
|  |  | ||||||
|             // Data layer |             // Data layer | ||||||
|             Provider(create: (_) => SnNetworkProvider()), |             Provider(create: (_) => SnNetworkProvider()), | ||||||
|  |             Provider(create: (ctx) => UserDirectoryProvider(ctx)), | ||||||
|             Provider(create: (ctx) => SnAttachmentProvider(ctx)), |             Provider(create: (ctx) => SnAttachmentProvider(ctx)), | ||||||
|             Provider(create: (ctx) => SnPostContentProvider(ctx)), |             Provider(create: (ctx) => SnPostContentProvider(ctx)), | ||||||
|             Provider(create: (ctx) => UserDirectoryProvider(ctx)), |  | ||||||
|             Provider(create: (ctx) => SnRelationshipProvider(ctx)), |             Provider(create: (ctx) => SnRelationshipProvider(ctx)), | ||||||
|             ChangeNotifierProvider(create: (ctx) => UserProvider(ctx)), |             ChangeNotifierProvider(create: (ctx) => UserProvider(ctx)), | ||||||
|             ChangeNotifierProvider(create: (ctx) => WebSocketProvider(ctx)), |             ChangeNotifierProvider(create: (ctx) => WebSocketProvider(ctx)), | ||||||
|   | |||||||
| @@ -2,14 +2,17 @@ import 'package:flutter/material.dart'; | |||||||
| import 'package:provider/provider.dart'; | import 'package:provider/provider.dart'; | ||||||
| import 'package:surface/providers/sn_attachment.dart'; | import 'package:surface/providers/sn_attachment.dart'; | ||||||
| import 'package:surface/providers/sn_network.dart'; | import 'package:surface/providers/sn_network.dart'; | ||||||
|  | import 'package:surface/providers/user_directory.dart'; | ||||||
| import 'package:surface/types/post.dart'; | import 'package:surface/types/post.dart'; | ||||||
|  |  | ||||||
| class SnPostContentProvider { | class SnPostContentProvider { | ||||||
|   late final SnNetworkProvider _sn; |   late final SnNetworkProvider _sn; | ||||||
|  |   late final UserDirectoryProvider _ud; | ||||||
|   late final SnAttachmentProvider _attach; |   late final SnAttachmentProvider _attach; | ||||||
|  |  | ||||||
|   SnPostContentProvider(BuildContext context) { |   SnPostContentProvider(BuildContext context) { | ||||||
|     _sn = context.read<SnNetworkProvider>(); |     _sn = context.read<SnNetworkProvider>(); | ||||||
|  |     _ud = context.read<UserDirectoryProvider>(); | ||||||
|     _attach = context.read<SnAttachmentProvider>(); |     _attach = context.read<SnAttachmentProvider>(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -37,6 +40,13 @@ class SnPostContentProvider { | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     await _ud.listAccount( | ||||||
|  |       attachments | ||||||
|  |           .where((ele) => ele != null) | ||||||
|  |           .map((ele) => ele!.accountId) | ||||||
|  |           .toSet(), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|     return out; |     return out; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,10 +1,16 @@ | |||||||
| import 'package:dismissible_page/dismissible_page.dart'; | import 'package:dismissible_page/dismissible_page.dart'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:google_fonts/google_fonts.dart'; | ||||||
| import 'package:photo_view/photo_view.dart'; | import 'package:photo_view/photo_view.dart'; | ||||||
| import 'package:photo_view/photo_view_gallery.dart'; | import 'package:photo_view/photo_view_gallery.dart'; | ||||||
| import 'package:provider/provider.dart'; | import 'package:provider/provider.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
| import 'package:surface/providers/sn_network.dart'; | import 'package:surface/providers/sn_network.dart'; | ||||||
|  | import 'package:surface/providers/user_directory.dart'; | ||||||
| import 'package:surface/types/attachment.dart'; | import 'package:surface/types/attachment.dart'; | ||||||
|  | import 'package:surface/widgets/account/account_image.dart'; | ||||||
| import 'package:surface/widgets/universal_image.dart'; | import 'package:surface/widgets/universal_image.dart'; | ||||||
| import 'package:uuid/uuid.dart'; | import 'package:uuid/uuid.dart'; | ||||||
|  |  | ||||||
| @@ -27,17 +33,37 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> { | |||||||
|   late final PageController _pageController = |   late final PageController _pageController = | ||||||
|       PageController(initialPage: widget.initialIndex ?? 0); |       PageController(initialPage: widget.initialIndex ?? 0); | ||||||
|  |  | ||||||
|  |   void _updatePage() { | ||||||
|  |     setState(() {}); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void initState() { | ||||||
|  |     super.initState(); | ||||||
|  |     _pageController.addListener(_updatePage); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   void dispose() { |   void dispose() { | ||||||
|  |     _pageController.removeListener(_updatePage); | ||||||
|     _pageController.dispose(); |     _pageController.dispose(); | ||||||
|     super.dispose(); |     super.dispose(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   Color get _unFocusColor => | ||||||
|  |       Theme.of(context).colorScheme.onSurface.withOpacity(0.75); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     final sn = context.read<SnNetworkProvider>(); |     final sn = context.read<SnNetworkProvider>(); | ||||||
|     final uuid = Uuid(); |     final uuid = Uuid(); | ||||||
|  |  | ||||||
|  |     final metaTextStyle = GoogleFonts.roboto( | ||||||
|  |       fontSize: 12, | ||||||
|  |       color: _unFocusColor, | ||||||
|  |       height: 1, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|     return DismissiblePage( |     return DismissiblePage( | ||||||
|       onDismissed: () { |       onDismissed: () { | ||||||
|         Navigator.of(context).pop(); |         Navigator.of(context).pop(); | ||||||
| @@ -45,51 +71,212 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> { | |||||||
|       direction: DismissiblePageDismissDirection.down, |       direction: DismissiblePageDismissDirection.down, | ||||||
|       backgroundColor: Colors.transparent, |       backgroundColor: Colors.transparent, | ||||||
|       isFullScreen: true, |       isFullScreen: true, | ||||||
|       child: Builder(builder: (context) { |       child: Stack( | ||||||
|         if (widget.data.length == 1) { |         children: [ | ||||||
|           final heroTag = widget.heroTags?.first ?? uuid.v4(); |           Builder(builder: (context) { | ||||||
|           return Hero( |             if (widget.data.length == 1) { | ||||||
|             tag: 'attachment-${widget.data.first.rid}-$heroTag', |               final heroTag = widget.heroTags?.first ?? uuid.v4(); | ||||||
|             child: PhotoView( |               return Hero( | ||||||
|               key: Key('attachment-detail-${widget.data.first.rid}-$heroTag'), |  | ||||||
|               backgroundDecoration: BoxDecoration(color: Colors.transparent), |  | ||||||
|               imageProvider: UniversalImage.provider( |  | ||||||
|                 sn.getAttachmentUrl(widget.data.first.rid), |  | ||||||
|               ), |  | ||||||
|             ), |  | ||||||
|           ); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return PhotoViewGallery.builder( |  | ||||||
|           pageController: _pageController, |  | ||||||
|           scrollPhysics: const BouncingScrollPhysics(), |  | ||||||
|           builder: (context, idx) { |  | ||||||
|             final heroTag = widget.heroTags?.elementAt(idx) ?? uuid.v4(); |  | ||||||
|             return PhotoViewGalleryPageOptions( |  | ||||||
|               imageProvider: UniversalImage.provider( |  | ||||||
|                 sn.getAttachmentUrl(widget.data.elementAt(idx).rid), |  | ||||||
|               ), |  | ||||||
|               heroAttributes: PhotoViewHeroAttributes( |  | ||||||
|                 tag: 'attachment-${widget.data.first.rid}-$heroTag', |                 tag: 'attachment-${widget.data.first.rid}-$heroTag', | ||||||
|  |                 child: PhotoView( | ||||||
|  |                   key: Key( | ||||||
|  |                       'attachment-detail-${widget.data.first.rid}-$heroTag'), | ||||||
|  |                   backgroundDecoration: | ||||||
|  |                       BoxDecoration(color: Colors.transparent), | ||||||
|  |                   imageProvider: UniversalImage.provider( | ||||||
|  |                     sn.getAttachmentUrl(widget.data.first.rid), | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |               ); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return PhotoViewGallery.builder( | ||||||
|  |               pageController: _pageController, | ||||||
|  |               scrollPhysics: const BouncingScrollPhysics(), | ||||||
|  |               builder: (context, idx) { | ||||||
|  |                 final heroTag = widget.heroTags?.elementAt(idx) ?? uuid.v4(); | ||||||
|  |                 return PhotoViewGalleryPageOptions( | ||||||
|  |                   imageProvider: UniversalImage.provider( | ||||||
|  |                     sn.getAttachmentUrl(widget.data.elementAt(idx).rid), | ||||||
|  |                   ), | ||||||
|  |                   heroAttributes: PhotoViewHeroAttributes( | ||||||
|  |                     tag: 'attachment-${widget.data.first.rid}-$heroTag', | ||||||
|  |                   ), | ||||||
|  |                 ); | ||||||
|  |               }, | ||||||
|  |               itemCount: widget.data.length, | ||||||
|  |               loadingBuilder: (context, event) => Center( | ||||||
|  |                 child: SizedBox( | ||||||
|  |                   width: 20.0, | ||||||
|  |                   height: 20.0, | ||||||
|  |                   child: CircularProgressIndicator( | ||||||
|  |                     value: event == null | ||||||
|  |                         ? 0 | ||||||
|  |                         : event.cumulativeBytesLoaded / | ||||||
|  |                             (event.expectedTotalBytes ?? 1), | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|               ), |               ), | ||||||
|  |               backgroundDecoration: BoxDecoration(color: Colors.transparent), | ||||||
|             ); |             ); | ||||||
|           }, |           }), | ||||||
|           itemCount: widget.data.length, |           Align( | ||||||
|           loadingBuilder: (context, event) => Center( |             alignment: Alignment.bottomCenter, | ||||||
|             child: SizedBox( |             child: IgnorePointer( | ||||||
|               width: 20.0, |               child: Container( | ||||||
|               height: 20.0, |                 height: 300, | ||||||
|               child: CircularProgressIndicator( |                 decoration: BoxDecoration( | ||||||
|                 value: event == null |                   gradient: LinearGradient( | ||||||
|                     ? 0 |                     begin: Alignment.bottomCenter, | ||||||
|                     : event.cumulativeBytesLoaded / |                     end: Alignment.topCenter, | ||||||
|                         (event.expectedTotalBytes ?? 1), |                     colors: [ | ||||||
|  |                       Theme.of(context).colorScheme.surface, | ||||||
|  |                       Colors.transparent, | ||||||
|  |                     ], | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|           backgroundDecoration: BoxDecoration(color: Colors.transparent), |           Positioned( | ||||||
|         ); |             left: 16, | ||||||
|       }), |             right: 16, | ||||||
|  |             bottom: MediaQuery.of(context).padding.bottom > 16 | ||||||
|  |                 ? -MediaQuery.of(context).padding.bottom | ||||||
|  |                 : 16, | ||||||
|  |             child: SizedBox( | ||||||
|  |               height: 180, | ||||||
|  |               child: Material( | ||||||
|  |                 color: Colors.transparent, | ||||||
|  |                 child: Builder(builder: (context) { | ||||||
|  |                   final ud = context.read<UserDirectoryProvider>(); | ||||||
|  |                   final item = widget.data.elementAt( | ||||||
|  |                     _pageController.page?.round() ?? 0, | ||||||
|  |                   ); | ||||||
|  |                   final account = ud.getAccountFromCache(item.accountId); | ||||||
|  |  | ||||||
|  |                   return Column( | ||||||
|  |                     crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                     children: [ | ||||||
|  |                       if (item.accountId > 0) | ||||||
|  |                         Row( | ||||||
|  |                           children: [ | ||||||
|  |                             IgnorePointer( | ||||||
|  |                               child: AccountImage( | ||||||
|  |                                 content: account!.avatar, | ||||||
|  |                                 radius: 19, | ||||||
|  |                               ), | ||||||
|  |                             ), | ||||||
|  |                             const Gap(8), | ||||||
|  |                             Expanded( | ||||||
|  |                               child: IgnorePointer( | ||||||
|  |                                 child: Column( | ||||||
|  |                                   crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                                   children: [ | ||||||
|  |                                     Text( | ||||||
|  |                                       'attachmentUploadBy'.tr(), | ||||||
|  |                                       style: | ||||||
|  |                                           Theme.of(context).textTheme.bodySmall, | ||||||
|  |                                     ), | ||||||
|  |                                     Text( | ||||||
|  |                                       account.nick, | ||||||
|  |                                       style: Theme.of(context) | ||||||
|  |                                           .textTheme | ||||||
|  |                                           .bodyMedium, | ||||||
|  |                                     ), | ||||||
|  |                                   ], | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                             ), | ||||||
|  |                             if (widget.data.length > 1) | ||||||
|  |                               IgnorePointer( | ||||||
|  |                                 child: Text( | ||||||
|  |                                   '${(_pageController.page?.round() ?? 0) + 1}/${widget.data.length}', | ||||||
|  |                                   style: GoogleFonts.robotoMono(fontSize: 13), | ||||||
|  |                                 ).padding(right: 8), | ||||||
|  |                               ), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                       const Gap(4), | ||||||
|  |                       IgnorePointer( | ||||||
|  |                         child: Text( | ||||||
|  |                           item.alt, | ||||||
|  |                           maxLines: 2, | ||||||
|  |                           overflow: TextOverflow.ellipsis, | ||||||
|  |                           style: const TextStyle( | ||||||
|  |                             fontSize: 15, | ||||||
|  |                             fontWeight: FontWeight.w500, | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                       const Gap(2), | ||||||
|  |                       IgnorePointer( | ||||||
|  |                         child: Wrap( | ||||||
|  |                           spacing: 6, | ||||||
|  |                           children: [ | ||||||
|  |                             if (item.metadata['exif'] == null) | ||||||
|  |                               Text( | ||||||
|  |                                 '#${item.rid}', | ||||||
|  |                                 style: metaTextStyle, | ||||||
|  |                               ), | ||||||
|  |                             if (item.metadata['exif']?['Model'] != null) | ||||||
|  |                               Text( | ||||||
|  |                                 'attachmentShotOn'.tr(args: [ | ||||||
|  |                                   item.metadata['exif']?['Model'], | ||||||
|  |                                 ]), | ||||||
|  |                                 style: metaTextStyle, | ||||||
|  |                               ).padding(right: 2), | ||||||
|  |                             if (item.metadata['exif']?['ShutterSpeed'] != null) | ||||||
|  |                               Text( | ||||||
|  |                                 item.metadata['exif']?['ShutterSpeed'], | ||||||
|  |                                 style: metaTextStyle, | ||||||
|  |                               ).padding(right: 2), | ||||||
|  |                             if (item.metadata['exif']?['ISO'] != null) | ||||||
|  |                               Text( | ||||||
|  |                                 'ISO${item.metadata['exif']?['ISO']}', | ||||||
|  |                                 style: metaTextStyle, | ||||||
|  |                               ).padding(right: 2), | ||||||
|  |                             if (item.metadata['exif']?['Aperture'] != null) | ||||||
|  |                               Text( | ||||||
|  |                                 'f/${item.metadata['exif']?['Aperture']}', | ||||||
|  |                                 style: metaTextStyle, | ||||||
|  |                               ).padding(right: 2), | ||||||
|  |                             if (item.metadata['exif']?['Megapixels'] != null && | ||||||
|  |                                 item.metadata['exif']?['Model'] != null) | ||||||
|  |                               Text( | ||||||
|  |                                 '${item.metadata['exif']?['Megapixels']}MP', | ||||||
|  |                                 style: metaTextStyle, | ||||||
|  |                               ) | ||||||
|  |                             else | ||||||
|  |                               Text( | ||||||
|  |                                 '${item.size} Bytes', | ||||||
|  |                                 style: metaTextStyle, | ||||||
|  |                               ), | ||||||
|  |                             Text( | ||||||
|  |                               '${item.metadata['width']}x${item.metadata['height']}', | ||||||
|  |                               style: metaTextStyle, | ||||||
|  |                             ), | ||||||
|  |                             if (item.metadata['ratio'] != null) | ||||||
|  |                               Text( | ||||||
|  |                                 (item.metadata['ratio'] as num) | ||||||
|  |                                     .toStringAsFixed(2), | ||||||
|  |                                 style: metaTextStyle, | ||||||
|  |                               ), | ||||||
|  |                             Text( | ||||||
|  |                               item.mimetype, | ||||||
|  |                               style: metaTextStyle, | ||||||
|  |                             ), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ], | ||||||
|  |                   ); | ||||||
|  |                 }), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -38,140 +38,141 @@ class _AttachmentListState extends State<AttachmentList> { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     final borderSide = widget.bordered |     final aspectRatio = widget.data[0]?.metadata['ratio']?.toDouble() ?? 1; | ||||||
|         ? BorderSide(width: 1, color: Theme.of(context).dividerColor) |  | ||||||
|         : BorderSide.none; |  | ||||||
|     final backgroundColor = Theme.of(context).colorScheme.surfaceContainer; |  | ||||||
|     final constraints = BoxConstraints( |  | ||||||
|       minWidth: 80, |  | ||||||
|       maxHeight: widget.maxHeight ?? double.infinity, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     if (widget.data.isEmpty) return const SizedBox.shrink(); |     return LayoutBuilder( | ||||||
|     if (widget.data.length == 1) { |       builder: (context, layoutConstraints) { | ||||||
|       return GestureDetector( |         final borderSide = widget.bordered | ||||||
|         child: Builder( |             ? BorderSide(width: 1, color: Theme.of(context).dividerColor) | ||||||
|           builder: (context) { |             : BorderSide.none; | ||||||
|             if (ResponsiveBreakpoints.of(context).largerThan(MOBILE) || |         final backgroundColor = Theme.of(context).colorScheme.surfaceContainer; | ||||||
|                 widget.noGrow) { |         final constraints = BoxConstraints( | ||||||
|               return Padding( |           minWidth: 80, | ||||||
|                 // Single child list-like displaying |           maxWidth: layoutConstraints.maxWidth - 20, | ||||||
|                 padding: widget.listPadding ?? EdgeInsets.zero, |           maxHeight: widget.maxHeight ?? double.infinity, | ||||||
|                 child: Container( |         ); | ||||||
|                   constraints: constraints, |  | ||||||
|                   decoration: BoxDecoration( |         if (widget.data.isEmpty) return const SizedBox.shrink(); | ||||||
|                     color: backgroundColor, |         if (widget.data.length == 1) { | ||||||
|                     border: Border(top: borderSide, bottom: borderSide), |           return AspectRatio( | ||||||
|                     borderRadius: AttachmentList.kDefaultRadius, |             aspectRatio: widget.data[0]?.metadata['ratio']?.toDouble() ?? | ||||||
|                   ), |                 switch (widget.data[0]?.mimetype.split('/').firstOrNull) { | ||||||
|                   child: AspectRatio( |                   'audio' => 16 / 9, | ||||||
|                     aspectRatio: widget.data[0]?.metadata['ratio'] |                   'video' => 16 / 9, | ||||||
|                             ?.toDouble() ?? |                   _ => 1, | ||||||
|                         switch ( |                 }, | ||||||
|                             widget.data[0]?.mimetype.split('/').firstOrNull) { |             child: GestureDetector( | ||||||
|                           'audio' => 16 / 9, |               child: Builder( | ||||||
|                           'video' => 16 / 9, |                 builder: (context) { | ||||||
|                           _ => 1, |                   if (ResponsiveBreakpoints.of(context).largerThan(MOBILE) || | ||||||
|                         }, |                       widget.noGrow) { | ||||||
|                     child: ClipRRect( |                     return Padding( | ||||||
|                       borderRadius: AttachmentList.kDefaultRadius, |                       // Single child list-like displaying | ||||||
|                       child: AttachmentItem( |                       padding: widget.listPadding ?? EdgeInsets.zero, | ||||||
|                         data: widget.data[0], |                       child: Container( | ||||||
|                         heroTag: heroTags[0], |                         constraints: constraints, | ||||||
|  |                         decoration: BoxDecoration( | ||||||
|  |                           color: backgroundColor, | ||||||
|  |                           border: Border(top: borderSide, bottom: borderSide), | ||||||
|  |                           borderRadius: AttachmentList.kDefaultRadius, | ||||||
|  |                         ), | ||||||
|  |                         child: ClipRRect( | ||||||
|  |                           borderRadius: AttachmentList.kDefaultRadius, | ||||||
|  |                           child: AttachmentItem( | ||||||
|  |                             data: widget.data[0], | ||||||
|  |                             heroTag: heroTags[0], | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|                       ), |                       ), | ||||||
|  |                     ); | ||||||
|  |                   } | ||||||
|  |  | ||||||
|  |                   return Container( | ||||||
|  |                     decoration: BoxDecoration( | ||||||
|  |                       color: backgroundColor, | ||||||
|  |                       border: Border(top: borderSide, bottom: borderSide), | ||||||
|                     ), |                     ), | ||||||
|                   ), |                     child: AttachmentItem( | ||||||
|                 ), |                       data: widget.data[0], | ||||||
|               ); |                       heroTag: heroTags.first, | ||||||
|             } |                     ), | ||||||
|  |                   ); | ||||||
|             return Container( |                 }, | ||||||
|               decoration: BoxDecoration( |  | ||||||
|                 color: backgroundColor, |  | ||||||
|                 border: Border(top: borderSide, bottom: borderSide), |  | ||||||
|               ), |               ), | ||||||
|               child: AspectRatio( |  | ||||||
|                 aspectRatio: widget.data[0]?.metadata['ratio']?.toDouble() ?? 1, |  | ||||||
|                 child: AttachmentItem( |  | ||||||
|                   data: widget.data[0], |  | ||||||
|                   heroTag: heroTags.first, |  | ||||||
|                 ), |  | ||||||
|               ), |  | ||||||
|             ); |  | ||||||
|           }, |  | ||||||
|         ), |  | ||||||
|         onTap: () { |  | ||||||
|           context.pushTransparentRoute( |  | ||||||
|             AttachmentZoomView( |  | ||||||
|               data: widget.data.where((ele) => ele != null).cast(), |  | ||||||
|               initialIndex: 0, |  | ||||||
|               heroTags: heroTags, |  | ||||||
|             ), |  | ||||||
|             backgroundColor: Colors.black.withOpacity(0.7), |  | ||||||
|             rootNavigator: true, |  | ||||||
|           ); |  | ||||||
|         }, |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return Container( |  | ||||||
|       constraints: BoxConstraints(maxHeight: widget.maxHeight ?? 320), |  | ||||||
|       child: ScrollConfiguration( |  | ||||||
|         behavior: _AttachmentListScrollBehavior(), |  | ||||||
|         child: ListView.separated( |  | ||||||
|           shrinkWrap: true, |  | ||||||
|           itemCount: widget.data.length, |  | ||||||
|           itemBuilder: (context, idx) { |  | ||||||
|             return GestureDetector( |  | ||||||
|               onTap: () { |               onTap: () { | ||||||
|                 context.pushTransparentRoute( |                 context.pushTransparentRoute( | ||||||
|                   AttachmentZoomView( |                   AttachmentZoomView( | ||||||
|                     data: widget.data.where((ele) => ele != null).cast(), |                     data: widget.data.where((ele) => ele != null).cast(), | ||||||
|                     initialIndex: idx, |                     initialIndex: 0, | ||||||
|                     heroTags: heroTags, |                     heroTags: heroTags, | ||||||
|                   ), |                   ), | ||||||
|                   backgroundColor: Colors.black.withOpacity(0.7), |                   backgroundColor: Colors.black.withOpacity(0.7), | ||||||
|                   rootNavigator: true, |                   rootNavigator: true, | ||||||
|                 ); |                 ); | ||||||
|               }, |               }, | ||||||
|               child: Stack( |             ), | ||||||
|                 children: [ |           ); | ||||||
|                   Container( |         } | ||||||
|                     constraints: constraints, |  | ||||||
|                     decoration: BoxDecoration( |         return AspectRatio( | ||||||
|                       color: backgroundColor, |           aspectRatio: aspectRatio, | ||||||
|                       border: Border(top: borderSide, bottom: borderSide), |           child: Container( | ||||||
|                       borderRadius: AttachmentList.kDefaultRadius, |             constraints: BoxConstraints(maxHeight: widget.maxHeight ?? 320), | ||||||
|                     ), |             child: ScrollConfiguration( | ||||||
|                     child: AspectRatio( |               behavior: _AttachmentListScrollBehavior(), | ||||||
|                       aspectRatio: |               child: ListView.separated( | ||||||
|                           widget.data[idx]?.metadata['ratio']?.toDouble() ?? 1, |                 shrinkWrap: true, | ||||||
|                       child: ClipRRect( |                 itemCount: widget.data.length, | ||||||
|                         borderRadius: AttachmentList.kDefaultRadius, |                 itemBuilder: (context, idx) { | ||||||
|                         child: AttachmentItem( |                   return GestureDetector( | ||||||
|                           data: widget.data[idx], |                     onTap: () { | ||||||
|                           heroTag: heroTags[idx], |                       context.pushTransparentRoute( | ||||||
|  |                         AttachmentZoomView( | ||||||
|  |                           data: widget.data.where((ele) => ele != null).cast(), | ||||||
|  |                           initialIndex: idx, | ||||||
|  |                           heroTags: heroTags, | ||||||
|                         ), |                         ), | ||||||
|                       ), |                         backgroundColor: Colors.black.withOpacity(0.7), | ||||||
|  |                         rootNavigator: true, | ||||||
|  |                       ); | ||||||
|  |                     }, | ||||||
|  |                     child: Stack( | ||||||
|  |                       children: [ | ||||||
|  |                         Container( | ||||||
|  |                           constraints: constraints, | ||||||
|  |                           decoration: BoxDecoration( | ||||||
|  |                             color: backgroundColor, | ||||||
|  |                             border: Border(top: borderSide, bottom: borderSide), | ||||||
|  |                             borderRadius: AttachmentList.kDefaultRadius, | ||||||
|  |                           ), | ||||||
|  |                           child: ClipRRect( | ||||||
|  |                             borderRadius: AttachmentList.kDefaultRadius, | ||||||
|  |                             child: AttachmentItem( | ||||||
|  |                               data: widget.data[idx], | ||||||
|  |                               heroTag: heroTags[idx], | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                         Positioned( | ||||||
|  |                           right: 8, | ||||||
|  |                           bottom: 12, | ||||||
|  |                           child: Chip( | ||||||
|  |                             label: Text('${idx + 1}/${widget.data.length}'), | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                       ], | ||||||
|                     ), |                     ), | ||||||
|                   ), |                   ); | ||||||
|                   Positioned( |                 }, | ||||||
|                     right: 12, |                 separatorBuilder: (context, index) => const Gap(8), | ||||||
|                     bottom: 12, |                 padding: widget.listPadding, | ||||||
|                     child: Chip( |                 physics: const BouncingScrollPhysics(), | ||||||
|                       label: Text('${idx + 1}/${widget.data.length}'), |                 scrollDirection: Axis.horizontal, | ||||||
|                     ), |  | ||||||
|                   ), |  | ||||||
|                 ], |  | ||||||
|               ), |               ), | ||||||
|             ); |             ), | ||||||
|           }, |           ), | ||||||
|           separatorBuilder: (context, index) => const Gap(8), |         ); | ||||||
|           padding: widget.listPadding, |       }, | ||||||
|           physics: const BouncingScrollPhysics(), |  | ||||||
|           scrollDirection: Axis.horizontal, |  | ||||||
|         ), |  | ||||||
|       ), |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user