💄 Use bottom modal sheet instead of popover
✨ Show strike on user profile page
			
			
This commit is contained in:
		| @@ -15,6 +15,7 @@ import 'package:surface/providers/experience.dart'; | ||||
| import 'package:surface/providers/relationship.dart'; | ||||
| import 'package:surface/providers/sn_network.dart'; | ||||
| import 'package:surface/screens/abuse_report.dart'; | ||||
| import 'package:surface/screens/account/punishments.dart'; | ||||
| import 'package:surface/types/account.dart'; | ||||
| import 'package:surface/types/check_in.dart'; | ||||
| import 'package:surface/types/post.dart'; | ||||
| @@ -457,7 +458,7 @@ class _UserScreenState extends State<UserScreen> | ||||
|                     ], | ||||
|                   ).padding(right: 8), | ||||
|                   if (_account!.profile!.description.isNotEmpty) | ||||
|                     const Gap(12) | ||||
|                     const Gap(4) | ||||
|                   else | ||||
|                     const Gap(8), | ||||
|                   if (_account!.profile!.description.isNotEmpty) | ||||
| @@ -503,14 +504,15 @@ class _UserScreenState extends State<UserScreen> | ||||
|                       ], | ||||
|                     ).padding(vertical: 8, horizontal: 12), | ||||
|                   ), | ||||
|                   const Gap(8), | ||||
|                   Wrap( | ||||
|                     spacing: 4, | ||||
|                     runSpacing: 4, | ||||
|                     children: _account!.badges | ||||
|                         .map((ele) => AccountBadge(badge: ele)) | ||||
|                         .toList(), | ||||
|                   ).padding(horizontal: 8), | ||||
|                   if (_account!.badges.isNotEmpty) const Gap(8), | ||||
|                   if (_account!.badges.isNotEmpty) | ||||
|                     Wrap( | ||||
|                       spacing: 4, | ||||
|                       runSpacing: 4, | ||||
|                       children: _account!.badges | ||||
|                           .map((ele) => AccountBadge(badge: ele)) | ||||
|                           .toList(), | ||||
|                     ).padding(horizontal: 8), | ||||
|                   const Gap(8), | ||||
|                   Column( | ||||
|                     children: [ | ||||
| @@ -619,6 +621,17 @@ class _UserScreenState extends State<UserScreen> | ||||
|                 ], | ||||
|               ).padding(all: 16), | ||||
|             ), | ||||
|           if (_account?.punishments.isNotEmpty ?? false) | ||||
|             SliverToBoxAdapter(child: const Divider()), | ||||
|           if (_account?.punishments.isNotEmpty ?? false) | ||||
|             SliverToBoxAdapter( | ||||
|               child: Column( | ||||
|                 children: [ | ||||
|                   for (final ele in _account!.punishments) | ||||
|                     PunishmentInfoCard(ele: ele), | ||||
|                 ], | ||||
|               ), | ||||
|             ), | ||||
|           if (_account?.profile?.links.isNotEmpty ?? false) | ||||
|             SliverToBoxAdapter(child: const Divider()), | ||||
|           if (_account?.profile?.links.isNotEmpty ?? false) | ||||
|   | ||||
| @@ -107,74 +107,7 @@ class _PunishmentsScreenState extends State<PunishmentsScreen> { | ||||
|                 itemCount: _punishments?.length ?? 0, | ||||
|                 itemBuilder: (context, index) { | ||||
|                   final ele = _punishments![index]; | ||||
|                   return Card( | ||||
|                     margin: EdgeInsets.symmetric(horizontal: 8), | ||||
|                     child: Column( | ||||
|                       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                       children: [ | ||||
|                         Row( | ||||
|                           children: [ | ||||
|                             Icon(kPunishmentIcons[ele.type], size: 20), | ||||
|                             const Gap(6), | ||||
|                             Expanded( | ||||
|                               child: Text('punishmentType${ele.type}') | ||||
|                                   .tr() | ||||
|                                   .fontSize(16) | ||||
|                                   .bold(), | ||||
|                             ), | ||||
|                           ], | ||||
|                         ), | ||||
|                         Text(ele.reason), | ||||
|                         const Gap(4), | ||||
|                         Text( | ||||
|                           'punishmentCreatedAt'.tr(args: [ | ||||
|                             DateFormat().format( | ||||
|                               ele.createdAt.toLocal(), | ||||
|                             ) | ||||
|                           ]), | ||||
|                         ).opacity(0.8), | ||||
|                         Text( | ||||
|                           ele.expiredAt == null | ||||
|                               ? 'punishmentExpiredNever'.tr() | ||||
|                               : 'punishmentExpiredAt'.tr(args: [ | ||||
|                                   DateFormat().format( | ||||
|                                     ele.expiredAt!.toLocal(), | ||||
|                                   ) | ||||
|                                 ]), | ||||
|                         ).opacity(0.8), | ||||
|                         const Gap(8), | ||||
|                         if (ele.moderator != null) | ||||
|                           Column( | ||||
|                             crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                             children: [ | ||||
|                               Text('punishmentModerator').tr().opacity(0.75), | ||||
|                               InkWell( | ||||
|                                 child: Row( | ||||
|                                   children: [ | ||||
|                                     AccountImage( | ||||
|                                       content: ele.moderator!.avatar, | ||||
|                                       radius: 8, | ||||
|                                     ), | ||||
|                                     const Gap(4), | ||||
|                                     Text(ele.moderator?.nick ?? 'unknown'), | ||||
|                                   ], | ||||
|                                 ), | ||||
|                                 onTap: () { | ||||
|                                   GoRouter.of(context).pushNamed( | ||||
|                                     'accountProfilePage', | ||||
|                                     pathParameters: { | ||||
|                                       'name': ele.moderator!.name, | ||||
|                                     }, | ||||
|                                   ); | ||||
|                                 }, | ||||
|                               ), | ||||
|                             ], | ||||
|                           ) | ||||
|                         else | ||||
|                           Text('punishmentMadeBySystem').tr().opacity(0.75), | ||||
|                       ], | ||||
|                     ).padding(horizontal: 24, vertical: 16), | ||||
|                   ); | ||||
|                   return PunishmentInfoCard(ele: ele); | ||||
|                 }, | ||||
|                 separatorBuilder: (_, __) => const Gap(8), | ||||
|               ), | ||||
| @@ -185,3 +118,82 @@ class _PunishmentsScreenState extends State<PunishmentsScreen> { | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class PunishmentInfoCard extends StatelessWidget { | ||||
|   const PunishmentInfoCard({ | ||||
|     super.key, | ||||
|     required this.ele, | ||||
|   }); | ||||
|  | ||||
|   final SnPunishment ele; | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Card( | ||||
|       margin: EdgeInsets.symmetric(horizontal: 8), | ||||
|       child: Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           Row( | ||||
|             children: [ | ||||
|               Icon(kPunishmentIcons[ele.type], size: 20), | ||||
|               const Gap(6), | ||||
|               Expanded( | ||||
|                 child: | ||||
|                     Text('punishmentType${ele.type}').tr().fontSize(16).bold(), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|           Text(ele.reason), | ||||
|           const Gap(4), | ||||
|           Text( | ||||
|             'punishmentCreatedAt'.tr(args: [ | ||||
|               DateFormat().format( | ||||
|                 ele.createdAt.toLocal(), | ||||
|               ) | ||||
|             ]), | ||||
|           ).opacity(0.8), | ||||
|           Text( | ||||
|             ele.expiredAt == null | ||||
|                 ? 'punishmentExpiredNever'.tr() | ||||
|                 : 'punishmentExpiredAt'.tr(args: [ | ||||
|                     DateFormat().format( | ||||
|                       ele.expiredAt!.toLocal(), | ||||
|                     ) | ||||
|                   ]), | ||||
|           ).opacity(0.8), | ||||
|           const Gap(8), | ||||
|           if (ele.moderator != null) | ||||
|             Column( | ||||
|               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|               children: [ | ||||
|                 Text('punishmentModerator').tr().opacity(0.75), | ||||
|                 InkWell( | ||||
|                   child: Row( | ||||
|                     children: [ | ||||
|                       AccountImage( | ||||
|                         content: ele.moderator!.avatar, | ||||
|                         radius: 8, | ||||
|                       ), | ||||
|                       const Gap(4), | ||||
|                       Text(ele.moderator?.nick ?? 'unknown'), | ||||
|                     ], | ||||
|                   ), | ||||
|                   onTap: () { | ||||
|                     GoRouter.of(context).pushNamed( | ||||
|                       'accountProfilePage', | ||||
|                       pathParameters: { | ||||
|                         'name': ele.moderator!.name, | ||||
|                       }, | ||||
|                     ); | ||||
|                   }, | ||||
|                 ), | ||||
|               ], | ||||
|             ) | ||||
|           else | ||||
|             Text('punishmentMadeBySystem').tr().opacity(0.75), | ||||
|         ], | ||||
|       ).padding(horizontal: 24, vertical: 16), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -39,7 +39,9 @@ class AccountPopoverCard extends StatelessWidget { | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|             ).padding(all: 16), | ||||
|             ).padding(all: 16) | ||||
|           else | ||||
|             const Gap(16), | ||||
|           // Top padding | ||||
|           Row( | ||||
|             crossAxisAlignment: CrossAxisAlignment.start, | ||||
| @@ -89,7 +91,9 @@ class AccountPopoverCard extends StatelessWidget { | ||||
|               data.profile?.description ?? '', | ||||
|               maxLines: 2, | ||||
|               overflow: TextOverflow.ellipsis, | ||||
|             ).padding(horizontal: 26, bottom: 8), | ||||
|             ).padding(horizontal: 26, bottom: 8) | ||||
|           else | ||||
|             const Gap(12), | ||||
|           Row( | ||||
|             crossAxisAlignment: CrossAxisAlignment.center, | ||||
|             children: [ | ||||
|   | ||||
| @@ -12,7 +12,6 @@ import 'package:go_router/go_router.dart'; | ||||
| import 'package:google_fonts/google_fonts.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:path_provider/path_provider.dart'; | ||||
| import 'package:popover/popover.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:qr_flutter/qr_flutter.dart'; | ||||
| import 'package:relative_time/relative_time.dart'; | ||||
| @@ -1274,20 +1273,11 @@ class _PostAvatar extends StatelessWidget { | ||||
|               ), | ||||
|             ), | ||||
|       onTap: () { | ||||
|         showPopover( | ||||
|           backgroundColor: Theme.of(context).colorScheme.surface, | ||||
|         showModalBottomSheet( | ||||
|           context: context, | ||||
|           transition: PopoverTransition.other, | ||||
|           bodyBuilder: (context) => SizedBox( | ||||
|             width: math.min(400, MediaQuery.of(context).size.width - 10), | ||||
|             child: PublisherPopoverCard( | ||||
|               data: data.publisher, | ||||
|             ), | ||||
|           builder: (context) => PublisherPopoverCard( | ||||
|             data: data.publisher, | ||||
|           ), | ||||
|           direction: PopoverDirection.bottom, | ||||
|           arrowHeight: 5, | ||||
|           arrowWidth: 15, | ||||
|           arrowDxOffset: -190, | ||||
|         ); | ||||
|       }, | ||||
|     ); | ||||
|   | ||||
| @@ -24,125 +24,137 @@ class PublisherPopoverCard extends StatelessWidget { | ||||
|  | ||||
|     final user = data.type == 0 ? ud.getFromCache(data.accountId) : null; | ||||
|  | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       mainAxisSize: MainAxisSize.min, | ||||
|       children: [ | ||||
|         if (data.banner.isNotEmpty) | ||||
|           Container( | ||||
|             color: Theme.of(context).colorScheme.surfaceContainer, | ||||
|             child: AspectRatio( | ||||
|               aspectRatio: 16 / 7, | ||||
|               child: AutoResizeUniversalImage( | ||||
|                 sn.getAttachmentUrl(data.banner), | ||||
|                 fit: BoxFit.cover, | ||||
|     return SingleChildScrollView( | ||||
|       child: Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           if (data.banner.isNotEmpty) | ||||
|             ClipRRect( | ||||
|               borderRadius: BorderRadius.circular(16), | ||||
|               child: Container( | ||||
|                 color: Theme.of(context).colorScheme.surfaceContainer, | ||||
|                 child: AspectRatio( | ||||
|                   aspectRatio: 16 / 7, | ||||
|                   child: AutoResizeUniversalImage( | ||||
|                     sn.getAttachmentUrl(data.banner), | ||||
|                     fit: BoxFit.cover, | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|         // Top padding | ||||
|         Gap(16), | ||||
|         Row( | ||||
|           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|           children: [ | ||||
|             AccountImage( | ||||
|               content: data.avatar, | ||||
|               radius: 20, | ||||
|               borderRadius: data.type == 1 ? 8 : 20, | ||||
|             ), | ||||
|             ).padding(all: 16) | ||||
|           else | ||||
|             // Top padding | ||||
|             Gap(16), | ||||
|             Expanded( | ||||
|               child: Column( | ||||
|                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                 mainAxisSize: MainAxisSize.min, | ||||
|                 children: [ | ||||
|                   Text(data.nick).bold(), | ||||
|                   Text('@${data.name}').fontSize(13).opacity(0.75), | ||||
|                 ], | ||||
|           Row( | ||||
|             crossAxisAlignment: CrossAxisAlignment.start, | ||||
|             children: [ | ||||
|               AccountImage( | ||||
|                 content: data.avatar, | ||||
|                 radius: 20, | ||||
|                 borderRadius: data.type == 1 ? 8 : 20, | ||||
|               ), | ||||
|             ), | ||||
|             IconButton( | ||||
|               onPressed: () { | ||||
|                 Navigator.pop(context); | ||||
|                 GoRouter.of(context).pushNamed( | ||||
|                   'postPublisher', | ||||
|                   pathParameters: {'name': data.name}, | ||||
|                 ); | ||||
|               }, | ||||
|               icon: const Icon(Symbols.chevron_right), | ||||
|               padding: EdgeInsets.zero, | ||||
|               visualDensity: const VisualDensity(horizontal: -4, vertical: -4), | ||||
|             ), | ||||
|             const Gap(8) | ||||
|           ], | ||||
|         ).padding(horizontal: 16), | ||||
|         if (user != null && user.badges.isNotEmpty) | ||||
|           Wrap( | ||||
|             spacing: 4, | ||||
|             children: user.badges | ||||
|                 .map( | ||||
|                   (ele) => AccountBadge(badge: ele), | ||||
|                 ) | ||||
|                 .toList(), | ||||
|           ).padding(horizontal: 24, top: 16), | ||||
|         const Gap(16), | ||||
|         if (data.description.isNotEmpty) | ||||
|           Text( | ||||
|             data.description, | ||||
|             maxLines: 2, | ||||
|             overflow: TextOverflow.ellipsis, | ||||
|           ).padding(horizontal: 26, bottom: 20), | ||||
|         Row( | ||||
|           children: [ | ||||
|             Expanded( | ||||
|               child: Column( | ||||
|                 mainAxisSize: MainAxisSize.min, | ||||
|                 crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                 children: [ | ||||
|                   Text('publisherSocialPoint').tr().fontSize(13).opacity(0.75), | ||||
|                   Text((data.totalUpvote - data.totalDownvote).toString()), | ||||
|                 ], | ||||
|               Gap(16), | ||||
|               Expanded( | ||||
|                 child: Column( | ||||
|                   crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                   mainAxisSize: MainAxisSize.min, | ||||
|                   children: [ | ||||
|                     Text(data.nick).bold(), | ||||
|                     Text('@${data.name}').fontSize(13).opacity(0.75), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|             SizedBox( | ||||
|               height: 20, | ||||
|               child: const VerticalDivider( | ||||
|                 thickness: 1, | ||||
|               IconButton( | ||||
|                 onPressed: () { | ||||
|                   Navigator.pop(context); | ||||
|                   GoRouter.of(context).pushNamed( | ||||
|                     'postPublisher', | ||||
|                     pathParameters: {'name': data.name}, | ||||
|                   ); | ||||
|                 }, | ||||
|                 icon: const Icon(Symbols.chevron_right), | ||||
|                 padding: EdgeInsets.zero, | ||||
|                 visualDensity: | ||||
|                     const VisualDensity(horizontal: -4, vertical: -4), | ||||
|               ), | ||||
|             ).padding(horizontal: 8), | ||||
|             Expanded( | ||||
|               child: Column( | ||||
|                 mainAxisSize: MainAxisSize.min, | ||||
|                 crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                 children: [ | ||||
|                   Text('publisherTotalUpvote').tr().fontSize(13).opacity(0.75), | ||||
|                   Text(data.totalUpvote.toString()), | ||||
|                 ], | ||||
|               const Gap(8) | ||||
|             ], | ||||
|           ).padding(horizontal: 16), | ||||
|           if (user != null && user.badges.isNotEmpty) | ||||
|             Wrap( | ||||
|               spacing: 4, | ||||
|               children: user.badges | ||||
|                   .map( | ||||
|                     (ele) => AccountBadge(badge: ele), | ||||
|                   ) | ||||
|                   .toList(), | ||||
|             ).padding(horizontal: 24, top: 16), | ||||
|           const Gap(16), | ||||
|           if (data.description.isNotEmpty) | ||||
|             Text( | ||||
|               data.description, | ||||
|               maxLines: 2, | ||||
|               overflow: TextOverflow.ellipsis, | ||||
|             ).padding(horizontal: 26, bottom: 20), | ||||
|           Row( | ||||
|             children: [ | ||||
|               Expanded( | ||||
|                 child: Column( | ||||
|                   mainAxisSize: MainAxisSize.min, | ||||
|                   crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                   children: [ | ||||
|                     Text('publisherSocialPoint') | ||||
|                         .tr() | ||||
|                         .fontSize(13) | ||||
|                         .opacity(0.75), | ||||
|                     Text((data.totalUpvote - data.totalDownvote).toString()), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|             SizedBox( | ||||
|               height: 20, | ||||
|               child: const VerticalDivider( | ||||
|                 thickness: 1, | ||||
|               SizedBox( | ||||
|                 height: 20, | ||||
|                 child: const VerticalDivider( | ||||
|                   thickness: 1, | ||||
|                 ), | ||||
|               ).padding(horizontal: 8), | ||||
|               Expanded( | ||||
|                 child: Column( | ||||
|                   mainAxisSize: MainAxisSize.min, | ||||
|                   crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                   children: [ | ||||
|                     Text('publisherTotalUpvote') | ||||
|                         .tr() | ||||
|                         .fontSize(13) | ||||
|                         .opacity(0.75), | ||||
|                     Text(data.totalUpvote.toString()), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|             ).padding(horizontal: 8), | ||||
|             Expanded( | ||||
|               child: Column( | ||||
|                 mainAxisSize: MainAxisSize.min, | ||||
|                 crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                 children: [ | ||||
|                   Text('publisherTotalDownvote') | ||||
|                       .tr() | ||||
|                       .fontSize(13) | ||||
|                       .opacity(0.75), | ||||
|                   Text(data.totalDownvote.toString()), | ||||
|                 ], | ||||
|               SizedBox( | ||||
|                 height: 20, | ||||
|                 child: const VerticalDivider( | ||||
|                   thickness: 1, | ||||
|                 ), | ||||
|               ).padding(horizontal: 8), | ||||
|               Expanded( | ||||
|                 child: Column( | ||||
|                   mainAxisSize: MainAxisSize.min, | ||||
|                   crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                   children: [ | ||||
|                     Text('publisherTotalDownvote') | ||||
|                         .tr() | ||||
|                         .fontSize(13) | ||||
|                         .opacity(0.75), | ||||
|                     Text(data.totalDownvote.toString()), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|           ], | ||||
|         ).padding(horizontal: 16), | ||||
|         // Bottom padding | ||||
|         const Gap(16), | ||||
|       ], | ||||
|             ], | ||||
|           ).padding(horizontal: 16), | ||||
|           // Bottom padding | ||||
|           const Gap(64), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user