最终版本
This commit is contained in:
parent
10d5e821f7
commit
8b05bb367a
@ -16,7 +16,7 @@ class SCVariant1Config implements AppConfig {
|
|||||||
@override
|
@override
|
||||||
String get apiHost => const String.fromEnvironment(
|
String get apiHost => const String.fromEnvironment(
|
||||||
'API_HOST',
|
'API_HOST',
|
||||||
defaultValue: 'http://192.168.110.64:1100/',
|
defaultValue: 'https://jvapi.haiyihy.com/',
|
||||||
); // 默认连本地接口,本地调试可通过 --dart-define=API_HOST 覆盖,线上“https://jvapi.haiyihy.com/”
|
); // 默认连本地接口,本地调试可通过 --dart-define=API_HOST 覆盖,线上“https://jvapi.haiyihy.com/”
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -141,6 +141,7 @@ class SCFirstRechargeRewardItem {
|
|||||||
const SCFirstRechargeRewardItem({
|
const SCFirstRechargeRewardItem({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.type,
|
required this.type,
|
||||||
|
required this.detailType,
|
||||||
required this.typeName,
|
required this.typeName,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.content,
|
required this.content,
|
||||||
@ -153,6 +154,7 @@ class SCFirstRechargeRewardItem {
|
|||||||
factory SCFirstRechargeRewardItem.fromJson(dynamic json) {
|
factory SCFirstRechargeRewardItem.fromJson(dynamic json) {
|
||||||
final map = _asMap(json);
|
final map = _asMap(json);
|
||||||
final content = _readString(map['content']);
|
final content = _readString(map['content']);
|
||||||
|
final detailType = _readRewardDetailType(map);
|
||||||
final badgeText = _firstString(map, const [
|
final badgeText = _firstString(map, const [
|
||||||
'badgeText',
|
'badgeText',
|
||||||
'cornerText',
|
'cornerText',
|
||||||
@ -176,6 +178,7 @@ class SCFirstRechargeRewardItem {
|
|||||||
return SCFirstRechargeRewardItem(
|
return SCFirstRechargeRewardItem(
|
||||||
id: _readString(map['id']),
|
id: _readString(map['id']),
|
||||||
type: _readString(map['type']),
|
type: _readString(map['type']),
|
||||||
|
detailType: detailType,
|
||||||
typeName: _firstString(map, const [
|
typeName: _firstString(map, const [
|
||||||
'typeName',
|
'typeName',
|
||||||
'rewardTypeName',
|
'rewardTypeName',
|
||||||
@ -190,7 +193,7 @@ class SCFirstRechargeRewardItem {
|
|||||||
name: _readString(map['name']),
|
name: _readString(map['name']),
|
||||||
content: content,
|
content: content,
|
||||||
quantity: _readInt(map['quantity']),
|
quantity: _readInt(map['quantity']),
|
||||||
cover: _readString(map['cover']),
|
cover: _readRewardCover(map, detailType: detailType),
|
||||||
badgeText: badgeText,
|
badgeText: badgeText,
|
||||||
days:
|
days:
|
||||||
explicitDays > 0
|
explicitDays > 0
|
||||||
@ -204,6 +207,7 @@ class SCFirstRechargeRewardItem {
|
|||||||
|
|
||||||
final String id;
|
final String id;
|
||||||
final String type;
|
final String type;
|
||||||
|
final String detailType;
|
||||||
final String typeName;
|
final String typeName;
|
||||||
final String name;
|
final String name;
|
||||||
final String content;
|
final String content;
|
||||||
@ -238,6 +242,7 @@ class SCFirstRechargeRewardItem {
|
|||||||
}
|
}
|
||||||
return _firstNonEmpty([
|
return _firstNonEmpty([
|
||||||
_inferRewardTypeName(
|
_inferRewardTypeName(
|
||||||
|
detailType: detailType,
|
||||||
typeName: typeName,
|
typeName: typeName,
|
||||||
type: type,
|
type: type,
|
||||||
name: name,
|
name: name,
|
||||||
@ -254,7 +259,52 @@ class SCFirstRechargeRewardItem {
|
|||||||
if (isCoinReward) {
|
if (isCoinReward) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return '${days > 0 ? days : 1}d';
|
final directBadgeText = badgeText.trim();
|
||||||
|
if (directBadgeText.isNotEmpty) {
|
||||||
|
return directBadgeText;
|
||||||
|
}
|
||||||
|
if (isCountBasedReward) {
|
||||||
|
return 'x${quantity > 0 ? quantity : 1}';
|
||||||
|
}
|
||||||
|
if (usesQuantityAsDuration) {
|
||||||
|
final durationDays = _firstPositiveInt([days, quantity]);
|
||||||
|
return '${durationDays > 0 ? durationDays : 1}d';
|
||||||
|
}
|
||||||
|
return days > 0 ? '${days}d' : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isCountBasedReward {
|
||||||
|
return _isGiftRewardType(_rewardTypeSearchText);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get usesQuantityAsDuration {
|
||||||
|
final explicitTypeSearchText =
|
||||||
|
[
|
||||||
|
detailType,
|
||||||
|
typeName,
|
||||||
|
type,
|
||||||
|
].where((value) => value.trim().isNotEmpty).join(' ').toLowerCase();
|
||||||
|
if (_isGiftRewardType(explicitTypeSearchText) ||
|
||||||
|
_isBadgeRewardType(explicitTypeSearchText)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (_usesQuantityAsDuration(explicitTypeSearchText)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (_isBadgeRewardType(_rewardTypeSearchText)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return _usesQuantityAsDuration(_rewardTypeSearchText);
|
||||||
|
}
|
||||||
|
|
||||||
|
String get _rewardTypeSearchText {
|
||||||
|
return [
|
||||||
|
detailType,
|
||||||
|
typeName,
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
content,
|
||||||
|
].where((value) => value.trim().isNotEmpty).join(' ').toLowerCase();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,6 +390,106 @@ String _firstString(Map<String, dynamic> map, List<String> keys) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _readRewardDetailType(Map<String, dynamic> map) {
|
||||||
|
final direct = _firstString(map, const [
|
||||||
|
'detailType',
|
||||||
|
'rewardDetailType',
|
||||||
|
'itemDetailType',
|
||||||
|
'propType',
|
||||||
|
'propsType',
|
||||||
|
'goodsType',
|
||||||
|
'categoryType',
|
||||||
|
'materialType',
|
||||||
|
'resourceType',
|
||||||
|
'resourceTypeCode',
|
||||||
|
]);
|
||||||
|
if (direct.isNotEmpty) {
|
||||||
|
return direct;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final key in const ['propsResources', 'propsResource', 'resource']) {
|
||||||
|
final nested = _asMap(map[key]);
|
||||||
|
final nestedType = _firstString(nested, const [
|
||||||
|
'detailType',
|
||||||
|
'type',
|
||||||
|
'propType',
|
||||||
|
'propsType',
|
||||||
|
'materialType',
|
||||||
|
'resourceType',
|
||||||
|
]);
|
||||||
|
if (nestedType.isNotEmpty) {
|
||||||
|
return nestedType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _readRewardCover(
|
||||||
|
Map<String, dynamic> map, {
|
||||||
|
required String detailType,
|
||||||
|
}) {
|
||||||
|
final typeName = _normalizeRewardToken(detailType);
|
||||||
|
if (typeName == 'noblevip' || typeName == 'vip') {
|
||||||
|
final directShortBadge = _firstString(map, const [
|
||||||
|
'shortBadgeCover',
|
||||||
|
'shortBadgeCoverUrl',
|
||||||
|
'shortBadgeUrl',
|
||||||
|
'vipShortBadgeCover',
|
||||||
|
'vipShortBadgeUrl',
|
||||||
|
'badgeCover',
|
||||||
|
'badgeCoverUrl',
|
||||||
|
]);
|
||||||
|
if (directShortBadge.isNotEmpty) {
|
||||||
|
return directShortBadge;
|
||||||
|
}
|
||||||
|
|
||||||
|
final nestedShortBadge = _readNestedCover(map['shortBadge']);
|
||||||
|
if (nestedShortBadge.isNotEmpty) {
|
||||||
|
return nestedShortBadge;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final direct = _firstString(map, const [
|
||||||
|
'cover',
|
||||||
|
'coverUrl',
|
||||||
|
'imageUrl',
|
||||||
|
'iconUrl',
|
||||||
|
'photo',
|
||||||
|
'picture',
|
||||||
|
'giftPhoto',
|
||||||
|
'resourceUrl',
|
||||||
|
'url',
|
||||||
|
]);
|
||||||
|
if (direct.isNotEmpty) {
|
||||||
|
return direct;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final key in const ['propsResources', 'propsResource', 'resource']) {
|
||||||
|
final nestedCover = _readNestedCover(map[key]);
|
||||||
|
if (nestedCover.isNotEmpty) {
|
||||||
|
return nestedCover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _readNestedCover(dynamic value) {
|
||||||
|
final nested = _asMap(value);
|
||||||
|
if (nested.isEmpty) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return _firstString(nested, const [
|
||||||
|
'cover',
|
||||||
|
'coverUrl',
|
||||||
|
'imageUrl',
|
||||||
|
'iconUrl',
|
||||||
|
'photo',
|
||||||
|
'picture',
|
||||||
|
'url',
|
||||||
|
'resourceUrl',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
String _firstNonEmpty(List<String> values) {
|
String _firstNonEmpty(List<String> values) {
|
||||||
for (final value in values) {
|
for (final value in values) {
|
||||||
final text = value.trim();
|
final text = value.trim();
|
||||||
@ -461,12 +611,18 @@ String _formatRewardTypeName(String type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _inferRewardTypeName({
|
String _inferRewardTypeName({
|
||||||
|
required String detailType,
|
||||||
required String typeName,
|
required String typeName,
|
||||||
required String type,
|
required String type,
|
||||||
required String name,
|
required String name,
|
||||||
required String content,
|
required String content,
|
||||||
required String cover,
|
required String cover,
|
||||||
}) {
|
}) {
|
||||||
|
final detailTypeName = _normalizeSpecificRewardTypeName(detailType);
|
||||||
|
if (detailTypeName.isNotEmpty) {
|
||||||
|
return detailTypeName;
|
||||||
|
}
|
||||||
|
|
||||||
final explicitTypeName = _normalizeSpecificRewardTypeName(typeName);
|
final explicitTypeName = _normalizeSpecificRewardTypeName(typeName);
|
||||||
if (explicitTypeName.isNotEmpty) {
|
if (explicitTypeName.isNotEmpty) {
|
||||||
return explicitTypeName;
|
return explicitTypeName;
|
||||||
@ -474,6 +630,8 @@ String _inferRewardTypeName({
|
|||||||
|
|
||||||
final searchable =
|
final searchable =
|
||||||
[
|
[
|
||||||
|
detailType,
|
||||||
|
typeName,
|
||||||
type,
|
type,
|
||||||
name,
|
name,
|
||||||
content,
|
content,
|
||||||
@ -506,6 +664,37 @@ String _inferRewardTypeName({
|
|||||||
])) {
|
])) {
|
||||||
return 'Vehicles';
|
return 'Vehicles';
|
||||||
}
|
}
|
||||||
|
if (_containsAny(searchable, const [
|
||||||
|
'chat_bubble',
|
||||||
|
'chatbubble',
|
||||||
|
'bubble',
|
||||||
|
'聊天气泡',
|
||||||
|
'气泡',
|
||||||
|
])) {
|
||||||
|
return 'Chat Bubble';
|
||||||
|
}
|
||||||
|
if (_containsAny(searchable, const [
|
||||||
|
'data_card',
|
||||||
|
'datacard',
|
||||||
|
'profile_card',
|
||||||
|
'资料卡',
|
||||||
|
])) {
|
||||||
|
return 'Data Card';
|
||||||
|
}
|
||||||
|
final textSearchable =
|
||||||
|
[
|
||||||
|
detailType,
|
||||||
|
typeName,
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
content,
|
||||||
|
].where((value) => value.trim().isNotEmpty).join(' ').toLowerCase();
|
||||||
|
if (_containsAny(textSearchable, const ['gift', 'present', '礼物'])) {
|
||||||
|
return 'Gift';
|
||||||
|
}
|
||||||
|
if (_containsAny(textSearchable, const ['badge', 'medal', '徽章', '勋章'])) {
|
||||||
|
return 'Badge';
|
||||||
|
}
|
||||||
if (_containsAny(searchable, const [
|
if (_containsAny(searchable, const [
|
||||||
'vip',
|
'vip',
|
||||||
'svip',
|
'svip',
|
||||||
@ -528,6 +717,13 @@ String _normalizeSpecificRewardTypeName(String value) {
|
|||||||
if (normalized == 'prop' || normalized == 'props' || normalized == '道具') {
|
if (normalized == 'prop' || normalized == 'props' || normalized == '道具') {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
if (normalized == 'noblevip' ||
|
||||||
|
normalized == 'vip' ||
|
||||||
|
normalized.contains('svip') ||
|
||||||
|
text.contains('贵族') ||
|
||||||
|
text.contains('会员')) {
|
||||||
|
return 'VIP';
|
||||||
|
}
|
||||||
if (normalized.contains('frame') || text.contains('框')) {
|
if (normalized.contains('frame') || text.contains('框')) {
|
||||||
return 'Frames';
|
return 'Frames';
|
||||||
}
|
}
|
||||||
@ -539,14 +735,76 @@ String _normalizeSpecificRewardTypeName(String value) {
|
|||||||
text.contains('车辆')) {
|
text.contains('车辆')) {
|
||||||
return 'Vehicles';
|
return 'Vehicles';
|
||||||
}
|
}
|
||||||
if (normalized.contains('vip') ||
|
if (normalized == 'gift' ||
|
||||||
text.contains('贵族') ||
|
normalized == 'gifts' ||
|
||||||
text.contains('会员')) {
|
normalized.contains('present') ||
|
||||||
return 'VIP';
|
text.contains('礼物')) {
|
||||||
|
return 'Gift';
|
||||||
|
}
|
||||||
|
if (normalized == 'chatbubble' ||
|
||||||
|
normalized.contains('chatbubble') ||
|
||||||
|
normalized.contains('bubble') ||
|
||||||
|
text.contains('聊天气泡') ||
|
||||||
|
text.contains('气泡')) {
|
||||||
|
return 'Chat Bubble';
|
||||||
|
}
|
||||||
|
if (normalized == 'datacard' ||
|
||||||
|
normalized.contains('datacard') ||
|
||||||
|
normalized.contains('profilecard') ||
|
||||||
|
text.contains('资料卡')) {
|
||||||
|
return 'Data Card';
|
||||||
|
}
|
||||||
|
if (normalized == 'badge' ||
|
||||||
|
normalized.contains('medal') ||
|
||||||
|
text.contains('徽章') ||
|
||||||
|
text.contains('勋章')) {
|
||||||
|
return 'Badge';
|
||||||
}
|
}
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _normalizeRewardToken(String value) {
|
||||||
|
return value.trim().toLowerCase().replaceAll(RegExp(r'[_\-\s]+'), '');
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isGiftRewardType(String value) {
|
||||||
|
final normalized = _normalizeRewardToken(value);
|
||||||
|
return normalized.contains('gift') ||
|
||||||
|
normalized.contains('present') ||
|
||||||
|
value.contains('礼物');
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isBadgeRewardType(String value) {
|
||||||
|
final normalized = _normalizeRewardToken(value);
|
||||||
|
return normalized.contains('badge') ||
|
||||||
|
normalized.contains('medal') ||
|
||||||
|
value.contains('徽章') ||
|
||||||
|
value.contains('勋章');
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _usesQuantityAsDuration(String value) {
|
||||||
|
final normalized = _normalizeRewardToken(value);
|
||||||
|
return normalized.contains('noblevip') ||
|
||||||
|
normalized.contains('vip') ||
|
||||||
|
normalized.contains('ride') ||
|
||||||
|
normalized.contains('vehicle') ||
|
||||||
|
normalized.contains('mount') ||
|
||||||
|
normalized.contains('avatarframe') ||
|
||||||
|
normalized.contains('frame') ||
|
||||||
|
normalized.contains('chatbubble') ||
|
||||||
|
normalized.contains('bubble') ||
|
||||||
|
normalized.contains('datacard') ||
|
||||||
|
normalized.contains('profilecard') ||
|
||||||
|
value.contains('贵族') ||
|
||||||
|
value.contains('会员') ||
|
||||||
|
value.contains('座驾') ||
|
||||||
|
value.contains('坐骑') ||
|
||||||
|
value.contains('头像框') ||
|
||||||
|
value.contains('聊天气泡') ||
|
||||||
|
value.contains('气泡') ||
|
||||||
|
value.contains('资料卡');
|
||||||
|
}
|
||||||
|
|
||||||
bool _containsAny(String value, List<String> tokens) {
|
bool _containsAny(String value, List<String> tokens) {
|
||||||
for (final token in tokens) {
|
for (final token in tokens) {
|
||||||
if (value.contains(token)) {
|
if (value.contains(token)) {
|
||||||
|
|||||||
@ -844,6 +844,9 @@ class _RewardCover extends StatelessWidget {
|
|||||||
if (title.contains('gift')) {
|
if (title.contains('gift')) {
|
||||||
return _FirstRechargeAssets.rewardGift;
|
return _FirstRechargeAssets.rewardGift;
|
||||||
}
|
}
|
||||||
|
if (title.contains('badge') || title.contains('medal')) {
|
||||||
|
return _FirstRechargeAssets.rewardBadge;
|
||||||
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -983,6 +986,7 @@ class _FirstRechargeAssets {
|
|||||||
'sc_images/first_recharge/reward_coin_stack.png';
|
'sc_images/first_recharge/reward_coin_stack.png';
|
||||||
static const String rewardGift = 'sc_images/first_recharge/reward_gift.png';
|
static const String rewardGift = 'sc_images/first_recharge/reward_gift.png';
|
||||||
static const String rewardVip = 'sc_images/vip/sc_vip_badge_1.png';
|
static const String rewardVip = 'sc_images/vip/sc_vip_badge_1.png';
|
||||||
|
static const String rewardBadge = 'sc_images/index/sc_icon_medals.png';
|
||||||
static const String buyNowButton =
|
static const String buyNowButton =
|
||||||
'sc_images/first_recharge/buy_now_button.png';
|
'sc_images/first_recharge/buy_now_button.png';
|
||||||
static const String discountBadge =
|
static const String discountBadge =
|
||||||
|
|||||||
@ -140,11 +140,14 @@ class _FloatingGameScreenWidgetState extends State<FloatingGameScreenWidget>
|
|||||||
final currentRoomId =
|
final currentRoomId =
|
||||||
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? "";
|
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? "";
|
||||||
final messageRoomId = (widget.message.roomId ?? "").trim();
|
final messageRoomId = (widget.message.roomId ?? "").trim();
|
||||||
|
if (!_isValidRoomId(messageRoomId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final isSameVisibleRoom =
|
final isSameVisibleRoom =
|
||||||
rtcProvider.shouldShowRoomVisualEffects &&
|
rtcProvider.shouldShowRoomVisualEffects &&
|
||||||
rtcProvider.isVoiceRoomRouteVisible &&
|
rtcProvider.isVoiceRoomRouteVisible &&
|
||||||
currentRoomId.isNotEmpty &&
|
currentRoomId.isNotEmpty &&
|
||||||
(messageRoomId.isEmpty || currentRoomId == messageRoomId);
|
currentRoomId == messageRoomId;
|
||||||
|
|
||||||
if (isSameVisibleRoom) {
|
if (isSameVisibleRoom) {
|
||||||
showRoomGameListSheet(
|
showRoomGameListSheet(
|
||||||
@ -153,9 +156,7 @@ class _FloatingGameScreenWidgetState extends State<FloatingGameScreenWidget>
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (messageRoomId.isNotEmpty) {
|
SCRoomUtils.goRoom(messageRoomId, currentContext, fromFloting: true);
|
||||||
SCRoomUtils.goRoom(messageRoomId, currentContext, fromFloting: true);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -453,6 +454,10 @@ class _FloatingGameScreenWidgetState extends State<FloatingGameScreenWidget>
|
|||||||
return gameId.isEmpty ? null : gameId;
|
return gameId.isEmpty ? null : gameId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isValidRoomId(String roomId) {
|
||||||
|
return roomId.isNotEmpty && roomId != "0";
|
||||||
|
}
|
||||||
|
|
||||||
String _formatCoins(num? value) {
|
String _formatCoins(num? value) {
|
||||||
final coins = value ?? 0;
|
final coins = value ?? 0;
|
||||||
if (coins >= 100000) {
|
if (coins >= 100000) {
|
||||||
|
|||||||
@ -289,7 +289,7 @@ class _FloatingLuckyGiftGlobalScreenWidgetState
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final messageRoomId = (widget.message.roomId ?? "").trim();
|
final messageRoomId = (widget.message.roomId ?? "").trim();
|
||||||
if (messageRoomId.isEmpty) {
|
if (!_isValidRoomId(messageRoomId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
|
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
|
||||||
@ -330,6 +330,10 @@ class _FloatingLuckyGiftGlobalScreenWidgetState
|
|||||||
_swipeController.forward();
|
_swipeController.forward();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isValidRoomId(String roomId) {
|
||||||
|
return roomId.isNotEmpty && roomId != "0";
|
||||||
|
}
|
||||||
|
|
||||||
String _winnerName() {
|
String _winnerName() {
|
||||||
return _firstNonBlank([
|
return _firstNonBlank([
|
||||||
widget.message.userName,
|
widget.message.userName,
|
||||||
|
|||||||
148
test/sc_first_recharge_reward_res_test.dart
Normal file
148
test/sc_first_recharge_reward_res_test.dart
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:yumi/shared/business_logic/models/res/sc_first_recharge_reward_res.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('SCFirstRechargeRewardItem display mapping', () {
|
||||||
|
test('maps known non-coin detail types', () {
|
||||||
|
final cases = <String, String>{
|
||||||
|
'NOBLE_VIP': 'VIP',
|
||||||
|
'RIDE': 'Vehicles',
|
||||||
|
'AVATAR_FRAME': 'Frames',
|
||||||
|
'CHAT_BUBBLE': 'Chat Bubble',
|
||||||
|
'DATA_CARD': 'Data Card',
|
||||||
|
'GIFT': 'Gift',
|
||||||
|
'BADGE': 'Badge',
|
||||||
|
};
|
||||||
|
|
||||||
|
cases.forEach((detailType, expectedTitle) {
|
||||||
|
final item = SCFirstRechargeRewardItem.fromJson({
|
||||||
|
'id': detailType,
|
||||||
|
'type': 'PROPS',
|
||||||
|
'detailType': detailType,
|
||||||
|
'name': 'Concrete material name',
|
||||||
|
'quantity': 7,
|
||||||
|
'cover': 'https://example.com/$detailType.png',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(item.displayTitle, expectedTitle);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('uses quantity as days for duration based props', () {
|
||||||
|
for (final detailType in const [
|
||||||
|
'NOBLE_VIP',
|
||||||
|
'RIDE',
|
||||||
|
'AVATAR_FRAME',
|
||||||
|
'CHAT_BUBBLE',
|
||||||
|
'DATA_CARD',
|
||||||
|
]) {
|
||||||
|
final item = SCFirstRechargeRewardItem.fromJson({
|
||||||
|
'id': detailType,
|
||||||
|
'type': 'PROPS',
|
||||||
|
'detailType': detailType,
|
||||||
|
'quantity': 9,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(item.displayBadgeText, '9d');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('treats vip aliases as duration rewards', () {
|
||||||
|
final item = SCFirstRechargeRewardItem.fromJson({
|
||||||
|
'id': 11,
|
||||||
|
'type': 'PROPS',
|
||||||
|
'typeName': 'VIP',
|
||||||
|
'quantity': 5,
|
||||||
|
'shortBadgeCoverUrl': 'https://example.com/vip-short.png',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(item.displayTitle, 'VIP');
|
||||||
|
expect(item.displayBadgeText, '5d');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('uses detailType before material name for vehicle rewards', () {
|
||||||
|
final item = SCFirstRechargeRewardItem.fromJson({
|
||||||
|
'id': 1,
|
||||||
|
'type': 'PROPS',
|
||||||
|
'detailType': 'RIDE',
|
||||||
|
'name': 'Meteor Trail',
|
||||||
|
'content': '1962463978629165057',
|
||||||
|
'quantity': 7,
|
||||||
|
'cover': 'https://example.com/material.png',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(item.displayTitle, 'Vehicles');
|
||||||
|
expect(item.displayBadgeText, '7d');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('uses quantity badge for gift rewards', () {
|
||||||
|
final item = SCFirstRechargeRewardItem.fromJson({
|
||||||
|
'id': 2,
|
||||||
|
'type': 'PROPS',
|
||||||
|
'detailType': 'GIFT',
|
||||||
|
'name': 'Rose',
|
||||||
|
'content': '1001',
|
||||||
|
'quantity': 3,
|
||||||
|
'cover': 'https://example.com/rose.png',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(item.displayTitle, 'Gift');
|
||||||
|
expect(item.displayBadgeText, 'x3');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not invent a duration badge for badge rewards', () {
|
||||||
|
final item = SCFirstRechargeRewardItem.fromJson({
|
||||||
|
'id': 22,
|
||||||
|
'type': 'PROPS',
|
||||||
|
'detailType': 'BADGE',
|
||||||
|
'name': 'VIP Medal',
|
||||||
|
'quantity': 7,
|
||||||
|
'cover': 'https://example.com/badge.png',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(item.displayTitle, 'Badge');
|
||||||
|
expect(item.displayBadgeText, '');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('keeps explicit corner text from the API', () {
|
||||||
|
final item = SCFirstRechargeRewardItem.fromJson({
|
||||||
|
'id': 3,
|
||||||
|
'type': 'PROPS',
|
||||||
|
'detailType': 'GIFT',
|
||||||
|
'name': 'Rose',
|
||||||
|
'quantity': 3,
|
||||||
|
'cornerText': 'x8',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(item.displayBadgeText, 'x8');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reads nested resource type when direct detailType is missing', () {
|
||||||
|
final item = SCFirstRechargeRewardItem.fromJson({
|
||||||
|
'id': 4,
|
||||||
|
'type': 'PROPS',
|
||||||
|
'name': 'Meteor Trail',
|
||||||
|
'quantity': 3,
|
||||||
|
'propsResources': {'type': 'RIDE'},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(item.displayTitle, 'Vehicles');
|
||||||
|
expect(item.displayBadgeText, '3d');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('uses vip short badge cover before generic cover', () {
|
||||||
|
final item = SCFirstRechargeRewardItem.fromJson({
|
||||||
|
'id': 5,
|
||||||
|
'type': 'PROPS',
|
||||||
|
'detailType': 'NOBLE_VIP',
|
||||||
|
'quantity': 7,
|
||||||
|
'cover': 'https://example.com/long-badge.png',
|
||||||
|
'shortBadge': {'coverUrl': 'https://example.com/short-badge.png'},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(item.displayTitle, 'VIP');
|
||||||
|
expect(item.cover, 'https://example.com/short-badge.png');
|
||||||
|
expect(item.displayBadgeText, '7d');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user