mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-21 03:15:14 +08:00
show medal wall
show user follow time show top image title Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
@@ -16,6 +16,7 @@ import 'package:PiliPlus/pages/fan/view.dart';
|
||||
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/utils/accounts.dart';
|
||||
import 'package:PiliPlus/utils/app_scheme.dart';
|
||||
import 'package:PiliPlus/utils/extension/context_ext.dart';
|
||||
@@ -25,9 +26,12 @@ import 'package:PiliPlus/utils/extension/theme_ext.dart';
|
||||
import 'package:PiliPlus/utils/image_utils.dart';
|
||||
import 'package:PiliPlus/utils/num_utils.dart';
|
||||
import 'package:PiliPlus/utils/page_utils.dart';
|
||||
import 'package:PiliPlus/utils/platform_utils.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class UserInfoCard extends StatelessWidget {
|
||||
@@ -41,6 +45,7 @@ class UserInfoCard extends StatelessWidget {
|
||||
this.live,
|
||||
this.silence,
|
||||
required this.headerControllerBuilder,
|
||||
required this.showLiveMedalWall,
|
||||
});
|
||||
|
||||
final bool isOwner;
|
||||
@@ -51,6 +56,7 @@ class UserInfoCard extends StatelessWidget {
|
||||
final Live? live;
|
||||
final int? silence;
|
||||
final ValueGetter<PageController> headerControllerBuilder;
|
||||
final VoidCallback showLiveMedalWall;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -88,9 +94,15 @@ class UserInfoCard extends StatelessWidget {
|
||||
case UserInfoType.like:
|
||||
count = card.likes?.likeNum;
|
||||
}
|
||||
void onShowCount() => SmartDialog.showToast(
|
||||
'${type.title}: $count',
|
||||
alignment: const Alignment(0.0, -0.8),
|
||||
);
|
||||
return GestureDetector(
|
||||
behavior: .opaque,
|
||||
onTap: onTap,
|
||||
onLongPress: PlatformUtils.isMobile ? onShowCount : null,
|
||||
onSecondaryTap: PlatformUtils.isDesktop ? onShowCount : null,
|
||||
child: Align(
|
||||
alignment: type.alignment,
|
||||
widthFactor: 1.0,
|
||||
@@ -119,206 +131,241 @@ class UserInfoCard extends StatelessWidget {
|
||||
BuildContext context,
|
||||
ColorScheme colorScheme,
|
||||
bool isLight,
|
||||
) => [
|
||||
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!),
|
||||
child: Text(
|
||||
card.name!,
|
||||
strutStyle: const StrutStyle(
|
||||
height: 1,
|
||||
leading: 0,
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: (card.vip?.status ?? -1) > 0 && card.vip?.type == 2
|
||||
? colorScheme.vipColor
|
||||
: null,
|
||||
),
|
||||
),
|
||||
) {
|
||||
Widget? liveMedal;
|
||||
if (card.liveFansWearing?.detailV2 case final detailV2?) {
|
||||
Color? nameColor;
|
||||
Color? levelColor;
|
||||
Color? backgroundColor;
|
||||
try {
|
||||
nameColor = Utils.parseColor(detailV2.medalColorName!);
|
||||
levelColor = Utils.parseColor(detailV2.medalColorLevel!);
|
||||
backgroundColor = Utils.parseColor(detailV2.medalColor!);
|
||||
} catch (e, s) {
|
||||
if (kDebugMode) {
|
||||
Utils.reportError(e, s);
|
||||
}
|
||||
}
|
||||
try {
|
||||
liveMedal = GestureDetector(
|
||||
onTap: showLiveMedalWall,
|
||||
child: MedalWidget(
|
||||
medalName: detailV2.medalName!,
|
||||
level: detailV2.level!,
|
||||
backgroundColor: backgroundColor ?? colorScheme.secondaryContainer,
|
||||
nameColor: nameColor ?? colorScheme.onSecondaryContainer,
|
||||
levelColor: levelColor ?? colorScheme.onSecondaryContainer,
|
||||
),
|
||||
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 EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: StyleString.mdRadius,
|
||||
color: colorScheme.vipColor,
|
||||
),
|
||||
);
|
||||
} catch (e, s) {
|
||||
if (kDebugMode) {
|
||||
Utils.reportError(e, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
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!),
|
||||
child: Text(
|
||||
card.vip?.label?.text ?? '大会员',
|
||||
card.name!,
|
||||
strutStyle: const StrutStyle(
|
||||
height: 1,
|
||||
fontSize: 10,
|
||||
leading: 0,
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 10,
|
||||
color: Colors.white,
|
||||
color: (card.vip?.status ?? -1) > 0 && card.vip?.type == 2
|
||||
? colorScheme.vipColor
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
// if (card.nameplate?.imageSmall?.isNotEmpty == true)
|
||||
// CachedNetworkImage(
|
||||
// imageUrl: ImageUtils.thumbnailUrl(card.nameplate!.imageSmall!),
|
||||
// height: 20,
|
||||
// placeholder: (context, url) {
|
||||
// return const SizedBox.shrink();
|
||||
// },
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
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?.icon?.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,
|
||||
),
|
||||
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 EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: StyleString.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,
|
||||
),
|
||||
),
|
||||
const TextSpan(
|
||||
text: ' ',
|
||||
),
|
||||
// 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?.icon?.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),
|
||||
),
|
||||
),
|
||||
],
|
||||
TextSpan(
|
||||
text: card.officialVerify!.spliceTitle!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
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}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
...?card.spaceTag?.map(
|
||||
(item) {
|
||||
final hasUri = item.uri?.isNotEmpty == true;
|
||||
final child = Text(
|
||||
item.title ?? '',
|
||||
),
|
||||
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}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: hasUri ? colorScheme.secondary : colorScheme.outline,
|
||||
color: colorScheme.outline,
|
||||
),
|
||||
);
|
||||
if (hasUri) {
|
||||
return GestureDetector(
|
||||
onTap: () => PiliScheme.routePushFromUrl(item.uri!),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
...?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,
|
||||
),
|
||||
);
|
||||
}
|
||||
return child;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (hasUri) {
|
||||
return GestureDetector(
|
||||
onTap: () => PiliScheme.routePushFromUrl(item.uri!),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
return child;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
];
|
||||
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,
|
||||
@@ -541,6 +588,34 @@ class UserInfoCard extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
bottom: 3.5,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 125),
|
||||
child: DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: .centerLeft,
|
||||
end: .centerRight,
|
||||
colors: [
|
||||
Colors.transparent,
|
||||
Colors.black12,
|
||||
Colors.black38,
|
||||
Colors.black45,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const .only(left: 15, right: 5, bottom: 2),
|
||||
child: HeaderTitle(
|
||||
images: imgUrls,
|
||||
pageController: controller,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
@@ -788,3 +863,79 @@ class _HeaderIndicatorState extends State<HeaderIndicator> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HeaderTitle extends StatefulWidget {
|
||||
const HeaderTitle({
|
||||
super.key,
|
||||
required this.images,
|
||||
required this.pageController,
|
||||
});
|
||||
|
||||
final List<TopImage> images;
|
||||
final PageController pageController;
|
||||
|
||||
@override
|
||||
State<HeaderTitle> createState() => _HeaderTitleState();
|
||||
}
|
||||
|
||||
class _HeaderTitleState extends State<HeaderTitle> {
|
||||
late int _index;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_updateIndex();
|
||||
widget.pageController.addListener(_listener);
|
||||
}
|
||||
|
||||
void _listener() {
|
||||
_updateIndex();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void _updateIndex() {
|
||||
_index = widget.pageController.page?.round() ?? 0;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.pageController.removeListener(_listener);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final title = widget.images[_index].title;
|
||||
if (title == null) return const SizedBox.shrink();
|
||||
try {
|
||||
return Column(
|
||||
crossAxisAlignment: .end,
|
||||
children: [
|
||||
Text(
|
||||
title.title!,
|
||||
maxLines: 1,
|
||||
overflow: .ellipsis,
|
||||
style: const TextStyle(fontSize: 12, color: Colors.white),
|
||||
),
|
||||
Text(
|
||||
title.subTitle!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'digital_id_num',
|
||||
color: title.subTitleColorFormat?.colors?.isNotEmpty == true
|
||||
? Utils.parseMedalColor(
|
||||
title.subTitleColorFormat!.colors!.last,
|
||||
)
|
||||
: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} catch (e, s) {
|
||||
if (kDebugMode) {
|
||||
Utils.reportError(e, s);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user