From a800a0f941d8539c768615959c494535b341f30c Mon Sep 17 00:00:00 2001 From: zhx Date: Mon, 18 May 2026 13:23:38 +0800 Subject: [PATCH] fix: preview store item effects --- .../store/chatbox/store_chatbox_page.dart | 147 ++-------- .../store/data_card/store_data_card_page.dart | 119 ++------ .../store/headdress/store_headdress_page.dart | 145 +--------- .../store/mountains/store_mountains_page.dart | 119 +------- lib/modules/store/theme/store_theme_page.dart | 142 +--------- .../widgets/store/store_bag_page_helpers.dart | 259 ++++++++++++++++-- 6 files changed, 315 insertions(+), 616 deletions(-) diff --git a/lib/modules/store/chatbox/store_chatbox_page.dart b/lib/modules/store/chatbox/store_chatbox_page.dart index 15e30e8..53d4178 100644 --- a/lib/modules/store/chatbox/store_chatbox_page.dart +++ b/lib/modules/store/chatbox/store_chatbox_page.dart @@ -1,17 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:yumi/app_localizations.dart'; -import 'package:yumi/ui_kit/components/sc_compontent.dart'; -import 'package:yumi/ui_kit/components/sc_tts.dart'; -import 'package:yumi/app/constants/sc_global_config.dart'; -import 'package:provider/provider.dart'; import 'package:yumi/ui_kit/components/sc_page_list.dart'; -import 'package:yumi/shared/tools/sc_loading_manager.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart'; import 'package:yumi/shared/business_logic/models/res/store_list_res.dart'; -import 'package:yumi/services/auth/user_profile_manager.dart'; import 'package:yumi/ui_kit/widgets/store/props_store_chatbox_detail_dialog.dart'; import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart'; @@ -20,8 +13,10 @@ import '../../../shared/data_sources/models/enum/sc_props_type.dart'; ///聊天气泡框 class StoreChatboxPage extends SCPageList { + const StoreChatboxPage({super.key}); + @override - _StoreChatboxPageState createState() => _StoreChatboxPageState(); + SCPageListState createState() => _StoreChatboxPageState(); } class _StoreChatboxPageState @@ -41,7 +36,7 @@ class _StoreChatboxPageState crossAxisCount: gridViewCount, mainAxisSpacing: 12.w, crossAxisSpacing: 12.w, - childAspectRatio: 0.78, + childAspectRatio: 0.88, ); loadData(1); } @@ -59,100 +54,15 @@ class _StoreChatboxPageState @override Widget buildItem(StoreListResBean res) { - return GestureDetector( - child: Container( - decoration: BoxDecoration( - color: - res.isSelecte - ? const Color(0xff18F2B1).withValues(alpha: 0.1) - : SCGlobalConfig.businessLogicStrategy - .getStoreItemBackgroundColor(), - border: Border.all( - color: - res.isSelecte - ? SCGlobalConfig.businessLogicStrategy - .getStoreItemSelectedBorderColor() - : SCGlobalConfig.businessLogicStrategy - .getStoreItemUnselectedBorderColor(), - width: 1.w, - ), - borderRadius: BorderRadius.all(Radius.circular(8.w)), - ), - child: Stack( - children: [ - Column( - children: [ - SizedBox(height: 10.w), - netImage( - url: res.res.propsResources?.cover ?? "", - height: 45.w, - fit: BoxFit.contain, - ), - SizedBox(height: 10.w), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(width: 10.w), - Expanded( - child: Text.rich( - textAlign: TextAlign.center, - TextSpan( - children: [ - WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: Image.asset( - SCGlobalConfig.businessLogicStrategy - .getStoreItemGoldIcon(), - width: 22.w, - ), - ), - TextSpan(text: " "), - disCount < 1 - ? TextSpan( - text: "${res.res.propsPrices![0].amount}", - style: TextStyle( - fontSize: 12.sp, - color: - SCGlobalConfig.businessLogicStrategy - .getStoreItemPriceTextColor(), - fontWeight: FontWeight.w600, - decoration: TextDecoration.lineThrough, - ), - ) - : TextSpan(), - disCount < 1 ? TextSpan(text: " ") : TextSpan(), - TextSpan( - text: - "${((res.res.propsPrices![0].amount ?? 0) * disCount).toInt()}/${res.res.propsPrices![0].days} ${SCAppLocalizations.of(context)!.day}", - style: TextStyle( - fontSize: 12.sp, - color: - SCGlobalConfig.businessLogicStrategy - .getStoreItemPriceTextColor(), - fontWeight: FontWeight.w600, - ), - ), - ], - ), - strutStyle: StrutStyle( - height: 1.3, // 行高倍数 - fontWeight: FontWeight.w500, - forceStrutHeight: true, // 强制应用行高 - ), - ), - ), - SizedBox(width: 10.w), - ], - ), - ], - ), - ], - ), - ), - onTap: () { - _selectItem(res); - _showDetail(res.res); - }, + return SCStoreGridItemCard( + res: res.res, + selected: res.isSelecte, + discount: disCount, + coverHeight: 45.w, + previewWidth: 88.w, + previewHeight: 58.w, + onPreviewTap: () => _togglePreview(res), + onDetailsTap: () => _showDetail(res.res), ); } @@ -191,39 +101,14 @@ class _StoreChatboxPageState ); } - void _selectItem(StoreListResBean res) { + void _togglePreview(StoreListResBean res) { + final shouldSelect = !res.isSelecte; for (var value in items) { value.isSelecte = false; } - res.isSelecte = true; + res.isSelecte = shouldSelect; setState(() {}); } - - void _buy(StoreListResBean res) { - if (res.res.propsPrices!.isEmpty) { - return; - } - SCLoadingManager.show(); - num days = res.res.propsPrices![0].days ?? 0; - SCStoreRepositoryImp() - .storePurchasing( - res.res.id ?? "", - SCPropsType.CHAT_BUBBLE.name, - SCCurrencyType.GOLD.name, - "$days", - ) - .then((value) { - SCTts.show(SCAppLocalizations.of(context)!.purchaseIsSuccessful); - Provider.of( - context, - listen: false, - ).updateBalance(value); - SCLoadingManager.hide(); - }) - .catchError((e) { - SCLoadingManager.hide(); - }); - } } class StoreListResBean { diff --git a/lib/modules/store/data_card/store_data_card_page.dart b/lib/modules/store/data_card/store_data_card_page.dart index 71ffb85..05684bb 100644 --- a/lib/modules/store/data_card/store_data_card_page.dart +++ b/lib/modules/store/data_card/store_data_card_page.dart @@ -1,13 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:yumi/app/constants/sc_global_config.dart'; -import 'package:yumi/app_localizations.dart'; import 'package:yumi/shared/business_logic/models/res/store_list_res.dart'; import 'package:yumi/shared/data_sources/models/enum/sc_currency_type.dart'; import 'package:yumi/shared/data_sources/models/enum/sc_props_type.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart'; -import 'package:yumi/ui_kit/components/sc_compontent.dart'; import 'package:yumi/ui_kit/components/sc_page_list.dart'; import 'package:yumi/ui_kit/widgets/store/props_store_data_card_detail_dialog.dart'; import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart'; @@ -35,7 +32,7 @@ class StoreDataCardPageState crossAxisCount: gridViewCount, mainAxisSpacing: 12.w, crossAxisSpacing: 12.w, - childAspectRatio: 0.78, + childAspectRatio: 0.88, ); loadData(1); } @@ -54,101 +51,20 @@ class StoreDataCardPageState @override Widget buildItem(StoreDataCardListResBean item) { final res = item; - final prices = res.res.propsPrices ?? const []; - final firstPrice = prices.isNotEmpty ? prices.first : null; - return GestureDetector( - child: Container( - decoration: BoxDecoration( - color: - res.isSelecte - ? const Color(0xff18F2B1).withValues(alpha: 0.1) - : SCGlobalConfig.businessLogicStrategy - .getStoreItemBackgroundColor(), - border: Border.all( - color: - res.isSelecte - ? SCGlobalConfig.businessLogicStrategy - .getStoreItemSelectedBorderColor() - : SCGlobalConfig.businessLogicStrategy - .getStoreItemUnselectedBorderColor(), - width: 1.w, - ), - borderRadius: BorderRadius.all(Radius.circular(8.w)), - ), - child: Column( - children: [ - SizedBox(height: 10.w), - ClipRRect( - borderRadius: BorderRadius.circular(6.w), - child: netImage( - url: res.res.propsResources?.cover ?? "", - width: 72.w, - height: 50.w, - fit: BoxFit.cover, - ), - ), - SizedBox(height: 12.w), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(width: 10.w), - Expanded( - child: Text.rich( - textAlign: TextAlign.center, - TextSpan( - children: [ - WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: Image.asset( - SCGlobalConfig.businessLogicStrategy - .getStoreItemGoldIcon(), - width: 22.w, - ), - ), - const TextSpan(text: " "), - if (disCount < 1) - TextSpan( - text: "${firstPrice?.amount ?? 0}", - style: TextStyle( - fontSize: 12.sp, - color: - SCGlobalConfig.businessLogicStrategy - .getStoreItemPriceTextColor(), - fontWeight: FontWeight.w600, - decoration: TextDecoration.lineThrough, - ), - ), - if (disCount < 1) const TextSpan(text: " "), - TextSpan( - text: - "${((firstPrice?.amount ?? 0) * disCount).toInt()}/${firstPrice?.days ?? 0} ${SCAppLocalizations.of(context)!.day}", - style: TextStyle( - fontSize: 12.sp, - color: - SCGlobalConfig.businessLogicStrategy - .getStoreItemPriceTextColor(), - fontWeight: FontWeight.w600, - ), - ), - ], - ), - strutStyle: const StrutStyle( - height: 1.3, - fontWeight: FontWeight.w500, - forceStrutHeight: true, - ), - ), - ), - SizedBox(width: 10.w), - ], - ), - ], - ), - ), - onTap: () { - _selectItem(res); - _showDetail(res.res); - }, + return SCStoreGridItemCard( + res: res.res, + selected: res.isSelecte, + discount: disCount, + previewKind: SCStoreItemPreviewKind.dataCard, + coverWidth: 72.w, + coverHeight: 50.w, + previewWidth: 88.w, + previewHeight: 58.w, + coverFit: BoxFit.cover, + previewFit: BoxFit.cover, + coverBorderRadius: BorderRadius.circular(6.w), + onPreviewTap: () => _togglePreview(res), + onDetailsTap: () => _showDetail(res.res), ); } @@ -184,11 +100,12 @@ class StoreDataCardPageState ); } - void _selectItem(StoreDataCardListResBean res) { + void _togglePreview(StoreDataCardListResBean res) { + final shouldSelect = !res.isSelecte; for (final value in items) { value.isSelecte = false; } - res.isSelecte = true; + res.isSelecte = shouldSelect; setState(() {}); } } diff --git a/lib/modules/store/headdress/store_headdress_page.dart b/lib/modules/store/headdress/store_headdress_page.dart index d9c8564..3d51c15 100644 --- a/lib/modules/store/headdress/store_headdress_page.dart +++ b/lib/modules/store/headdress/store_headdress_page.dart @@ -1,16 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:yumi/app_localizations.dart'; -import 'package:yumi/ui_kit/components/sc_compontent.dart'; -import 'package:yumi/ui_kit/components/sc_tts.dart'; -import 'package:yumi/app/constants/sc_global_config.dart'; -import 'package:yumi/shared/tools/sc_loading_manager.dart'; -import 'package:provider/provider.dart'; import 'package:yumi/ui_kit/components/sc_page_list.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart'; import 'package:yumi/shared/business_logic/models/res/store_list_res.dart'; -import 'package:yumi/services/auth/user_profile_manager.dart'; import 'package:yumi/ui_kit/widgets/store/props_store_headdress_detail_dialog.dart'; import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart'; @@ -19,8 +12,10 @@ import '../../../shared/data_sources/models/enum/sc_props_type.dart'; ///头饰 class StoreHeaddressPage extends SCPageList { + const StoreHeaddressPage({super.key}); + @override - _StoreHeaddressPageState createState() => _StoreHeaddressPageState(); + SCPageListState createState() => _StoreHeaddressPageState(); } class _StoreHeaddressPageState @@ -40,7 +35,7 @@ class _StoreHeaddressPageState crossAxisCount: gridViewCount, mainAxisSpacing: 12.w, crossAxisSpacing: 12.w, - childAspectRatio: 0.78, + childAspectRatio: 0.88, ); loadData(1); } @@ -58,100 +53,13 @@ class _StoreHeaddressPageState @override Widget buildItem(StoreListResBean res) { - return GestureDetector( - child: Container( - decoration: BoxDecoration( - color: - res.isSelecte - ? const Color(0xff18F2B1).withValues(alpha: 0.1) - : SCGlobalConfig.businessLogicStrategy - .getStoreItemBackgroundColor(), - border: Border.all( - color: - res.isSelecte - ? SCGlobalConfig.businessLogicStrategy - .getStoreItemSelectedBorderColor() - : SCGlobalConfig.businessLogicStrategy - .getStoreItemUnselectedBorderColor(), - width: 1.w, - ), - borderRadius: BorderRadius.all(Radius.circular(8.w)), - ), - child: Stack( - children: [ - Column( - children: [ - SizedBox(height: 10.w), - netImage( - url: res.res.propsResources?.cover ?? "", - width: 55.w, - height: 55.w, - ), - SizedBox(height: 10.w), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(width: 10.w), - Expanded( - child: Text.rich( - textAlign: TextAlign.center, - TextSpan( - children: [ - WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: Image.asset( - SCGlobalConfig.businessLogicStrategy - .getStoreItemGoldIcon(), - width: 22.w, - ), - ), - TextSpan(text: " "), - disCount < 1 - ? TextSpan( - text: "${res.res.propsPrices![0].amount}", - style: TextStyle( - fontSize: 12.sp, - color: - SCGlobalConfig.businessLogicStrategy - .getStoreItemPriceTextColor(), - fontWeight: FontWeight.w600, - decoration: TextDecoration.lineThrough, - ), - ) - : TextSpan(), - disCount < 1 ? TextSpan(text: " ") : TextSpan(), - TextSpan( - text: - "${((res.res.propsPrices![0].amount ?? 0) * disCount).toInt()}/${res.res.propsPrices![0].days} ${SCAppLocalizations.of(context)!.day}", - style: TextStyle( - fontSize: 12.sp, - color: - SCGlobalConfig.businessLogicStrategy - .getStoreItemPriceTextColor(), - fontWeight: FontWeight.w600, - ), - ), - ], - ), - strutStyle: StrutStyle( - height: 1.3, // 行高倍数 - fontWeight: FontWeight.w500, - forceStrutHeight: true, // 强制应用行高 - ), - ), - ), - SizedBox(width: 10.w), - ], - ), - ], - ), - ], - ), - ), - onTap: () { - _selectItem(res); - _showDetail(res.res); - }, + return SCStoreGridItemCard( + res: res.res, + selected: res.isSelecte, + discount: disCount, + previewKind: SCStoreItemPreviewKind.avatarFrame, + onPreviewTap: () => _togglePreview(res), + onDetailsTap: () => _showDetail(res.res), ); } @@ -190,39 +98,14 @@ class _StoreHeaddressPageState ); } - void _selectItem(StoreListResBean res) { + void _togglePreview(StoreListResBean res) { + final shouldSelect = !res.isSelecte; for (var value in items) { value.isSelecte = false; } - res.isSelecte = true; + res.isSelecte = shouldSelect; setState(() {}); } - - void _buy(StoreListResBean res) { - if (res.res.propsPrices!.isEmpty) { - return; - } - SCLoadingManager.show(); - num days = res.res.propsPrices![0].days ?? 0; - SCStoreRepositoryImp() - .storePurchasing( - res.res.id ?? "", - SCPropsType.AVATAR_FRAME.name, - SCCurrencyType.GOLD.name, - "$days", - ) - .then((value) { - SCTts.show(SCAppLocalizations.of(context)!.purchaseIsSuccessful); - Provider.of( - context, - listen: false, - ).updateBalance(value); - SCLoadingManager.hide(); - }) - .catchError((e) { - SCLoadingManager.hide(); - }); - } } class StoreListResBean { diff --git a/lib/modules/store/mountains/store_mountains_page.dart b/lib/modules/store/mountains/store_mountains_page.dart index c1d8819..69d4428 100644 --- a/lib/modules/store/mountains/store_mountains_page.dart +++ b/lib/modules/store/mountains/store_mountains_page.dart @@ -1,10 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:yumi/app_localizations.dart'; -import 'package:yumi/ui_kit/components/sc_compontent.dart'; import 'package:yumi/ui_kit/components/sc_page_list.dart'; -import 'package:yumi/app/constants/sc_global_config.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart'; import 'package:yumi/shared/business_logic/models/res/store_list_res.dart'; import 'package:yumi/ui_kit/widgets/store/props_store_mountains_detail_dialog.dart'; @@ -16,10 +13,10 @@ import '../../../shared/data_sources/models/enum/sc_props_type.dart'; ///坐骑 class StoreMountainsPage extends SCPageList { - StoreMountainsPage(); + const StoreMountainsPage({super.key}); @override - _StoreMountainsPageState createState() => _StoreMountainsPageState(); + SCPageListState createState() => _StoreMountainsPageState(); } class _StoreMountainsPageState @@ -39,16 +36,11 @@ class _StoreMountainsPageState crossAxisCount: gridViewCount, mainAxisSpacing: 12.w, crossAxisSpacing: 12.w, - childAspectRatio: 0.78, + childAspectRatio: 0.88, ); loadData(1); } - @override - void dispose() { - super.dispose(); - } - @override Widget build(BuildContext context) { return Scaffold( @@ -62,100 +54,12 @@ class _StoreMountainsPageState @override Widget buildItem(StoreListResBean res) { - return GestureDetector( - child: Container( - decoration: BoxDecoration( - color: - res.isSelecte - ? const Color(0xff18F2B1).withValues(alpha: 0.1) - : SCGlobalConfig.businessLogicStrategy - .getStoreItemBackgroundColor(), - border: Border.all( - color: - res.isSelecte - ? SCGlobalConfig.businessLogicStrategy - .getStoreItemSelectedBorderColor() - : SCGlobalConfig.businessLogicStrategy - .getStoreItemUnselectedBorderColor(), - width: 1.w, - ), - borderRadius: BorderRadius.all(Radius.circular(8.w)), - ), - child: Stack( - children: [ - Column( - children: [ - SizedBox(height: 10.w), - netImage( - url: res.res.propsResources?.cover ?? "", - width: 55.w, - height: 55.w, - ), - SizedBox(height: 10.w), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(width: 10.w), - Expanded( - child: Text.rich( - textAlign: TextAlign.center, - TextSpan( - children: [ - WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: Image.asset( - SCGlobalConfig.businessLogicStrategy - .getStoreItemGoldIcon(), - width: 22.w, - ), - ), - TextSpan(text: " "), - disCount < 1 - ? TextSpan( - text: "${res.res.propsPrices![0].amount}", - style: TextStyle( - fontSize: 12.sp, - color: - SCGlobalConfig.businessLogicStrategy - .getStoreItemPriceTextColor(), - fontWeight: FontWeight.w600, - decoration: TextDecoration.lineThrough, - ), - ) - : TextSpan(), - disCount < 1 ? TextSpan(text: " ") : TextSpan(), - TextSpan( - text: - "${((res.res.propsPrices![0].amount ?? 0) * disCount).toInt()}/${res.res.propsPrices![0].days} ${SCAppLocalizations.of(context)!.day}", - style: TextStyle( - fontSize: 12.sp, - color: - SCGlobalConfig.businessLogicStrategy - .getStoreItemPriceTextColor(), - fontWeight: FontWeight.w600, - ), - ), - ], - ), - strutStyle: StrutStyle( - height: 1.3, // 行高倍数 - fontWeight: FontWeight.w500, - forceStrutHeight: true, // 强制应用行高 - ), - ), - ), - SizedBox(width: 10.w), - ], - ), - ], - ), - ], - ), - ), - onTap: () { - _selectItem(res); - _showDetail(res.res); - }, + return SCStoreGridItemCard( + res: res.res, + selected: res.isSelecte, + discount: disCount, + onPreviewTap: () => _togglePreview(res), + onDetailsTap: () => _showDetail(res.res), ); } @@ -194,11 +98,12 @@ class _StoreMountainsPageState ); } - void _selectItem(StoreListResBean res) { + void _togglePreview(StoreListResBean res) { + final shouldSelect = !res.isSelecte; for (var value in items) { value.isSelecte = false; } - res.isSelecte = true; + res.isSelecte = shouldSelect; setState(() {}); } } diff --git a/lib/modules/store/theme/store_theme_page.dart b/lib/modules/store/theme/store_theme_page.dart index c1da299..d7eebbe 100644 --- a/lib/modules/store/theme/store_theme_page.dart +++ b/lib/modules/store/theme/store_theme_page.dart @@ -1,16 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:provider/provider.dart'; -import 'package:yumi/app_localizations.dart'; -import 'package:yumi/ui_kit/components/sc_compontent.dart'; import 'package:yumi/ui_kit/components/sc_page_list.dart'; -import 'package:yumi/ui_kit/components/sc_tts.dart'; -import 'package:yumi/app/constants/sc_global_config.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart'; import 'package:yumi/shared/business_logic/models/res/store_list_res.dart'; -import 'package:yumi/services/auth/user_profile_manager.dart'; import 'package:yumi/ui_kit/widgets/store/props_store_theme_detail_dialog.dart'; import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart'; import 'package:yumi/modules/store/headdress/store_headdress_page.dart'; @@ -20,10 +14,10 @@ import '../../../shared/data_sources/models/enum/sc_props_type.dart'; ///房间主题 class StoreThemePage extends SCPageList { - StoreThemePage(); + const StoreThemePage({super.key}); @override - _StoreThemePagePageState createState() => _StoreThemePagePageState(); + SCPageListState createState() => _StoreThemePagePageState(); } class _StoreThemePagePageState @@ -43,7 +37,7 @@ class _StoreThemePagePageState crossAxisCount: gridViewCount, mainAxisSpacing: 12.w, crossAxisSpacing: 12.w, - childAspectRatio: 0.78, + childAspectRatio: 0.88, ); loadData(1); } @@ -61,101 +55,13 @@ class _StoreThemePagePageState @override Widget buildItem(StoreListResBean res) { - return GestureDetector( - child: Container( - decoration: BoxDecoration( - color: - res.isSelecte - ? const Color(0xff18F2B1).withValues(alpha: 0.1) - : SCGlobalConfig.businessLogicStrategy - .getStoreItemBackgroundColor(), - border: Border.all( - color: - res.isSelecte - ? SCGlobalConfig.businessLogicStrategy - .getStoreItemSelectedBorderColor() - : SCGlobalConfig.businessLogicStrategy - .getStoreItemUnselectedBorderColor(), - width: 1.w, - ), - borderRadius: BorderRadius.all(Radius.circular(8.w)), - ), - child: Stack( - children: [ - Column( - children: [ - SizedBox(height: 10.w), - netImage( - url: res.res.propsResources?.cover ?? "", - width: 55.w, - height: 55.w, - borderRadius: BorderRadius.all(Radius.circular(8.w)), - ), - SizedBox(height: 10.w), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(width: 10.w), - Expanded( - child: Text.rich( - textAlign: TextAlign.center, - TextSpan( - children: [ - WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: Image.asset( - SCGlobalConfig.businessLogicStrategy - .getStoreItemGoldIcon(), - width: 22.w, - ), - ), - TextSpan(text: " "), - disCount < 1 - ? TextSpan( - text: "${res.res.propsPrices![0].amount}", - style: TextStyle( - fontSize: 12.sp, - color: - SCGlobalConfig.businessLogicStrategy - .getStoreItemPriceTextColor(), - fontWeight: FontWeight.w600, - decoration: TextDecoration.lineThrough, - ), - ) - : TextSpan(), - disCount < 1 ? TextSpan(text: " ") : TextSpan(), - TextSpan( - text: - "${((res.res.propsPrices![0].amount ?? 0) * disCount).toInt()}/${res.res.propsPrices![0].days} ${SCAppLocalizations.of(context)!.day}", - style: TextStyle( - fontSize: 12.sp, - color: - SCGlobalConfig.businessLogicStrategy - .getStoreItemPriceTextColor(), - fontWeight: FontWeight.w600, - ), - ), - ], - ), - strutStyle: StrutStyle( - height: 1.3, // 行高倍数 - fontWeight: FontWeight.w500, - forceStrutHeight: true, // 强制应用行高 - ), - ), - ), - SizedBox(width: 10.w), - ], - ), - ], - ), - ], - ), - ), - onTap: () { - _selectItem(res); - _showDetail(res.res); - }, + return SCStoreGridItemCard( + res: res.res, + selected: res.isSelecte, + discount: disCount, + coverBorderRadius: BorderRadius.all(Radius.circular(8.w)), + onPreviewTap: () => _togglePreview(res), + onDetailsTap: () => _showDetail(res.res), ); } @@ -194,34 +100,12 @@ class _StoreThemePagePageState } } - void _selectItem(StoreListResBean res) { + void _togglePreview(StoreListResBean res) { + final shouldSelect = !res.isSelecte; for (var value in items) { value.isSelecte = false; } - res.isSelecte = true; + res.isSelecte = shouldSelect; setState(() {}); } - - void _buy(StoreListResBean res) { - num days = 0; - if (res.res.propsPrices!.length > 1) { - days = res.res.propsPrices![1].days ?? 0; - } else { - days = res.res.propsPrices![0].days ?? 0; - } - SCStoreRepositoryImp() - .storePurchasing( - res.res.id ?? "", - SCPropsType.THEME.name, - SCCurrencyType.GOLD.name, - "$days", - ) - .then((value) { - SCTts.show(SCAppLocalizations.of(context)!.purchaseIsSuccessful); - Provider.of( - context, - listen: false, - ).updateBalance(value); - }); - } } diff --git a/lib/ui_kit/widgets/store/store_bag_page_helpers.dart b/lib/ui_kit/widgets/store/store_bag_page_helpers.dart index df7c0c8..a16801f 100644 --- a/lib/ui_kit/widgets/store/store_bag_page_helpers.dart +++ b/lib/ui_kit/widgets/store/store_bag_page_helpers.dart @@ -1,6 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:yumi/app/constants/sc_global_config.dart'; +import 'package:yumi/app_localizations.dart'; +import 'package:yumi/shared/business_logic/models/res/store_list_res.dart'; +import 'package:yumi/shared/data_sources/sources/local/user_manager.dart'; +import 'package:yumi/ui_kit/components/sc_compontent.dart'; import 'package:yumi/ui_kit/components/text/sc_text.dart'; +import 'package:yumi/ui_kit/widgets/props/sc_data_card_resource_view.dart'; const Duration _kStoreBagSkeletonAnimationDuration = Duration( milliseconds: 1450, @@ -11,6 +17,8 @@ const Color _kStoreBagSkeletonBoneBase = Color(0xFF2B5C53); const Color _kStoreBagSkeletonBoneHighlight = Color(0xFF5F8177); const Color _kStoreBagSkeletonBorder = Color(0x66D8B57B); +enum SCStoreItemPreviewKind { resource, avatarFrame, dataCard } + Widget buildStoreBagItemTitle( String content, { required double fontSize, @@ -86,7 +94,7 @@ class SCStoreGridSkeleton extends StatelessWidget { crossAxisCount: 3, mainAxisSpacing: 12.w, crossAxisSpacing: 12.w, - childAspectRatio: 0.78, + childAspectRatio: 0.88, ), itemCount: itemCount, itemBuilder: (context, index) => _buildStoreCardSkeleton(progress), @@ -95,6 +103,228 @@ class SCStoreGridSkeleton extends StatelessWidget { } } +class SCStoreGridItemCard extends StatelessWidget { + const SCStoreGridItemCard({ + super.key, + required this.res, + required this.selected, + required this.discount, + required this.onPreviewTap, + required this.onDetailsTap, + this.previewKind = SCStoreItemPreviewKind.resource, + this.coverWidth, + this.coverHeight, + this.previewWidth, + this.previewHeight, + this.coverFit = BoxFit.contain, + this.previewFit = BoxFit.contain, + this.coverBorderRadius, + }); + + final StoreListRes res; + final bool selected; + final double discount; + final VoidCallback onPreviewTap; + final VoidCallback onDetailsTap; + final SCStoreItemPreviewKind previewKind; + final double? coverWidth; + final double? coverHeight; + final double? previewWidth; + final double? previewHeight; + final BoxFit coverFit; + final BoxFit previewFit; + final BorderRadius? coverBorderRadius; + + @override + Widget build(BuildContext context) { + final firstPrice = + (res.propsPrices?.isNotEmpty ?? false) ? res.propsPrices!.first : null; + final amount = ((firstPrice?.amount ?? 0) * discount).toInt(); + final tagText = _buildDurationTag(context, firstPrice?.days); + + return GestureDetector( + onTap: onDetailsTap, + child: Container( + decoration: BoxDecoration( + color: + selected + ? const Color(0xff18F2B1).withValues(alpha: 0.1) + : SCGlobalConfig.businessLogicStrategy + .getStoreItemBackgroundColor(), + border: Border.all( + color: + selected + ? SCGlobalConfig.businessLogicStrategy + .getStoreItemSelectedBorderColor() + : SCGlobalConfig.businessLogicStrategy + .getStoreItemUnselectedBorderColor(), + width: 1.w, + ), + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + child: Stack( + children: [ + PositionedDirectional( + top: 6.w, + start: 6.w, + child: _buildDurationBadge(tagText), + ), + Padding( + padding: EdgeInsets.fromLTRB(6.w, 16.w, 6.w, 8.w), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildPreviewTapArea(), + SizedBox(height: 8.w), + _buildPriceRow(amount, firstPrice), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildPreviewTapArea() { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: onPreviewTap, + child: SizedBox( + width: double.infinity, + height: + previewHeight ?? + coverHeight ?? + (previewKind == SCStoreItemPreviewKind.avatarFrame ? 64.w : 62.w), + child: Center(child: selected ? _buildPreview() : _buildCover()), + ), + ); + } + + Widget _buildCover() { + Widget child = netImage( + url: res.propsResources?.cover ?? "", + width: coverWidth ?? 58.w, + height: coverHeight ?? 58.w, + fit: coverFit, + ); + if (coverBorderRadius != null) { + child = ClipRRect(borderRadius: coverBorderRadius!, child: child); + } + return child; + } + + Widget _buildPreview() { + switch (previewKind) { + case SCStoreItemPreviewKind.avatarFrame: + return head( + url: AccountStorage().getCurrentUser()?.userProfile?.userAvatar ?? "", + width: previewWidth ?? 64.w, + height: previewHeight ?? 64.w, + headdress: res.propsResources?.sourceUrl, + headdressCover: res.propsResources?.cover, + ); + case SCStoreItemPreviewKind.dataCard: + return ClipRRect( + borderRadius: BorderRadius.circular(6.w), + child: SizedBox( + width: previewWidth ?? 88.w, + height: previewHeight ?? 58.w, + child: SCDataCardResourceView( + resource: res.propsResources, + fit: previewFit, + ), + ), + ); + case SCStoreItemPreviewKind.resource: + return SizedBox( + width: previewWidth ?? 72.w, + height: previewHeight ?? 62.w, + child: SCDataCardResourceView( + resource: res.propsResources, + fit: previewFit, + ), + ); + } + } + + Widget _buildPriceRow(int amount, PropsPrices? firstPrice) { + return SizedBox( + width: double.infinity, + height: 22.w, + child: FittedBox( + fit: BoxFit.scaleDown, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + SCGlobalConfig.businessLogicStrategy.getStoreItemGoldIcon(), + width: 18.w, + height: 18.w, + ), + SizedBox(width: 4.w), + if (discount < 1) ...[ + text( + "${firstPrice?.amount ?? 0}", + fontSize: 12.sp, + textColor: + SCGlobalConfig.businessLogicStrategy + .getStoreItemPriceTextColor(), + fontWeight: FontWeight.w600, + decoration: TextDecoration.lineThrough, + ), + SizedBox(width: 4.w), + ], + text( + "$amount", + fontSize: 13.sp, + textColor: + SCGlobalConfig.businessLogicStrategy + .getStoreItemPriceTextColor(), + fontWeight: FontWeight.w600, + ), + ], + ), + ), + ); + } + + Widget _buildDurationBadge(String tagText) { + if (tagText.isEmpty) { + return const SizedBox.shrink(); + } + return Container( + constraints: BoxConstraints(maxWidth: 58.w), + padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 2.w), + decoration: BoxDecoration( + color: const Color(0xff18F2B1).withValues(alpha: 0.16), + borderRadius: BorderRadius.circular(4.w), + border: Border.all( + color: const Color(0xff18F2B1).withValues(alpha: 0.28), + width: 0.5.w, + ), + ), + child: text( + tagText, + fontSize: 9.sp, + textColor: + SCGlobalConfig.businessLogicStrategy.getStoreItemPriceTextColor(), + fontWeight: FontWeight.w600, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ); + } + + String _buildDurationTag(BuildContext context, num? days) { + if (days == null) { + return ""; + } + final normalizedDays = days % 1 == 0 ? days.toInt().toString() : "$days"; + return "$normalizedDays ${SCAppLocalizations.of(context)!.day}"; + } +} + class SCBagGridSkeleton extends StatelessWidget { const SCBagGridSkeleton({ super.key, @@ -130,9 +360,18 @@ Widget _buildStoreCardSkeleton(double progress) { return DecoratedBox( decoration: _buildStoreBagShellDecoration(radius: 8.w), child: Padding( - padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.w), + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 8.w), child: Column( children: [ + Align( + alignment: AlignmentDirectional.centerStart, + child: _buildStoreBagSkeletonBone( + progress, + width: 42.w, + height: 14.w, + borderRadius: BorderRadius.circular(4.w), + ), + ), SizedBox(height: 2.w), _buildStoreBagSkeletonBone( progress, @@ -140,14 +379,7 @@ Widget _buildStoreCardSkeleton(double progress) { height: 55.w, borderRadius: BorderRadius.circular(12.w), ), - SizedBox(height: 10.w), - _buildStoreBagSkeletonBone( - progress, - width: double.infinity, - height: 12.w, - borderRadius: BorderRadius.circular(999.w), - ), - SizedBox(height: 10.w), + SizedBox(height: 8.w), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -166,13 +398,6 @@ Widget _buildStoreCardSkeleton(double progress) { ), ], ), - SizedBox(height: 6.w), - _buildStoreBagSkeletonBone( - progress, - width: 48.w, - height: 11.w, - borderRadius: BorderRadius.circular(999.w), - ), ], ), ),