show member guard

Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-04-05 12:10:18 +08:00
parent 279f21857d
commit c0b55f9af3
15 changed files with 776 additions and 300 deletions

View File

@@ -7,7 +7,9 @@ import 'package:PiliPlus/common/widgets/scroll_physics.dart';
import 'package:PiliPlus/common/widgets/view_safe_area.dart';
import 'package:PiliPlus/models/common/image_preview_type.dart';
import 'package:PiliPlus/models/common/member/user_info_type.dart';
import 'package:PiliPlus/models/model_owner.dart';
import 'package:PiliPlus/models_new/space/space/card.dart';
import 'package:PiliPlus/models_new/space/space/elec.dart';
import 'package:PiliPlus/models_new/space/space/followings_followed_upper.dart';
import 'package:PiliPlus/models_new/space/space/images.dart';
import 'package:PiliPlus/models_new/space/space/live.dart';
@@ -18,6 +20,8 @@ import 'package:PiliPlus/pages/follow/view.dart';
import 'package:PiliPlus/pages/follow_type/followed/view.dart';
import 'package:PiliPlus/pages/member/widget/header_layout_widget.dart';
import 'package:PiliPlus/pages/member/widget/medal_widget.dart';
import 'package:PiliPlus/pages/member_guard/view.dart';
import 'package:PiliPlus/pages/member_upower_rank/view.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:PiliPlus/utils/extension/context_ext.dart';
@@ -47,6 +51,10 @@ class UserInfoCard extends StatelessWidget {
this.silence,
required this.headerControllerBuilder,
required this.showLiveMedalWall,
required this.charges,
required this.chargeCount,
required this.guards,
required this.guardCount,
});
final bool isOwner;
@@ -58,6 +66,10 @@ class UserInfoCard extends StatelessWidget {
final int? silence;
final ValueGetter<PageController> headerControllerBuilder;
final VoidCallback showLiveMedalWall;
final List<ElecItem>? charges;
final Object? chargeCount;
final List<Owner>? guards;
final Object? guardCount;
@override
Widget build(BuildContext context) {
@@ -132,7 +144,22 @@ class UserInfoCard extends StatelessWidget {
BuildContext context,
ColorScheme colorScheme,
bool isLight,
bool isPortrait,
) {
return [
_buildName(context, colorScheme),
if (card.officialVerify?.desc?.isNotEmpty ?? false)
_buildVerify(colorScheme),
if (card.sign?.isNotEmpty ?? false) _buildSign(),
?_buildChargeAndGuard(colorScheme, isPortrait),
if (card.followingsFollowedUpper?.items?.isNotEmpty ?? false)
_buildFollowedUp(colorScheme, card.followingsFollowedUpper!),
_buildExtraInfo(colorScheme),
if (silence == 1) _buildBanWidget(colorScheme, isLight),
];
}
Widget _buildName(BuildContext context, ColorScheme colorScheme) {
Widget? liveMedal;
if (card.liveFansWearing?.detailV2 case final detailV2?) {
Color? nameColor;
@@ -161,212 +188,210 @@ class UserInfoCard extends StatelessWidget {
}
}
}
return [
Padding(
padding: const EdgeInsets.only(left: 20, right: 20),
child: Wrap(
spacing: 8,
runSpacing: 8,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
GestureDetector(
onTap: () => Utils.copyText(card.name!),
return Padding(
padding: const .only(left: 20, right: 20),
child: Wrap(
spacing: 8,
runSpacing: 8,
crossAxisAlignment: .center,
children: [
GestureDetector(
onTap: () => Utils.copyText(card.name!),
child: Text(
card.name!,
strutStyle: const StrutStyle(
height: 1,
leading: 0,
fontSize: 17,
fontWeight: .bold,
),
style: TextStyle(
height: 1,
fontSize: 17,
fontWeight: .bold,
color: (card.vip?.status ?? -1) > 0 && card.vip?.type == 2
? colorScheme.vipColor
: null,
),
),
),
Image.asset(
Utils.levelName(
card.levelInfo!.currentLevel!,
isSeniorMember: card.levelInfo?.identity == 2,
),
height: 11,
cacheHeight: 11.cacheSize(context),
semanticLabel: '等级${card.levelInfo?.currentLevel}',
),
if (card.vip?.status == 1)
Container(
padding: const .symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
borderRadius: Style.mdRadius,
color: colorScheme.vipColor,
),
child: Text(
card.name!,
card.vip?.label?.text ?? '大会员',
strutStyle: const StrutStyle(
height: 1,
leading: 0,
fontSize: 17,
fontWeight: FontWeight.bold,
fontSize: 10,
fontWeight: .bold,
),
style: TextStyle(
style: const TextStyle(
height: 1,
fontSize: 17,
fontWeight: FontWeight.bold,
color: (card.vip?.status ?? -1) > 0 && card.vip?.type == 2
? colorScheme.vipColor
: null,
fontSize: 10,
color: Colors.white,
fontWeight: .bold,
),
),
),
Image.asset(
Utils.levelName(
card.levelInfo!.currentLevel!,
isSeniorMember: card.levelInfo?.identity == 2,
// if (card.nameplate?.imageSmall?.isNotEmpty ?? false)
// CachedNetworkImage(
// imageUrl: ImageUtils.thumbnailUrl(card.nameplate!.imageSmall!),
// height: 20,
// placeholder: (context, url) {
// return const SizedBox.shrink();
// },
// ),
?liveMedal,
],
),
);
}
Widget _buildVerify(ColorScheme colorScheme) {
return Container(
margin: const .only(left: 20, top: 8, right: 20),
padding: const .symmetric(horizontal: 5, vertical: 2),
decoration: BoxDecoration(
borderRadius: const .all(.circular(12)),
color: colorScheme.onInverseSurface,
),
child: Text.rich(
TextSpan(
children: [
if (card.officialVerify?.spliceTitle?.isNotEmpty ?? false) ...[
WidgetSpan(
alignment: .middle,
child: DecoratedBox(
decoration: BoxDecoration(
shape: .circle,
color: colorScheme.surface,
),
child: Icon(
Icons.offline_bolt,
color: card.officialVerify?.type == 0
? const Color(0xFFFFCC00)
: Colors.lightBlueAccent,
size: 18,
),
),
),
const TextSpan(text: ' '),
],
TextSpan(
text: card.officialVerify!.spliceTitle!,
style: TextStyle(
fontSize: 12,
fontWeight: .bold,
color: colorScheme.onSurface.withValues(alpha: 0.7),
),
height: 11,
cacheHeight: 11.cacheSize(context),
semanticLabel: '等级${card.levelInfo?.currentLevel}',
),
if (card.vip?.status == 1)
Container(
padding: const .symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
borderRadius: Style.mdRadius,
color: colorScheme.vipColor,
),
child: Text(
card.vip?.label?.text ?? '大会员',
strutStyle: const StrutStyle(
height: 1,
leading: 0,
fontSize: 10,
fontWeight: FontWeight.bold,
),
style: const TextStyle(
height: 1,
fontSize: 10,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
// if (card.nameplate?.imageSmall?.isNotEmpty == true)
// CachedNetworkImage(
// imageUrl: ImageUtils.thumbnailUrl(card.nameplate!.imageSmall!),
// height: 20,
// placeholder: (context, url) {
// return const SizedBox.shrink();
// },
// ),
?liveMedal,
],
),
),
if (card.officialVerify?.desc?.isNotEmpty == true)
Container(
margin: const EdgeInsets.only(left: 20, top: 8, right: 20),
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(12)),
color: colorScheme.onInverseSurface,
),
child: Text.rich(
TextSpan(
children: [
if (card.officialVerify?.spliceTitle?.isNotEmpty == true) ...[
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: DecoratedBox(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: colorScheme.surface,
),
child: Icon(
Icons.offline_bolt,
color: card.officialVerify?.type == 0
? const Color(0xFFFFCC00)
: Colors.lightBlueAccent,
size: 18,
),
),
),
const TextSpan(
text: ' ',
),
],
TextSpan(
text: card.officialVerify!.spliceTitle!,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: colorScheme.onSurface.withValues(alpha: 0.7),
),
),
],
);
}
Widget _buildSign() {
return Padding(
padding: const .only(left: 20, top: 6, right: 20),
child: SelectableText(
card.sign!.trim().replaceAll(RegExp(r'\n{2,}'), '\n'),
style: const TextStyle(fontSize: 14),
),
);
}
Widget _buildExtraInfo(ColorScheme colorScheme) {
return Padding(
padding: const .only(left: 20, top: 6, right: 20),
child: Wrap(
spacing: 10,
runSpacing: 8,
crossAxisAlignment: .center,
children: [
GestureDetector(
onTap: () => Utils.copyText(card.mid.toString()),
child: Text(
'UID: ${card.mid}',
style: TextStyle(fontSize: 12, color: colorScheme.outline),
),
),
),
if (card.sign?.isNotEmpty == true)
Padding(
padding: const EdgeInsets.only(left: 20, top: 6, right: 20),
child: SelectableText(
card.sign!.trim().replaceAll(RegExp(r'\n{2,}'), '\n'),
style: const TextStyle(fontSize: 14),
),
),
if (card.followingsFollowedUpper?.items?.isNotEmpty == true) ...[
const SizedBox(height: 6),
_buildFollowedUp(colorScheme, card.followingsFollowedUpper!),
],
Padding(
padding: const EdgeInsets.only(left: 20, top: 6, right: 20),
child: Wrap(
spacing: 10,
runSpacing: 8,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
GestureDetector(
onTap: () => Utils.copyText(card.mid.toString()),
child: Text(
'UID: ${card.mid}',
...?card.spaceTag?.map(
(item) {
final hasUri = item.uri?.isNotEmpty ?? false;
final child = Text(
item.title ?? '',
style: TextStyle(
fontSize: 12,
color: colorScheme.outline,
color: hasUri ? colorScheme.secondary : colorScheme.outline,
),
);
if (hasUri) {
return GestureDetector(
onTap: () => PiliScheme.routePushFromUrl(item.uri!),
child: child,
);
}
return child;
},
),
],
),
);
}
Widget _buildBanWidget(ColorScheme colorScheme, bool isLight) {
return Container(
width: .infinity,
decoration: BoxDecoration(
borderRadius: const .all(.circular(6)),
color: isLight ? colorScheme.errorContainer : colorScheme.error,
),
margin: const .only(left: 20, top: 8, right: 20),
padding: const .symmetric(horizontal: 8, vertical: 4),
child: Text.rich(
TextSpan(
children: [
WidgetSpan(
alignment: .middle,
child: Icon(
Icons.info,
size: 17,
color: isLight
? colorScheme.onErrorContainer
: colorScheme.onError,
),
),
...?card.spaceTag?.map(
(item) {
final hasUri = item.uri?.isNotEmpty == true;
final child = Text(
item.title ?? '',
style: TextStyle(
fontSize: 12,
color: hasUri ? colorScheme.secondary : colorScheme.outline,
),
);
if (hasUri) {
return GestureDetector(
onTap: () => PiliScheme.routePushFromUrl(item.uri!),
child: child,
);
}
return child;
},
TextSpan(
text: ' 该账号封禁中',
style: TextStyle(
color: isLight
? colorScheme.onErrorContainer
: colorScheme.onError,
),
),
],
),
),
if (silence == 1)
Container(
width: double.infinity,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(6)),
color: isLight ? colorScheme.errorContainer : colorScheme.error,
),
margin: const EdgeInsets.only(left: 20, top: 8, right: 20),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Text.rich(
TextSpan(
children: [
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Icon(
Icons.info,
size: 17,
color: isLight
? colorScheme.onErrorContainer
: colorScheme.onError,
),
),
TextSpan(
text: ' 该账号封禁中',
style: TextStyle(
color: isLight
? colorScheme.onErrorContainer
: colorScheme.onError,
),
),
],
),
),
),
];
);
}
Column _buildRight(ColorScheme colorScheme) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
Row(
children: UserInfoType.values
@@ -392,7 +417,7 @@ class UserInfoCard extends StatelessWidget {
const SizedBox(height: 5),
Row(
spacing: 10,
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
if (!isOwner)
IconButton.outlined(
@@ -417,9 +442,9 @@ class UserInfoCard extends StatelessWidget {
width: 1.0,
color: colorScheme.outline.withValues(alpha: 0.3),
),
padding: .zero,
tapTargetSize: .padded,
padding: EdgeInsets.zero,
visualDensity: VisualDensity.compact,
visualDensity: .compact,
),
),
Expanded(
@@ -440,7 +465,7 @@ class UserInfoCard extends StatelessWidget {
children: [
if (relation != 0 && relation != 128) ...[
WidgetSpan(
alignment: PlaceholderAlignment.middle,
alignment: .middle,
child: Icon(
Icons.sort,
size: 16,
@@ -512,8 +537,8 @@ class UserInfoCard extends StatelessWidget {
) {
final imgUrls = images.collectionTopSimple?.top?.imgUrls;
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: .min,
crossAxisAlignment: .start,
children: [
HeaderLayoutWidget(
header: imgUrls != null && imgUrls.isNotEmpty
@@ -533,8 +558,8 @@ class UserInfoCard extends StatelessWidget {
actions: _buildRight(scheme),
),
const SizedBox(height: 5),
..._buildLeft(context, scheme, isLight),
if (card.prInfo?.content?.isNotEmpty == true)
..._buildLeft(context, scheme, isLight, true),
if (card.prInfo?.content?.isNotEmpty ?? false)
buildPrInfo(context, scheme, isLight, card.prInfo!),
const SizedBox(height: 5),
],
@@ -673,8 +698,8 @@ class UserInfoCard extends StatelessWidget {
: null,
colorBlendMode: filter
? isLight
? BlendMode.lighten
: BlendMode.darken
? .lighten
: .darken
: null,
fadeInDuration: const Duration(milliseconds: 120),
fadeOutDuration: const Duration(milliseconds: 120),
@@ -699,8 +724,8 @@ class UserInfoCard extends StatelessWidget {
: null;
Widget child = Container(
margin: const EdgeInsets.only(top: 8),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
margin: const .only(top: 8),
padding: const .symmetric(horizontal: 16, vertical: 10),
color: Utils.parseColor(isLight ? prInfo.bgColor : prInfo.bgColorNight),
child: Row(
children: [
@@ -721,7 +746,7 @@ class UserInfoCard extends StatelessWidget {
style: TextStyle(fontSize: 13, color: textColor),
),
),
if (prInfo.url?.isNotEmpty == true) ...[
if (prInfo.url?.isNotEmpty ?? false) ...[
const SizedBox(width: 10),
Icon(
Icons.keyboard_arrow_right,
@@ -731,7 +756,7 @@ class UserInfoCard extends StatelessWidget {
],
),
);
if (prInfo.url?.isNotEmpty == true) {
if (prInfo.url?.isNotEmpty ?? false) {
return GestureDetector(
onTap: () => PageUtils.handleWebview(prInfo.url!),
child: child,
@@ -742,8 +767,8 @@ class UserInfoCard extends StatelessWidget {
Column _buildH(BuildContext context, ColorScheme scheme, bool isLight) =>
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: .min,
crossAxisAlignment: .start,
children: [
// _buildHeader(context),
const SizedBox(height: kToolbarHeight),
@@ -751,7 +776,7 @@ class UserInfoCard extends StatelessWidget {
children: [
const SizedBox(width: 20),
Padding(
padding: EdgeInsets.only(
padding: .only(
top: 10,
bottom: card.prInfo?.content?.isNotEmpty == true ? 0 : 10,
),
@@ -761,27 +786,110 @@ class UserInfoCard extends StatelessWidget {
Expanded(
flex: 5,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: .min,
crossAxisAlignment: .start,
children: [
const SizedBox(height: 10),
..._buildLeft(context, scheme, isLight),
..._buildLeft(context, scheme, isLight, false),
const SizedBox(height: 5),
],
),
),
Expanded(
flex: 3,
child: _buildRight(scheme),
),
Expanded(flex: 3, child: _buildRight(scheme)),
const SizedBox(width: 20),
],
),
if (card.prInfo?.content?.isNotEmpty == true)
if (card.prInfo?.content?.isNotEmpty ?? false)
buildPrInfo(context, scheme, isLight, card.prInfo!),
],
);
Widget _buildChargeItem(
ColorScheme colorScheme,
List<Owner>? list,
Object? count,
String desc,
VoidCallback onTap,
) {
return GestureDetector(
onTap: onTap,
child: Row(
mainAxisSize: .min,
children: [
avatars(
gap: 10,
colorScheme: colorScheme,
users: list!.take(3),
),
const SizedBox(width: 4),
Text.rich(
TextSpan(
children: [
TextSpan(
text: NumUtils.numFormat(count),
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurfaceVariant,
),
),
TextSpan(
text: desc,
style: TextStyle(
fontSize: 13,
color: colorScheme.outline,
),
),
],
),
),
Icon(
Icons.keyboard_arrow_right,
size: 20,
color: colorScheme.outline,
),
],
),
);
}
Widget? _buildChargeAndGuard(ColorScheme colorScheme, bool isPortrait) {
final children = [
if (charges?.isNotEmpty ?? false)
_buildChargeItem(
colorScheme,
charges,
chargeCount,
'人为TA充电',
() => UpowerRankPage.toUpowerRank(
mid: card.mid!,
name: card.name!,
count: chargeCount,
),
),
if (guards?.isNotEmpty ?? false)
_buildChargeItem(
colorScheme,
guards,
guardCount,
'人加入大航海',
() => MemberGuard.toMemberGuard(
mid: card.mid!,
name: card.name!,
count: guardCount,
),
),
];
if (children.isNotEmpty) {
return Padding(
padding: const .only(left: 20, right: 20, top: 6),
child: isPortrait
? Row(mainAxisAlignment: .spaceBetween, children: children)
: Wrap(spacing: 10, runSpacing: 6, children: children),
);
}
return null;
}
Widget _buildFollowedUp(
ColorScheme colorScheme,
FollowingsFollowedUpper item,
@@ -789,34 +897,39 @@ class UserInfoCard extends StatelessWidget {
var list = item.items!;
final flag = list.length > 3;
if (flag) list = list.sublist(0, 3);
Widget child = Row(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(width: 20),
avatars(colorScheme: colorScheme, users: list),
const SizedBox(width: 4),
Flexible(
child: Text(
list.map((e) => e.name).join(''),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
color: colorScheme.onSurfaceVariant,
Widget child = Padding(
padding: const .only(left: 20, top: 6, right: 20),
child: Row(
mainAxisSize: .min,
children: [
avatars(
gap: 10,
colorScheme: colorScheme,
users: list,
),
const SizedBox(width: 4),
Flexible(
child: Text(
list.map((e) => e.name).join(''),
maxLines: 1,
overflow: .ellipsis,
style: TextStyle(
fontSize: 13,
color: colorScheme.onSurfaceVariant,
),
),
),
),
Text(
'${flag ? '${item.items!.length}' : ''}TA ',
style: TextStyle(fontSize: 13, color: colorScheme.outline),
),
Icon(
Icons.keyboard_arrow_right,
size: 20,
color: colorScheme.outline,
),
const SizedBox(width: 10),
],
Text(
'${flag ? '${item.items!.length}' : ''}TA',
style: TextStyle(fontSize: 13, color: colorScheme.outline),
),
Icon(
Icons.keyboard_arrow_right,
size: 20,
color: colorScheme.outline,
),
],
),
);
return GestureDetector(
onTap: () => FollowedPage.toFollowedPage(mid: card.mid, name: card.name),