修改vip排版

This commit is contained in:
zhx 2026-05-18 19:56:50 +08:00
parent 071ab9ffed
commit 0a3ace948a

View File

@ -14,6 +14,7 @@ import 'package:yumi/ui_kit/components/sc_compontent.dart';
import 'package:yumi/ui_kit/components/sc_debounce_widget.dart'; import 'package:yumi/ui_kit/components/sc_debounce_widget.dart';
import 'package:yumi/ui_kit/components/sc_tts.dart'; import 'package:yumi/ui_kit/components/sc_tts.dart';
import 'package:yumi/ui_kit/widgets/props/sc_data_card_resource_view.dart'; import 'package:yumi/ui_kit/widgets/props/sc_data_card_resource_view.dart';
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
class VipDetailPage extends StatefulWidget { class VipDetailPage extends StatefulWidget {
const VipDetailPage({super.key}); const VipDetailPage({super.key});
@ -470,8 +471,7 @@ class _VipDetailPageState extends State<VipDetailPage> {
Widget _buildLevelCard() { Widget _buildLevelCard() {
final assetLevel = _assetLevel; final assetLevel = _assetLevel;
final backgroundCard = _selectedBackgroundCard; final effectImage = _selectedEffectImage;
final effectImage = _selectedEffectImage ?? _selectedLongBadge;
return Center( return Center(
child: SizedBox( child: SizedBox(
width: 351.w, width: 351.w,
@ -482,10 +482,7 @@ class _VipDetailPageState extends State<VipDetailPage> {
Positioned.fill( Positioned.fill(
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(8.w), borderRadius: BorderRadius.circular(8.w),
child: _buildVipCardBackground( child: _buildVipCardBackground(assetLevel: assetLevel),
resource: backgroundCard,
assetLevel: assetLevel,
),
), ),
), ),
Positioned.fill( Positioned.fill(
@ -525,41 +522,30 @@ class _VipDetailPageState extends State<VipDetailPage> {
), ),
), ),
), ),
PositionedDirectional( if (_hasResourceUrl(effectImage))
end: 24.w, PositionedDirectional(
top: -36.w, end: 24.w,
child: _buildDynamicResourceVisual( top: -36.w,
resource: effectImage, child: _buildDynamicResourceVisual(
width: 135.w, resource: effectImage,
height: 135.w, width: 135.w,
fallbackIcon: Icons.workspace_premium_outlined, height: 135.w,
fallbackIcon: Icons.workspace_premium_outlined,
),
), ),
),
], ],
), ),
), ),
); );
} }
Widget _buildVipCardBackground({ Widget _buildVipCardBackground({required int assetLevel}) {
required SCVipResourceRes? resource, return Image.asset(
required int assetLevel,
}) {
final fallback = Image.asset(
'sc_images/vip/sc_vip_card_bg_$assetLevel.png', 'sc_images/vip/sc_vip_card_bg_$assetLevel.png',
fit: BoxFit.fill, fit: BoxFit.fill,
width: double.infinity, width: double.infinity,
height: double.infinity, height: double.infinity,
); );
if (!_hasResourceUrl(resource)) {
return fallback;
}
return SCDataCardResourceView(
sourceUrl: resource?.sourceResourceUrl,
coverUrl: resource?.previewUrl,
fit: BoxFit.fill,
fallback: fallback,
);
} }
Widget _buildLevelDivider() { Widget _buildLevelDivider() {
@ -632,19 +618,27 @@ class _VipDetailPageState extends State<VipDetailPage> {
} }
Widget _buildDecorationGrid(List<_VipFeatureData> items) { Widget _buildDecorationGrid(List<_VipFeatureData> items) {
return Wrap( return LayoutBuilder(
spacing: 10.w, builder: (context, constraints) {
runSpacing: 10.w, final spacing = 10.w;
children: final maxWidth =
items constraints.maxWidth.isFinite ? constraints.maxWidth : 314.w;
.map( final itemWidth = (maxWidth - spacing * 2) / 3;
(item) => _buildFeatureTile( return Wrap(
item, spacing: spacing,
width: item.wide ? 206.w : 98.w, runSpacing: spacing,
height: 100.w, children:
), items
) .map(
.toList(), (item) => _buildFeatureTile(
item,
width: itemWidth,
height: 100.w,
),
)
.toList(),
);
},
); );
} }
@ -772,6 +766,7 @@ class _VipDetailPageState extends State<VipDetailPage> {
resource: _selectedAvatarFrame, resource: _selectedAvatarFrame,
fallbackTitle: 'Avatar Frame', fallbackTitle: 'Avatar Frame',
fallbackIcon: Icons.person_pin_circle_outlined, fallbackIcon: Icons.person_pin_circle_outlined,
previewKind: SCStoreItemPreviewKind.avatarFrame,
); );
_addResourceItem( _addResourceItem(
items, items,
@ -790,14 +785,14 @@ class _VipDetailPageState extends State<VipDetailPage> {
resource: _selectedEntryEffect, resource: _selectedEntryEffect,
fallbackTitle: 'Entry Effect', fallbackTitle: 'Entry Effect',
fallbackIcon: Icons.airline_seat_recline_extra, fallbackIcon: Icons.airline_seat_recline_extra,
wide: true,
); );
_addResourceItem( _addResourceItem(
items, items,
resource: _selectedBackgroundCard, resource: _selectedBackgroundCard,
fallbackTitle: 'Profile Card', fallbackTitle: 'Profile Card',
fallbackIcon: Icons.badge_outlined, fallbackIcon: Icons.badge_outlined,
wide: true, previewKind: SCStoreItemPreviewKind.dataCard,
previewFit: BoxFit.cover,
); );
return items; return items;
} }
@ -815,6 +810,9 @@ class _VipDetailPageState extends State<VipDetailPage> {
required String fallbackTitle, required String fallbackTitle,
IconData? fallbackIcon, IconData? fallbackIcon,
bool wide = false, bool wide = false,
SCStoreItemPreviewKind previewKind = SCStoreItemPreviewKind.resource,
BoxFit previewFit = BoxFit.contain,
BoxFit tileFit = BoxFit.contain,
}) { }) {
if (!_hasResource(resource)) { if (!_hasResource(resource)) {
return; return;
@ -824,8 +822,12 @@ class _VipDetailPageState extends State<VipDetailPage> {
_resourceTitle(resource, fallbackTitle), _resourceTitle(resource, fallbackTitle),
null, null,
icon: fallbackIcon, icon: fallbackIcon,
resourceUrl: resource?.previewUrl, sourceUrl: resource?.sourceResourceUrl,
coverUrl: _resourceCoverUrl(resource),
wide: wide, wide: wide,
previewKind: previewKind,
previewFit: previewFit,
tileFit: tileFit,
), ),
); );
} }
@ -846,6 +848,18 @@ class _VipDetailPageState extends State<VipDetailPage> {
return fallbackTitle; return fallbackTitle;
} }
String? _resourceCoverUrl(SCVipResourceRes? resource) {
final coverUrl = resource?.coverUrl?.trim();
if (coverUrl != null && coverUrl.isNotEmpty) {
return coverUrl;
}
final cover = resource?.cover?.trim();
if (cover != null && cover.isNotEmpty) {
return cover;
}
return null;
}
Widget _buildDynamicResourceVisual({ Widget _buildDynamicResourceVisual({
required SCVipResourceRes? resource, required SCVipResourceRes? resource,
required double width, required double width,
@ -946,7 +960,7 @@ class _VipDetailPageState extends State<VipDetailPage> {
} }
Widget _buildFeatureIcon(_VipFeatureData item, {required double size}) { Widget _buildFeatureIcon(_VipFeatureData item, {required double size}) {
final url = item.resourceUrl?.trim(); final url = item.coverUrl?.trim();
if (url != null && url.isNotEmpty) { if (url != null && url.isNotEmpty) {
return ClipRRect( return ClipRRect(
borderRadius: BorderRadius.circular(6.w), borderRadius: BorderRadius.circular(6.w),
@ -973,15 +987,15 @@ class _VipDetailPageState extends State<VipDetailPage> {
} }
Widget _buildFeatureVisual(_VipFeatureData item, {required bool isWide}) { Widget _buildFeatureVisual(_VipFeatureData item, {required bool isWide}) {
final url = item.resourceUrl?.trim(); final coverUrl = item.coverUrl?.trim();
if (url != null && url.isNotEmpty) { if (coverUrl != null && coverUrl.isNotEmpty) {
return netImage( return netImage(
url: url, url: coverUrl,
fit: BoxFit.contain, fit: item.tileFit,
noDefaultImg: true, noDefaultImg: true,
errorWidget: errorWidget:
item.assetPath != null item.assetPath != null
? Image.asset(item.assetPath!, fit: BoxFit.contain) ? Image.asset(item.assetPath!, fit: item.tileFit)
: isWide : isWide
? Center(child: _buildWideRewardPlaceholder()) ? Center(child: _buildWideRewardPlaceholder())
: _buildRewardPlaceholder(item.icon ?? Icons.auto_awesome), : _buildRewardPlaceholder(item.icon ?? Icons.auto_awesome),
@ -1062,32 +1076,32 @@ class _VipDetailPageState extends State<VipDetailPage> {
required double height, required double height,
}) { }) {
final isWide = item.wide; final isWide = item.wide;
return Container( return GestureDetector(
width: width, behavior: HitTestBehavior.opaque,
height: height, onTap:
decoration: BoxDecoration( item.canPreview
borderRadius: BorderRadius.circular(6.w), ? () => showStoreBagFullScreenResourcePreview(
border: Border.all(color: const Color(0xFFEBCB76), width: 1.w), context,
color: Colors.black.withValues(alpha: 0.38), sourceUrl: item.sourceUrl,
), coverUrl: item.coverUrl,
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 8.w), previewKind: item.previewKind,
child: Column( fit: item.previewFit,
mainAxisAlignment: MainAxisAlignment.center, )
children: [ : null,
Expanded(child: _buildFeatureVisual(item, isWide: isWide)), child: Semantics(
SizedBox(height: 5.w), label: item.title,
Text( button: item.canPreview,
item.title, child: Container(
maxLines: 2, width: width,
textAlign: TextAlign.center, height: height,
overflow: TextOverflow.ellipsis, decoration: BoxDecoration(
style: TextStyle( borderRadius: BorderRadius.circular(6.w),
color: Colors.white, border: Border.all(color: const Color(0xFFEBCB76), width: 1.w),
fontSize: 12.sp, color: Colors.black.withValues(alpha: 0.38),
fontWeight: FontWeight.w400,
),
), ),
], padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 8.w),
child: Center(child: _buildFeatureVisual(item, isWide: isWide)),
),
), ),
); );
} }
@ -1420,15 +1434,27 @@ class _VipFeatureData {
this.title, this.title,
this.assetPath, { this.assetPath, {
this.icon, this.icon,
this.resourceUrl, this.sourceUrl,
this.coverUrl,
this.wide = false, this.wide = false,
this.previewKind = SCStoreItemPreviewKind.resource,
this.previewFit = BoxFit.contain,
this.tileFit = BoxFit.contain,
}); });
final String title; final String title;
final String? assetPath; final String? assetPath;
final IconData? icon; final IconData? icon;
final String? resourceUrl; final String? sourceUrl;
final String? coverUrl;
final bool wide; final bool wide;
final SCStoreItemPreviewKind previewKind;
final BoxFit previewFit;
final BoxFit tileFit;
bool get canPreview =>
(sourceUrl?.trim().isNotEmpty ?? false) ||
(coverUrl?.trim().isNotEmpty ?? false);
} }
class _VipActionButtonConfig { class _VipActionButtonConfig {