From d5cf2478d8051fb93c006cba5c392f4aaf7207b3 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Wed, 2 Apr 2025 00:51:46 +0800 Subject: [PATCH] :lipstick: Use bottom modal sheet instead of popover :sparkles: Show strike on user profile page --- lib/screens/account/profile_page.dart | 31 ++- lib/screens/account/punishments.dart | 148 +++++++------- lib/widgets/account/account_popover.dart | 8 +- lib/widgets/post/post_item.dart | 16 +- lib/widgets/post/publisher_popover.dart | 234 ++++++++++++----------- 5 files changed, 234 insertions(+), 203 deletions(-) diff --git a/lib/screens/account/profile_page.dart b/lib/screens/account/profile_page.dart index 596b93b..273b3df 100644 --- a/lib/screens/account/profile_page.dart +++ b/lib/screens/account/profile_page.dart @@ -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 ], ).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 ], ).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 ], ).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) diff --git a/lib/screens/account/punishments.dart b/lib/screens/account/punishments.dart index f100b5d..6fed3fb 100644 --- a/lib/screens/account/punishments.dart +++ b/lib/screens/account/punishments.dart @@ -107,74 +107,7 @@ class _PunishmentsScreenState extends State { 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 { ); } } + +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), + ); + } +} diff --git a/lib/widgets/account/account_popover.dart b/lib/widgets/account/account_popover.dart index 03c7615..e4bba35 100644 --- a/lib/widgets/account/account_popover.dart +++ b/lib/widgets/account/account_popover.dart @@ -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: [ diff --git a/lib/widgets/post/post_item.dart b/lib/widgets/post/post_item.dart index 7bb6046..9031223 100644 --- a/lib/widgets/post/post_item.dart +++ b/lib/widgets/post/post_item.dart @@ -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, ); }, ); diff --git a/lib/widgets/post/publisher_popover.dart b/lib/widgets/post/publisher_popover.dart index 69c04b5..d17e550 100644 --- a/lib/widgets/post/publisher_popover.dart +++ b/lib/widgets/post/publisher_popover.dart @@ -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), + ], + ), ); } }