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