💄 Better attachments list styles
This commit is contained in:
		| @@ -107,7 +107,7 @@ PODS: | ||||
|     - GoogleUtilities/Privacy | ||||
|   - image_picker_ios (0.0.1): | ||||
|     - Flutter | ||||
|   - livekit_client (2.2.1): | ||||
|   - livekit_client (2.2.2): | ||||
|     - Flutter | ||||
|     - WebRTC-SDK (= 125.6422.04) | ||||
|   - media_kit_libs_ios_video (1.0.4): | ||||
| @@ -138,11 +138,11 @@ PODS: | ||||
|   - SDWebImage (5.19.4): | ||||
|     - SDWebImage/Core (= 5.19.4) | ||||
|   - SDWebImage/Core (5.19.4) | ||||
|   - Sentry/HybridSDK (8.30.1) | ||||
|   - sentry_flutter (8.4.0): | ||||
|   - Sentry/HybridSDK (8.32.0) | ||||
|   - sentry_flutter (8.5.0): | ||||
|     - Flutter | ||||
|     - FlutterMacOS | ||||
|     - Sentry/HybridSDK (= 8.30.1) | ||||
|     - Sentry/HybridSDK (= 8.32.0) | ||||
|   - sqflite (0.0.3): | ||||
|     - Flutter | ||||
|     - FlutterMacOS | ||||
| @@ -268,7 +268,7 @@ SPEC CHECKSUMS: | ||||
|   GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a | ||||
|   GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 | ||||
|   image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 | ||||
|   livekit_client: 411d387fd6f993851081069afbe7f04a8e974f1b | ||||
|   livekit_client: c767049a635d5b6d43de3273dca3c439b8a6e970 | ||||
|   media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1 | ||||
|   media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a | ||||
|   media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e | ||||
| @@ -281,8 +281,8 @@ SPEC CHECKSUMS: | ||||
|   protocol_handler_ios: a5db8abc38526ee326988b808be621e5fd568990 | ||||
|   screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625 | ||||
|   SDWebImage: 066c47b573f408f18caa467d71deace7c0f8280d | ||||
|   Sentry: 514a3ea653886e9a48c6287d8b7bf05ec24bf3be | ||||
|   sentry_flutter: edc037f7af0dc1512d6c33a5c2c7c838bd0d6806 | ||||
|   Sentry: 96ae1dcdf01a644bc3a3b1dc279cecaf48a833fb | ||||
|   sentry_flutter: f1d86adcb93a959bc47a40d8d55059bdf7569bc5 | ||||
|   sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec | ||||
|   SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 | ||||
|   url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe | ||||
|   | ||||
| @@ -1,10 +1,8 @@ | ||||
| import 'package:solian/models/account.dart'; | ||||
|  | ||||
| class Attachment { | ||||
|   int id; | ||||
|   DateTime createdAt; | ||||
|   DateTime updatedAt; | ||||
|   dynamic deletedAt; | ||||
|   DateTime? deletedAt; | ||||
|   String uuid; | ||||
|   int size; | ||||
|   String name; | ||||
| @@ -15,7 +13,6 @@ class Attachment { | ||||
|   String destination; | ||||
|   Map<String, dynamic>? metadata; | ||||
|   bool isMature; | ||||
|   Account account; | ||||
|   int accountId; | ||||
|  | ||||
|   Attachment({ | ||||
| @@ -33,7 +30,6 @@ class Attachment { | ||||
|     required this.destination, | ||||
|     required this.metadata, | ||||
|     required this.isMature, | ||||
|     required this.account, | ||||
|     required this.accountId, | ||||
|   }); | ||||
|  | ||||
| @@ -41,7 +37,7 @@ class Attachment { | ||||
|     id: json['id'], | ||||
|     createdAt: DateTime.parse(json['created_at']), | ||||
|     updatedAt: DateTime.parse(json['updated_at']), | ||||
|     deletedAt: json['deleted_at'], | ||||
|     deletedAt: json['deleted_at'] != null ? DateTime.parse(json['deleted_at']) : null, | ||||
|     uuid: json['uuid'], | ||||
|     size: json['size'], | ||||
|     name: json['name'], | ||||
| @@ -52,7 +48,6 @@ class Attachment { | ||||
|     destination: json['destination'], | ||||
|     metadata: json['metadata'], | ||||
|     isMature: json['is_mature'], | ||||
|     account: Account.fromJson(json['account']), | ||||
|     accountId: json['account_id'], | ||||
|   ); | ||||
|  | ||||
| @@ -60,7 +55,7 @@ class Attachment { | ||||
|     'id': id, | ||||
|     'created_at': createdAt.toIso8601String(), | ||||
|     'updated_at': updatedAt.toIso8601String(), | ||||
|     'deleted_at': deletedAt, | ||||
|     'deleted_at': deletedAt?.toIso8601String(), | ||||
|     'uuid': uuid, | ||||
|     'size': size, | ||||
|     'name': name, | ||||
| @@ -71,7 +66,6 @@ class Attachment { | ||||
|     'destination': destination, | ||||
|     'metadata': metadata, | ||||
|     'is_mature': isMature, | ||||
|     'account': account.toJson(), | ||||
|     'account_id': accountId, | ||||
|   }; | ||||
| } | ||||
| @@ -44,70 +44,66 @@ class _HomeScreenState extends State<HomeScreen> | ||||
|   Widget build(BuildContext context) { | ||||
|     return Material( | ||||
|       color: Theme.of(context).colorScheme.surface, | ||||
|       child: SafeArea( | ||||
|         bottom: false, | ||||
|         child: Scaffold( | ||||
|           floatingActionButton: FloatingActionButton( | ||||
|             child: const Icon(Icons.add), | ||||
|             onPressed: () { | ||||
|               showModalBottomSheet( | ||||
|                 useRootNavigator: true, | ||||
|                 isScrollControlled: true, | ||||
|                 context: context, | ||||
|                 builder: (context) => const PostCreatePopup(), | ||||
|               ); | ||||
|             }, | ||||
|           ), | ||||
|           body: NestedScrollView( | ||||
|             headerSliverBuilder: | ||||
|                 (BuildContext context, bool innerBoxIsScrolled) { | ||||
|               return [ | ||||
|                 SliverAppBar( | ||||
|                   title: AppBarTitle('home'.tr), | ||||
|                   centerTitle: false, | ||||
|                   floating: true, | ||||
|                   toolbarHeight: SolianTheme.toolbarHeight(context), | ||||
|                   leading: AppBarLeadingButton.adaptive(context), | ||||
|                   actions: [ | ||||
|                     const BackgroundStateWidget(), | ||||
|                     const NotificationButton(), | ||||
|                     SizedBox( | ||||
|                       width: SolianTheme.isLargeScreen(context) ? 8 : 16, | ||||
|                     ), | ||||
|                   ], | ||||
|                   bottom: TabBar( | ||||
|                     controller: _tabController, | ||||
|                     tabs: [ | ||||
|                       Tab(text: 'postListNews'.tr), | ||||
|                       Tab(text: 'postListShuffle'.tr), | ||||
|                     ], | ||||
|       child: Scaffold( | ||||
|         floatingActionButton: FloatingActionButton( | ||||
|           child: const Icon(Icons.add), | ||||
|           onPressed: () { | ||||
|             showModalBottomSheet( | ||||
|               useRootNavigator: true, | ||||
|               isScrollControlled: true, | ||||
|               context: context, | ||||
|               builder: (context) => const PostCreatePopup(), | ||||
|             ); | ||||
|           }, | ||||
|         ), | ||||
|         body: NestedScrollView( | ||||
|           headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { | ||||
|             return [ | ||||
|               SliverAppBar( | ||||
|                 title: AppBarTitle('home'.tr), | ||||
|                 centerTitle: false, | ||||
|                 floating: true, | ||||
|                 toolbarHeight: SolianTheme.toolbarHeight(context), | ||||
|                 leading: AppBarLeadingButton.adaptive(context), | ||||
|                 actions: [ | ||||
|                   const BackgroundStateWidget(), | ||||
|                   const NotificationButton(), | ||||
|                   SizedBox( | ||||
|                     width: SolianTheme.isLargeScreen(context) ? 8 : 16, | ||||
|                   ), | ||||
|                 ) | ||||
|               ]; | ||||
|             }, | ||||
|             body: Obx(() { | ||||
|               if (_postController.isPreparing.isTrue) { | ||||
|                 return const Center( | ||||
|                   child: CircularProgressIndicator(), | ||||
|                 ); | ||||
|               } | ||||
|  | ||||
|               return TabBarView( | ||||
|                 physics: const NeverScrollableScrollPhysics(), | ||||
|                 controller: _tabController, | ||||
|                 children: [ | ||||
|                   RefreshIndicator( | ||||
|                     onRefresh: () => _postController.reloadAllOver(), | ||||
|                     child: CustomScrollView(slivers: [ | ||||
|                       FeedListWidget( | ||||
|                           controller: _postController.pagingController), | ||||
|                     ]), | ||||
|                   ), | ||||
|                   PostShuffleSwiper(controller: _postController), | ||||
|                 ], | ||||
|                 bottom: TabBar( | ||||
|                   controller: _tabController, | ||||
|                   tabs: [ | ||||
|                     Tab(text: 'postListNews'.tr), | ||||
|                     Tab(text: 'postListShuffle'.tr), | ||||
|                   ], | ||||
|                 ), | ||||
|               ) | ||||
|             ]; | ||||
|           }, | ||||
|           body: Obx(() { | ||||
|             if (_postController.isPreparing.isTrue) { | ||||
|               return const Center( | ||||
|                 child: CircularProgressIndicator(), | ||||
|               ); | ||||
|             }), | ||||
|           ), | ||||
|             } | ||||
|  | ||||
|             return TabBarView( | ||||
|               physics: const NeverScrollableScrollPhysics(), | ||||
|               controller: _tabController, | ||||
|               children: [ | ||||
|                 RefreshIndicator( | ||||
|                   onRefresh: () => _postController.reloadAllOver(), | ||||
|                   child: CustomScrollView(slivers: [ | ||||
|                     FeedListWidget( | ||||
|                         controller: _postController.pagingController), | ||||
|                   ]), | ||||
|                 ), | ||||
|                 PostShuffleSwiper(controller: _postController), | ||||
|               ], | ||||
|             ); | ||||
|           }), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import 'dart:math' show min; | ||||
| import 'dart:ui'; | ||||
|  | ||||
| import 'package:carousel_slider/carousel_slider.dart'; | ||||
| @@ -11,7 +12,7 @@ import 'package:solian/widgets/attachments/attachment_list_fullscreen.dart'; | ||||
| class AttachmentList extends StatefulWidget { | ||||
|   final String parentId; | ||||
|   final List<int> attachmentsId; | ||||
|   final bool divided; | ||||
|   final bool isGrid; | ||||
|  | ||||
|   final double? width; | ||||
|   final double? viewport; | ||||
| @@ -20,7 +21,7 @@ class AttachmentList extends StatefulWidget { | ||||
|     super.key, | ||||
|     required this.parentId, | ||||
|     required this.attachmentsId, | ||||
|     this.divided = false, | ||||
|     this.isGrid = false, | ||||
|     this.width, | ||||
|     this.viewport, | ||||
|   }); | ||||
| @@ -101,12 +102,43 @@ class _AttachmentListState extends State<AttachmentList> { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Widget buildEntry(Attachment element, int idx) { | ||||
|   Widget _buildEntry(Attachment? element, int idx) { | ||||
|     if (element == null) { | ||||
|       return Center( | ||||
|         child: Container( | ||||
|           constraints: const BoxConstraints(maxWidth: 280), | ||||
|           child: Column( | ||||
|             mainAxisAlignment: MainAxisAlignment.center, | ||||
|             children: [ | ||||
|               const Icon(Icons.close, size: 32), | ||||
|               const SizedBox(height: 8), | ||||
|               Text( | ||||
|                 'attachmentLoadFailed'.tr, | ||||
|                 style: | ||||
|                     const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), | ||||
|               ), | ||||
|               Text( | ||||
|                 'attachmentLoadFailedCaption'.tr, | ||||
|                 textAlign: TextAlign.center, | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return GestureDetector( | ||||
|       child: Container( | ||||
|         width: widget.width ?? MediaQuery.of(context).size.width, | ||||
|         decoration: BoxDecoration( | ||||
|           color: Theme.of(context).colorScheme.surfaceContainerHigh, | ||||
|           border: widget.attachmentsId.length > 1 | ||||
|               ? Border.symmetric( | ||||
|                   vertical: BorderSide( | ||||
|                     width: 0.3, | ||||
|                     color: Theme.of(context).dividerColor, | ||||
|                   ), | ||||
|                 ) | ||||
|               : null, | ||||
|         ), | ||||
|         child: Stack( | ||||
|           fit: StackFit.expand, | ||||
| @@ -115,7 +147,7 @@ class _AttachmentListState extends State<AttachmentList> { | ||||
|               parentId: widget.parentId, | ||||
|               key: Key('a${element.uuid}'), | ||||
|               item: element, | ||||
|               badge: _attachmentsMeta.length > 1 | ||||
|               badge: _attachmentsMeta.length > 1 && !widget.isGrid | ||||
|                   ? '${idx + 1}/${_attachmentsMeta.length}' | ||||
|                   : null, | ||||
|               showHideButton: !element.isMature || _showMature, | ||||
| @@ -139,15 +171,19 @@ class _AttachmentListState extends State<AttachmentList> { | ||||
|                   child: Column( | ||||
|                     mainAxisAlignment: MainAxisAlignment.center, | ||||
|                     children: [ | ||||
|                       const Icon(Icons.visibility_off, | ||||
|                           color: Colors.white, size: 32), | ||||
|                       const Icon( | ||||
|                         Icons.visibility_off, | ||||
|                         color: Colors.white, | ||||
|                         size: 32, | ||||
|                       ), | ||||
|                       const SizedBox(height: 8), | ||||
|                       Text( | ||||
|                         'matureContent'.tr, | ||||
|                         style: const TextStyle( | ||||
|                             color: Colors.white, | ||||
|                             fontWeight: FontWeight.bold, | ||||
|                             fontSize: 16), | ||||
|                           color: Colors.white, | ||||
|                           fontWeight: FontWeight.bold, | ||||
|                           fontSize: 16, | ||||
|                         ), | ||||
|                       ), | ||||
|                       Text( | ||||
|                         'matureContentCaption'.tr, | ||||
| @@ -193,62 +229,65 @@ class _AttachmentListState extends State<AttachmentList> { | ||||
|     if (_isLoading) { | ||||
|       return Container( | ||||
|         decoration: BoxDecoration( | ||||
|             color: Theme.of(context).colorScheme.surfaceContainerHigh), | ||||
|           color: Theme.of(context).colorScheme.surfaceContainerHigh, | ||||
|         ), | ||||
|         child: const LinearProgressIndicator(), | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return CarouselSlider.builder( | ||||
|       options: CarouselOptions( | ||||
|         aspectRatio: _aspectRatio, | ||||
|         viewportFraction: widget.viewport ?? (widget.divided ? 0.9 : 1), | ||||
|         enableInfiniteScroll: false, | ||||
|       ), | ||||
|       itemCount: _attachmentsMeta.length, | ||||
|       itemBuilder: (context, idx, _) { | ||||
|         final element = _attachmentsMeta[idx]; | ||||
|  | ||||
|         if (element == null) { | ||||
|           return Center( | ||||
|             child: Container( | ||||
|               constraints: const BoxConstraints(maxWidth: 280), | ||||
|               child: Column( | ||||
|                 mainAxisAlignment: MainAxisAlignment.center, | ||||
|                 children: [ | ||||
|                   const Icon(Icons.close, size: 32), | ||||
|                   const SizedBox(height: 8), | ||||
|                   Text( | ||||
|                     'attachmentLoadFailed'.tr, | ||||
|                     style: const TextStyle( | ||||
|                         fontWeight: FontWeight.bold, fontSize: 16), | ||||
|                   ), | ||||
|                   Text( | ||||
|                     'attachmentLoadFailedCaption'.tr, | ||||
|                     textAlign: TextAlign.center, | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
|             ), | ||||
|           ); | ||||
|         } | ||||
|  | ||||
|         if (widget.divided) { | ||||
|           const radius = BorderRadius.all(Radius.circular(16)); | ||||
|     if (widget.isGrid) { | ||||
|       const radius = BorderRadius.all(Radius.circular(8)); | ||||
|       return GridView.builder( | ||||
|         padding: EdgeInsets.zero, | ||||
|         primary: false, | ||||
|         physics: const NeverScrollableScrollPhysics(), | ||||
|         shrinkWrap: true, | ||||
|         gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( | ||||
|           crossAxisCount: min(3, widget.attachmentsId.length), | ||||
|           mainAxisSpacing: 8.0, | ||||
|           crossAxisSpacing: 8.0, | ||||
|         ), | ||||
|         itemCount: widget.attachmentsId.length, | ||||
|         itemBuilder: (context, idx) { | ||||
|           final element = _attachmentsMeta[idx]; | ||||
|           return Container( | ||||
|             decoration: BoxDecoration( | ||||
|               border: | ||||
|                   Border.all(color: Theme.of(context).dividerColor, width: 1), | ||||
|               Border.all(color: Theme.of(context).dividerColor, width: 1), | ||||
|               borderRadius: radius, | ||||
|             ), | ||||
|             child: ClipRRect( | ||||
|               borderRadius: radius, | ||||
|               child: buildEntry(element, idx), | ||||
|             ), | ||||
|           ).paddingSymmetric(horizontal: widget.divided ? 4 : 0); | ||||
|         } else { | ||||
|           return buildEntry(element, idx); | ||||
|         } | ||||
|       }, | ||||
|               child: _buildEntry(element, idx), | ||||
|             ) | ||||
|           ); | ||||
|         }, | ||||
|       ).paddingSymmetric(horizontal: 24); | ||||
|     } | ||||
|  | ||||
|     return Container( | ||||
|       decoration: BoxDecoration( | ||||
|         color: Theme.of(context).colorScheme.surfaceContainerHigh, | ||||
|         border: Border.symmetric( | ||||
|           horizontal: BorderSide( | ||||
|             width: 0.3, | ||||
|             color: Theme.of(context).dividerColor, | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|       child: CarouselSlider.builder( | ||||
|         options: CarouselOptions( | ||||
|           aspectRatio: _aspectRatio, | ||||
|           viewportFraction: | ||||
|               widget.viewport ?? (widget.attachmentsId.length > 1 ? 0.95 : 1), | ||||
|           enableInfiniteScroll: false, | ||||
|         ), | ||||
|         itemCount: _attachmentsMeta.length, | ||||
|         itemBuilder: (context, idx, _) { | ||||
|           final element = _attachmentsMeta[idx]; | ||||
|           return _buildEntry(element, idx); | ||||
|         }, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import 'package:solian/controllers/chat_events_controller.dart'; | ||||
| import 'package:solian/models/event.dart'; | ||||
| import 'package:solian/widgets/account/account_avatar.dart'; | ||||
| import 'package:solian/widgets/account/account_profile_popup.dart'; | ||||
| import 'package:solian/widgets/attachments/attachment_list.dart'; | ||||
| import 'package:solian/widgets/chat/chat_event_action_log.dart'; | ||||
| import 'package:solian/widgets/chat/chat_event_message.dart'; | ||||
| import 'package:timeago/timeago.dart' show format; | ||||
| @@ -36,7 +37,30 @@ class ChatEvent extends StatelessWidget { | ||||
|     return '$negativeSign${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds'; | ||||
|   } | ||||
|  | ||||
|   Widget buildQuote() { | ||||
|   Widget _buildAttachment(BuildContext context) { | ||||
|     final attachments = item.body['attachments'] != null | ||||
|         ? List<int>.from(item.body['attachments'].map((x) => x)) | ||||
|         : List<int>.empty(); | ||||
|  | ||||
|     if (attachments.isEmpty) return const SizedBox(); | ||||
|  | ||||
|     return Container( | ||||
|       key: Key('m${item.uuid}attachments-box'), | ||||
|       width: MediaQuery.of(context).size.width, | ||||
|       padding: EdgeInsets.only(top: isMerged ? 0 : 4), | ||||
|       constraints: const BoxConstraints( | ||||
|         maxHeight: 720, | ||||
|       ), | ||||
|       child: AttachmentList( | ||||
|         key: Key('m${item.uuid}attachments'), | ||||
|         parentId: item.uuid, | ||||
|         attachmentsId: attachments, | ||||
|         viewport: 1, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget _buildQuote() { | ||||
|     return FutureBuilder( | ||||
|       future: chatController!.getEvent( | ||||
|         item.body['quote_event'], | ||||
| @@ -55,7 +79,7 @@ class ChatEvent extends StatelessWidget { | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget buildContent() { | ||||
|   Widget _buildContent() { | ||||
|     switch (item.type) { | ||||
|       case 'messages.new': | ||||
|         return ChatEventMessage( | ||||
| @@ -121,17 +145,17 @@ class ChatEvent extends StatelessWidget { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Widget buildBody(BuildContext context) { | ||||
|   Widget _buildBody(BuildContext context) { | ||||
|     if (isContentPreviewing || (isMerged && !isQuote)) { | ||||
|       return Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           if (item.body['quote_event'] != null && chatController != null) | ||||
|             buildQuote(), | ||||
|             _buildQuote(), | ||||
|           Row( | ||||
|             crossAxisAlignment: CrossAxisAlignment.center, | ||||
|             children: [ | ||||
|               Expanded(child: buildContent()), | ||||
|               Expanded(child: _buildContent()), | ||||
|               if (item.isPending) | ||||
|                 const SizedBox( | ||||
|                   width: 12, | ||||
| @@ -139,9 +163,10 @@ class ChatEvent extends StatelessWidget { | ||||
|                   child: CircularProgressIndicator(strokeWidth: 2), | ||||
|                 ), | ||||
|             ], | ||||
|           ), | ||||
|           ).paddingOnly(right: 12), | ||||
|           _buildAttachment(context), | ||||
|         ], | ||||
|       ).paddingOnly(right: 12); | ||||
|       ); | ||||
|     } else if (isQuote) { | ||||
|       return Card( | ||||
|         child: Row( | ||||
| @@ -170,7 +195,7 @@ class ChatEvent extends StatelessWidget { | ||||
|                       Text(format(item.createdAt, locale: 'en_short')), | ||||
|                     ], | ||||
|                   ), | ||||
|                   buildContent().paddingOnly(left: 0.5), | ||||
|                   _buildContent().paddingOnly(left: 0.5), | ||||
|                 ], | ||||
|               ), | ||||
|             ), | ||||
| @@ -214,11 +239,11 @@ class ChatEvent extends StatelessWidget { | ||||
|                     ).paddingSymmetric(horizontal: 12), | ||||
|                     if (item.body['quote_event'] != null && | ||||
|                         chatController != null) | ||||
|                       buildQuote(), | ||||
|                       _buildQuote(), | ||||
|                     Row( | ||||
|                       crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                       children: [ | ||||
|                         Expanded(child: buildContent()), | ||||
|                         Expanded(child: _buildContent()), | ||||
|                         if (item.isPending) | ||||
|                           const SizedBox( | ||||
|                             width: 12, | ||||
| @@ -232,6 +257,7 @@ class ChatEvent extends StatelessWidget { | ||||
|               ), | ||||
|             ], | ||||
|           ).paddingSymmetric(horizontal: 12), | ||||
|           _buildAttachment(context), | ||||
|         ], | ||||
|       ); | ||||
|     } | ||||
| @@ -239,6 +265,6 @@ class ChatEvent extends StatelessWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return buildBody(context); | ||||
|     return _buildBody(context); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:solian/models/event.dart'; | ||||
| import 'package:solian/widgets/attachments/attachment_list.dart'; | ||||
| import 'package:solian/widgets/markdown_text_content.dart'; | ||||
|  | ||||
| class ChatEventMessage extends StatelessWidget { | ||||
| @@ -20,66 +19,52 @@ class ChatEventMessage extends StatelessWidget { | ||||
|     this.isQuote = false, | ||||
|   }); | ||||
|  | ||||
|   Widget buildAttachment(BuildContext context) { | ||||
|     final body = EventMessageBody.fromJson(item.body); | ||||
|  | ||||
|     return Container( | ||||
|       key: Key('m${item.uuid}attachments-box'), | ||||
|       width: MediaQuery.of(context).size.width, | ||||
|       constraints: const BoxConstraints( | ||||
|         maxHeight: 720, | ||||
|         maxWidth: 640, | ||||
|       ), | ||||
|       child: AttachmentList( | ||||
|         key: Key('m${item.uuid}attachments'), | ||||
|         parentId: item.uuid, | ||||
|         attachmentsId: body.attachments ?? List.empty(), | ||||
|         divided: true, | ||||
|         viewport: 1, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget buildContent() { | ||||
|   Widget _buildContent(BuildContext context) { | ||||
|     final body = EventMessageBody.fromJson(item.body); | ||||
|     final hasAttachment = body.attachments?.isNotEmpty ?? false; | ||||
|  | ||||
|     return MarkdownTextContent(content: body.text).paddingOnly( | ||||
|     if (body.text.isEmpty && hasAttachment) { | ||||
|       final unFocusColor = Theme.of(context).colorScheme.onSurface.withOpacity(0.75); | ||||
|       return Row( | ||||
|         children: [ | ||||
|           Icon( | ||||
|             Icons.attachment, | ||||
|             size: 18, | ||||
|             color: unFocusColor, | ||||
|           ).paddingOnly(right: 6), | ||||
|           Text( | ||||
|             'postAttachmentTip'.trParams( | ||||
|               {'count': body.attachments?.length.toString() ?? 0.toString()}, | ||||
|             ), | ||||
|             style: TextStyle(color: unFocusColor), | ||||
|           ) | ||||
|         ], | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return MarkdownTextContent(content: body.text); | ||||
|   } | ||||
|  | ||||
|   Widget _buildBody(BuildContext context) { | ||||
|     if (isContentPreviewing) { | ||||
|       return _buildContent(context); | ||||
|     } else if (isMerged) { | ||||
|       return _buildContent(context).paddingOnly(left: 52); | ||||
|     } else { | ||||
|       return _buildContent(context); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final body = EventMessageBody.fromJson(item.body); | ||||
|     final hasAttachment = body.attachments?.isNotEmpty ?? false; | ||||
|  | ||||
|     return _buildBody(context).paddingOnly( | ||||
|       left: isQuote ? 0 : 12, | ||||
|       right: isQuote ? 0 : 12, | ||||
|       top: body.quoteEvent == null ? 2 : 0, | ||||
|       bottom: hasAttachment ? 4 : (isHasMerged ? 2 : 0), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget buildBody(BuildContext context) { | ||||
|     final body = EventMessageBody.fromJson(item.body); | ||||
|  | ||||
|     if (isContentPreviewing) { | ||||
|       return buildContent(); | ||||
|     } else if (isMerged) { | ||||
|       return Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           buildContent().paddingOnly(left: 52), | ||||
|           if (body.attachments?.isNotEmpty ?? false) | ||||
|             buildAttachment(context).paddingOnly(left: 52, bottom: 4), | ||||
|         ], | ||||
|       ); | ||||
|     } else { | ||||
|       return Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           buildContent(), | ||||
|           if (body.attachments?.isNotEmpty ?? false) | ||||
|             buildAttachment(context).paddingOnly(bottom: 4), | ||||
|         ], | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return buildBody(context); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -52,15 +52,21 @@ class _PostItemState extends State<PostItem> { | ||||
|     super.initState(); | ||||
|   } | ||||
|  | ||||
|   Widget buildDate() { | ||||
|   Widget _buildDate() { | ||||
|     if (widget.isFullDate) { | ||||
|       return Text(DateFormat('y/M/d H:m').format(item.createdAt.toLocal())); | ||||
|       return Text(DateFormat('y/M/d H:m') | ||||
|           .format(item.publishedAt?.toLocal() ?? DateTime.now())); | ||||
|     } else { | ||||
|       return Text(format(item.createdAt.toLocal(), locale: 'en_short')); | ||||
|       return Text( | ||||
|         format( | ||||
|           item.publishedAt?.toLocal() ?? DateTime.now(), | ||||
|           locale: 'en_short', | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Widget buildHeader() { | ||||
|   Widget _buildHeader() { | ||||
|     return Row( | ||||
|       children: [ | ||||
|         if (widget.isCompact) | ||||
| @@ -72,12 +78,12 @@ class _PostItemState extends State<PostItem> { | ||||
|           item.author.nick, | ||||
|           style: const TextStyle(fontWeight: FontWeight.bold), | ||||
|         ).paddingOnly(left: widget.isCompact ? 6 : 12), | ||||
|         buildDate().paddingOnly(left: 4), | ||||
|         _buildDate().paddingOnly(left: 4), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget buildFooter() { | ||||
|   Widget _buildFooter() { | ||||
|     List<String> labels = List.empty(growable: true); | ||||
|     if (widget.item.createdAt != widget.item.updatedAt) { | ||||
|       labels.add('postEdited'.trParams({ | ||||
| @@ -116,7 +122,7 @@ class _PostItemState extends State<PostItem> { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Widget buildReply(BuildContext context) { | ||||
|   Widget _buildReply(BuildContext context) { | ||||
|     return Column( | ||||
|       children: [ | ||||
|         Row( | ||||
| @@ -148,7 +154,7 @@ class _PostItemState extends State<PostItem> { | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget buildRepost(BuildContext context) { | ||||
|   Widget _buildRepost(BuildContext context) { | ||||
|     return Column( | ||||
|       children: [ | ||||
|         Row( | ||||
| @@ -191,7 +197,7 @@ class _PostItemState extends State<PostItem> { | ||||
|       return Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           buildHeader().paddingSymmetric(horizontal: 12), | ||||
|           _buildHeader().paddingSymmetric(horizontal: 12), | ||||
|           MarkdownTextContent( | ||||
|             content: item.body['content'], | ||||
|             isSelectable: widget.isContentSelectable, | ||||
| @@ -201,7 +207,7 @@ class _PostItemState extends State<PostItem> { | ||||
|             top: 2, | ||||
|             bottom: hasAttachment ? 4 : 0, | ||||
|           ), | ||||
|           buildFooter().paddingOnly(left: 16), | ||||
|           _buildFooter().paddingOnly(left: 16), | ||||
|           if (attachments.isNotEmpty) | ||||
|             Row( | ||||
|               children: [ | ||||
| @@ -246,14 +252,14 @@ class _PostItemState extends State<PostItem> { | ||||
|               child: Column( | ||||
|                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                 children: [ | ||||
|                   buildHeader(), | ||||
|                   _buildHeader(), | ||||
|                   MarkdownTextContent( | ||||
|                     content: item.body['content'], | ||||
|                     isSelectable: widget.isContentSelectable, | ||||
|                   ).paddingOnly(left: 12, right: 8), | ||||
|                   if (widget.item.replyTo != null && widget.isShowEmbed) | ||||
|                     GestureDetector( | ||||
|                       child: buildReply(context).paddingOnly(top: 4), | ||||
|                       child: _buildReply(context).paddingOnly(top: 4), | ||||
|                       onTap: () { | ||||
|                         if (!widget.isClickable) return; | ||||
|                         AppRouter.instance.pushNamed( | ||||
| @@ -266,7 +272,7 @@ class _PostItemState extends State<PostItem> { | ||||
|                     ), | ||||
|                   if (widget.item.repostTo != null && widget.isShowEmbed) | ||||
|                     GestureDetector( | ||||
|                       child: buildRepost(context).paddingOnly(top: 4), | ||||
|                       child: _buildRepost(context).paddingOnly(top: 4), | ||||
|                       onTap: () { | ||||
|                         if (!widget.isClickable) return; | ||||
|                         AppRouter.instance.pushNamed( | ||||
| @@ -277,7 +283,7 @@ class _PostItemState extends State<PostItem> { | ||||
|                         ); | ||||
|                       }, | ||||
|                     ), | ||||
|                   buildFooter().paddingOnly(left: 12), | ||||
|                   _buildFooter().paddingOnly(left: 12), | ||||
|                 ], | ||||
|               ), | ||||
|             ) | ||||
| @@ -297,7 +303,7 @@ class _PostItemState extends State<PostItem> { | ||||
|           child: AttachmentList( | ||||
|             parentId: widget.item.id.toString(), | ||||
|             attachmentsId: attachments, | ||||
|             divided: true, | ||||
|             isGrid: attachments.length > 1, | ||||
|           ), | ||||
|         ), | ||||
|         if (widget.isShowReply && widget.isReactable) | ||||
|   | ||||
| @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; | ||||
| import 'package:solian/models/post.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/widgets/sized_container.dart'; | ||||
| import 'package:solian/widgets/posts/post_action.dart'; | ||||
| @@ -79,6 +80,9 @@ class PostListEntryWidget extends StatelessWidget { | ||||
|         ); | ||||
|       }, | ||||
|       onLongPress: () { | ||||
|         final AuthProvider auth = Get.find(); | ||||
|         if (auth.isAuthorized.isFalse) return; | ||||
|  | ||||
|         showModalBottomSheet( | ||||
|           useRootNavigator: true, | ||||
|           context: context, | ||||
|   | ||||
| @@ -40,6 +40,9 @@ class _PostShuffleSwiperState extends State<PostShuffleSwiper> { | ||||
|           return PostSingleDisplay( | ||||
|             key: Key('p${element.id}'), | ||||
|             item: element, | ||||
|             onUpdate: () { | ||||
|               widget.controller.reloadAllOver(); | ||||
|             }, | ||||
|           ); | ||||
|         }, | ||||
|         padding: const EdgeInsets.all(24), | ||||
|   | ||||
| @@ -1,12 +1,16 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:solian/models/post.dart'; | ||||
| import 'package:solian/widgets/posts/post_item.dart'; | ||||
| import 'package:solian/widgets/posts/post_list.dart'; | ||||
|  | ||||
| class PostSingleDisplay extends StatelessWidget { | ||||
|   final Post item; | ||||
|   final Function onUpdate; | ||||
|  | ||||
|   const PostSingleDisplay({super.key, required this.item}); | ||||
|   const PostSingleDisplay({ | ||||
|     super.key, | ||||
|     required this.item, | ||||
|     required this.onUpdate, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
| @@ -14,9 +18,13 @@ class PostSingleDisplay extends StatelessWidget { | ||||
|       alignment: Alignment.center, | ||||
|       child: Card( | ||||
|         child: SingleChildScrollView( | ||||
|           child: PostItem( | ||||
|           child: PostListEntryWidget( | ||||
|             item: item, | ||||
|           ).paddingSymmetric(horizontal: 10, vertical: 16), | ||||
|             isClickable: true, | ||||
|             isShowEmbed: true, | ||||
|             isNestedClickable: true, | ||||
|             onUpdate: onUpdate, | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user