From c2b5ef9e16cd32563a53b3dcb03bb05db625fdc7 Mon Sep 17 00:00:00 2001 From: zhx Date: Mon, 18 May 2026 12:01:26 +0800 Subject: [PATCH] fix: wrap long badge displays --- .../user/profile/person_detail_page.dart | 25 +++ .../widgets/badge/sc_user_badge_strip.dart | 204 +++++++++++++++++- .../widgets/room/room_user_info_card.dart | 10 +- 3 files changed, 228 insertions(+), 11 deletions(-) diff --git a/lib/modules/user/profile/person_detail_page.dart b/lib/modules/user/profile/person_detail_page.dart index 1cd11b2..22ebc22 100644 --- a/lib/modules/user/profile/person_detail_page.dart +++ b/lib/modules/user/profile/person_detail_page.dart @@ -436,6 +436,29 @@ class _PersonDetailPageState extends State { ); } + Widget _buildHeaderLongBadgeStrip(SocialChatUserProfileManager ref) { + return SizedBox( + width: ScreenUtil().screenWidth - 40.w, + child: SCUserBadgeStrip( + userId: ref.userProfile?.id ?? widget.tageId, + fallbackBadges: + ref.userProfile?.wearBadge + ?.where((item) => item.use ?? false) + .toList() ?? + const [], + displayScope: SCUserBadgeDisplayScope.long, + height: 45.w, + badgeHeight: 20.w, + longBadgeWidth: 62.w, + spacing: 4.w, + wrap: true, + maxWrapRows: 2, + scrollLastWrapRow: true, + reserveSpace: false, + ), + ); + } + Widget _buildHeaderIntroduction(SocialChatUserProfileManager ref) { final intro = (ref.userProfile?.autograph ?? "").trim(); return Container( @@ -674,6 +697,8 @@ class _PersonDetailPageState extends State { SizedBox(height: 5.w), _buildHeaderMeta(ref), SizedBox(height: 4.w), + _buildHeaderLongBadgeStrip(ref), + SizedBox(height: 4.w), _buildHeaderBadgeStrip(ref), SizedBox(height: 6.w), _buildHeaderIntroduction(ref), diff --git a/lib/ui_kit/widgets/badge/sc_user_badge_strip.dart b/lib/ui_kit/widgets/badge/sc_user_badge_strip.dart index 35c4a13..b42462f 100644 --- a/lib/ui_kit/widgets/badge/sc_user_badge_strip.dart +++ b/lib/ui_kit/widgets/badge/sc_user_badge_strip.dart @@ -20,6 +20,8 @@ class SCUserBadgeStrip extends StatefulWidget { this.spacing, this.maxBadges, this.wrap = false, + this.maxWrapRows, + this.scrollLastWrapRow = false, this.reserveSpace = false, this.alignment = WrapAlignment.center, }); @@ -33,6 +35,8 @@ class SCUserBadgeStrip extends StatefulWidget { final double? spacing; final int? maxBadges; final bool wrap; + final int? maxWrapRows; + final bool scrollLastWrapRow; final bool reserveSpace; final WrapAlignment alignment; @@ -164,6 +168,14 @@ class _SCUserBadgeStripState extends State { .toList(); if (widget.wrap) { + if (widget.maxWrapRows != null) { + return _buildLimitedWrapBadges( + badges, + stripHeight: stripHeight, + badgeHeight: badgeHeight, + spacing: spacing, + ); + } return SizedBox( height: stripHeight, child: Center( @@ -187,14 +199,137 @@ class _SCUserBadgeStripState extends State { ); } + Widget _buildLimitedWrapBadges( + List badges, { + required double stripHeight, + required double badgeHeight, + required double spacing, + }) { + final maxRows = widget.maxWrapRows ?? 1; + if (maxRows <= 1) { + return _buildScrollableBadgeRow( + badges, + stripHeight: stripHeight, + badgeHeight: badgeHeight, + spacing: spacing, + ); + } + + return SizedBox( + height: stripHeight, + child: LayoutBuilder( + builder: (context, constraints) { + final maxWidth = + constraints.maxWidth.isFinite + ? constraints.maxWidth + : _badgesTotalWidth(badges, badgeHeight, spacing); + final widestBadge = badges + .map((badge) => _badgeWidth(badge, badgeHeight)) + .fold( + 0, + (current, width) => width > current ? width : current, + ); + final perRow = + widestBadge <= 0 + ? badges.length + : ((maxWidth + spacing) / (widestBadge + spacing)) + .floor() + .clamp(1, badges.length); + final firstRows = maxRows - 1; + final fixedCount = (perRow * firstRows).clamp(0, badges.length); + final fixedBadges = badges.take(fixedCount).toList(); + final lastRowBadges = badges.skip(fixedCount).toList(); + final rows = []; + + for (var start = 0; start < fixedBadges.length; start += perRow) { + final end = + start + perRow > fixedBadges.length + ? fixedBadges.length + : start + perRow; + rows.add( + _buildBadgeRow( + fixedBadges.sublist(start, end), + badgeHeight: badgeHeight, + spacing: spacing, + ), + ); + } + + if (lastRowBadges.isNotEmpty) { + rows.add( + widget.scrollLastWrapRow + ? _buildScrollableBadgeRow( + lastRowBadges, + stripHeight: badgeHeight, + badgeHeight: badgeHeight, + spacing: spacing, + ) + : _buildBadgeRow( + lastRowBadges.take(perRow).toList(), + badgeHeight: badgeHeight, + spacing: spacing, + ), + ); + } + + if (rows.isEmpty) { + return const SizedBox.shrink(); + } + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: _withVerticalSpacing(rows, 5.w), + ); + }, + ), + ); + } + + Widget _buildBadgeRow( + List badges, { + required double badgeHeight, + required double spacing, + }) { + return SizedBox( + height: badgeHeight, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: _rowAlignment, + children: _withSpacing( + badges + .map((badge) => _buildBadge(badge, badgeHeight: badgeHeight)) + .toList(), + spacing, + ), + ), + ); + } + + Widget _buildScrollableBadgeRow( + List badges, { + required double stripHeight, + required double badgeHeight, + required double spacing, + }) { + return SizedBox( + height: stripHeight, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + physics: const BouncingScrollPhysics(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: _withSpacing( + badges + .map((badge) => _buildBadge(badge, badgeHeight: badgeHeight)) + .toList(), + spacing, + ), + ), + ), + ); + } + Widget _buildBadge(WearBadge badge, {required double badgeHeight}) { - final type = badge.type?.trim().toUpperCase() ?? ""; - final sourceType = badge.sourceType?.trim().toUpperCase() ?? ""; - final isLongBadge = type == "LONG" || type == "VIP" || sourceType == "VIP"; - final width = - isLongBadge - ? (widget.longBadgeWidth ?? badgeHeight * 2.2) - : badgeHeight; + final width = _badgeWidth(badge, badgeHeight); return netImage( url: _badgeUrl(badge), width: width, @@ -206,6 +341,47 @@ class _SCUserBadgeStripState extends State { ); } + double _badgeWidth(WearBadge badge, double badgeHeight) { + final type = badge.type?.trim().toUpperCase() ?? ""; + final sourceType = badge.sourceType?.trim().toUpperCase() ?? ""; + final isLongBadge = type == "LONG" || type == "VIP" || sourceType == "VIP"; + return isLongBadge + ? (widget.longBadgeWidth ?? badgeHeight * 2.2) + : badgeHeight; + } + + double _badgesTotalWidth( + List badges, + double badgeHeight, + double spacing, + ) { + if (badges.isEmpty) { + return 0; + } + final badgeWidth = badges.fold( + 0, + (sum, badge) => sum + _badgeWidth(badge, badgeHeight), + ); + return badgeWidth + spacing * (badges.length - 1); + } + + MainAxisAlignment get _rowAlignment { + switch (widget.alignment) { + case WrapAlignment.start: + return MainAxisAlignment.start; + case WrapAlignment.end: + return MainAxisAlignment.end; + case WrapAlignment.spaceBetween: + return MainAxisAlignment.spaceBetween; + case WrapAlignment.spaceAround: + return MainAxisAlignment.spaceAround; + case WrapAlignment.spaceEvenly: + return MainAxisAlignment.spaceEvenly; + case WrapAlignment.center: + return MainAxisAlignment.center; + } + } + String _badgeUrl(WearBadge badge) { return (badge.selectUrl ?? "").trim(); } @@ -223,6 +399,20 @@ class _SCUserBadgeStripState extends State { } return spaced; } + + List _withVerticalSpacing(List children, double spacing) { + if (children.length < 2) { + return children; + } + final spaced = []; + for (var i = 0; i < children.length; i++) { + if (i > 0) { + spaced.add(SizedBox(height: spacing)); + } + spaced.add(children[i]); + } + return spaced; + } } class _BadgeFutureCacheEntry { diff --git a/lib/ui_kit/widgets/room/room_user_info_card.dart b/lib/ui_kit/widgets/room/room_user_info_card.dart index e2b05ca..08b559b 100644 --- a/lib/ui_kit/widgets/room/room_user_info_card.dart +++ b/lib/ui_kit/widgets/room/room_user_info_card.dart @@ -562,16 +562,18 @@ class _RoomUserInfoCardState extends State { SocialChatUserProfile? profile, ) { return ConstrainedBox( - constraints: BoxConstraints(maxWidth: 64.w), + constraints: BoxConstraints(maxWidth: 140.w), child: SCUserBadgeStrip( userId: userId, fallbackBadges: _roomCardFallbackBadges(ref, profile), displayScope: SCUserBadgeDisplayScope.long, - height: 22.w, + height: 45.w, badgeHeight: 20.w, longBadgeWidth: 62.w, - spacing: 0, - maxBadges: 1, + spacing: 4.w, + wrap: true, + maxWrapRows: 2, + scrollLastWrapRow: true, reserveSpace: false, ), );