方案1
This commit is contained in:
parent
6e10d57808
commit
ea296e461b
@ -726,5 +726,6 @@
|
|||||||
"roomRocketNoteSectionBody": "1. في كل مرة يتم فيها شراء مستوى VIP، تكون صلاحيته 30 يومًا، ويمكن تجديده يدويًا بعد انتهاء الصلاحية.",
|
"roomRocketNoteSectionBody": "1. في كل مرة يتم فيها شراء مستوى VIP، تكون صلاحيته 30 يومًا، ويمكن تجديده يدويًا بعد انتهاء الصلاحية.",
|
||||||
"roomRocketRewardEmptyText": "لم تحصل على مكافآت. حظًا أوفر في المرة القادمة.",
|
"roomRocketRewardEmptyText": "لم تحصل على مكافآت. حظًا أوفر في المرة القادمة.",
|
||||||
"roomRocketRecordEmptyText": "لا توجد سجلات مكافآت",
|
"roomRocketRecordEmptyText": "لا توجد سجلات مكافآت",
|
||||||
"launchRocket": "إطلاق الصاروخ"
|
"launchRocket": "إطلاق الصاروخ",
|
||||||
|
"roomRocketFuelFullNotice": "اكتمل وقود الصاروخ! سيتم الإطلاق بعد {1} ثانية!"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -726,5 +726,6 @@
|
|||||||
"roomRocketNoteSectionBody": "1. প্রতিবার VIP লেভেল কেনার পর এটি ৩০ দিনের জন্য বৈধ থাকে এবং মেয়াদ শেষ হলে ম্যানুয়ালি নবায়ন করা যায়।",
|
"roomRocketNoteSectionBody": "1. প্রতিবার VIP লেভেল কেনার পর এটি ৩০ দিনের জন্য বৈধ থাকে এবং মেয়াদ শেষ হলে ম্যানুয়ালি নবায়ন করা যায়।",
|
||||||
"roomRocketRewardEmptyText": "কোনো পুরস্কার পাওয়া যায়নি। পরের বার শুভকামনা।",
|
"roomRocketRewardEmptyText": "কোনো পুরস্কার পাওয়া যায়নি। পরের বার শুভকামনা।",
|
||||||
"roomRocketRecordEmptyText": "কোনো পুরস্কারের রেকর্ড নেই",
|
"roomRocketRecordEmptyText": "কোনো পুরস্কারের রেকর্ড নেই",
|
||||||
"launchRocket": "রকেট চালু করুন"
|
"launchRocket": "রকেট চালু করুন",
|
||||||
|
"roomRocketFuelFullNotice": "রকেটের জ্বালানি পূর্ণ! {1} সেকেন্ড পরে উৎক্ষেপণ!"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -726,5 +726,6 @@
|
|||||||
"roomRocketNoteSectionBody": "1. Each time a VIP level is purchased, it is valid for 30 days, and can be manually renewed after expiration.",
|
"roomRocketNoteSectionBody": "1. Each time a VIP level is purchased, it is valid for 30 days, and can be manually renewed after expiration.",
|
||||||
"roomRocketRewardEmptyText": "No rewards earned. Better luck next time.",
|
"roomRocketRewardEmptyText": "No rewards earned. Better luck next time.",
|
||||||
"roomRocketRecordEmptyText": "No reward records",
|
"roomRocketRecordEmptyText": "No reward records",
|
||||||
"launchRocket": "Launch Rocket"
|
"launchRocket": "Launch Rocket",
|
||||||
|
"roomRocketFuelFullNotice": "Rocket fuel is full! Launching in {1}s!"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -726,5 +726,6 @@
|
|||||||
"roomRocketNoteSectionBody": "1. Chaque fois qu'un niveau VIP est acheté, il est valable 30 jours et peut être renouvelé manuellement après expiration.",
|
"roomRocketNoteSectionBody": "1. Chaque fois qu'un niveau VIP est acheté, il est valable 30 jours et peut être renouvelé manuellement après expiration.",
|
||||||
"roomRocketRewardEmptyText": "Aucune récompense gagnée. Plus de chance la prochaine fois.",
|
"roomRocketRewardEmptyText": "Aucune récompense gagnée. Plus de chance la prochaine fois.",
|
||||||
"roomRocketRecordEmptyText": "Aucun enregistrement de récompense",
|
"roomRocketRecordEmptyText": "Aucun enregistrement de récompense",
|
||||||
"launchRocket": "Lancement de fusée"
|
"launchRocket": "Lancement de fusée",
|
||||||
|
"roomRocketFuelFullNotice": "Le carburant de la fusée est plein ! Lancement dans {1} s !"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -726,5 +726,6 @@
|
|||||||
"roomRocketNoteSectionBody": "1. Ogni volta che viene acquistato un livello VIP, è valido per 30 giorni e può essere rinnovato manualmente dopo la scadenza.",
|
"roomRocketNoteSectionBody": "1. Ogni volta che viene acquistato un livello VIP, è valido per 30 giorni e può essere rinnovato manualmente dopo la scadenza.",
|
||||||
"roomRocketRewardEmptyText": "Nessun premio guadagnato. Più fortuna la prossima volta.",
|
"roomRocketRewardEmptyText": "Nessun premio guadagnato. Più fortuna la prossima volta.",
|
||||||
"roomRocketRecordEmptyText": "Nessun record di ricompensa",
|
"roomRocketRecordEmptyText": "Nessun record di ricompensa",
|
||||||
"launchRocket": "Lancia il razzo"
|
"launchRocket": "Lancia il razzo",
|
||||||
|
"roomRocketFuelFullNotice": "Carburante del razzo pieno! Lancio tra {1} s!"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -726,5 +726,6 @@
|
|||||||
"roomRocketNoteSectionBody": "1. Cada vez que um nível VIP é adquirido, ele é válido por 30 dias, podendo ser renovado manualmente após expirar.",
|
"roomRocketNoteSectionBody": "1. Cada vez que um nível VIP é adquirido, ele é válido por 30 dias, podendo ser renovado manualmente após expirar.",
|
||||||
"roomRocketRewardEmptyText": "Nenhuma recompensa ganha. Melhor sorte da próxima vez.",
|
"roomRocketRewardEmptyText": "Nenhuma recompensa ganha. Melhor sorte da próxima vez.",
|
||||||
"roomRocketRecordEmptyText": "Nenhum registro de recompensa",
|
"roomRocketRecordEmptyText": "Nenhum registro de recompensa",
|
||||||
"launchRocket": "Lançar foguete"
|
"launchRocket": "Lançar foguete",
|
||||||
|
"roomRocketFuelFullNotice": "Combustível do foguete cheio! Lançamento em {1}s!"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -726,5 +726,6 @@
|
|||||||
"roomRocketNoteSectionBody": "1. Her VIP seviyesi satın alındığında 30 gün geçerlidir ve süresi dolduktan sonra manuel olarak yenilenebilir.",
|
"roomRocketNoteSectionBody": "1. Her VIP seviyesi satın alındığında 30 gün geçerlidir ve süresi dolduktan sonra manuel olarak yenilenebilir.",
|
||||||
"roomRocketRewardEmptyText": "Ödül kazanmadın. Bir dahaki sefere bol şans.",
|
"roomRocketRewardEmptyText": "Ödül kazanmadın. Bir dahaki sefere bol şans.",
|
||||||
"roomRocketRecordEmptyText": "Ödül kaydı yok",
|
"roomRocketRecordEmptyText": "Ödül kaydı yok",
|
||||||
"launchRocket": "Roketi Fırlat"
|
"launchRocket": "Roketi Fırlat",
|
||||||
|
"roomRocketFuelFullNotice": "Roket yakıtı doldu! {1} saniye sonra fırlatılıyor!"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -79,6 +79,9 @@ class SCRoomMsgType {
|
|||||||
///用户火箭中奖
|
///用户火箭中奖
|
||||||
static const String rocketRewardUser = "ROCKET_REWARD_USER";
|
static const String rocketRewardUser = "ROCKET_REWARD_USER";
|
||||||
|
|
||||||
|
///火箭燃料满倒计时发射提示
|
||||||
|
static const String rocketLaunchNotice = "ROCKET_LAUNCH_NOTICE";
|
||||||
|
|
||||||
///语音房火箭状态更新
|
///语音房火箭状态更新
|
||||||
static const String voiceRoomRocketStatusUpdate =
|
static const String voiceRoomRocketStatusUpdate =
|
||||||
"VOICE_ROOM_ROCKET_STATUS_UPDATE";
|
"VOICE_ROOM_ROCKET_STATUS_UPDATE";
|
||||||
|
|||||||
@ -845,6 +845,9 @@ class SCAppLocalizations {
|
|||||||
|
|
||||||
String get launchRocket => translate('launchRocket');
|
String get launchRocket => translate('launchRocket');
|
||||||
|
|
||||||
|
String roomRocketFuelFullNotice(String seconds) =>
|
||||||
|
translate('roomRocketFuelFullNotice').replaceAll('{1}', seconds);
|
||||||
|
|
||||||
String get useCoupontips => translate('useCoupontips');
|
String get useCoupontips => translate('useCoupontips');
|
||||||
|
|
||||||
String get joinMemberTips2 => translate('joinMemberTips2');
|
String get joinMemberTips2 => translate('joinMemberTips2');
|
||||||
|
|||||||
@ -54,7 +54,6 @@ import 'package:yumi/shared/business_logic/models/res/broad_cast_mic_change_push
|
|||||||
hide Data;
|
hide Data;
|
||||||
import 'package:yumi/shared/business_logic/models/res/gift_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/gift_res.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/sc_public_message_page_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/sc_public_message_page_res.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/sc_room_rocket_api_res.dart';
|
|
||||||
import 'package:yumi/shared/business_logic/models/res/sc_room_theme_list_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/sc_room_theme_list_res.dart';
|
||||||
import 'package:yumi/services/general/sc_app_general_manager.dart';
|
import 'package:yumi/services/general/sc_app_general_manager.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/room/invite/invite_room_dialog.dart';
|
import 'package:yumi/ui_kit/widgets/room/invite/invite_room_dialog.dart';
|
||||||
@ -119,7 +118,6 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
static const int _luckyGiftBurstPlaybackWatchdogMs = 8000;
|
static const int _luckyGiftBurstPlaybackWatchdogMs = 8000;
|
||||||
|
|
||||||
BuildContext? context;
|
BuildContext? context;
|
||||||
final Set<String> _sentRoomRocketRewardMessageKeys = <String>{};
|
|
||||||
|
|
||||||
void _giftFxLog(String message) {}
|
void _giftFxLog(String message) {}
|
||||||
|
|
||||||
@ -1106,6 +1104,8 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
return true;
|
return true;
|
||||||
case SCRoomMsgType.rocketRewardUser:
|
case SCRoomMsgType.rocketRewardUser:
|
||||||
return true;
|
return true;
|
||||||
|
case SCRoomMsgType.rocketLaunchNotice:
|
||||||
|
return true;
|
||||||
case SCRoomMsgType.emoticons:
|
case SCRoomMsgType.emoticons:
|
||||||
return (msg.number ?? -1) < 0;
|
return (msg.number ?? -1) < 0;
|
||||||
default:
|
default:
|
||||||
@ -1139,6 +1139,10 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
_hasRoomRocketRewardMessage(msg)) {
|
_hasRoomRocketRewardMessage(msg)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (msg.type == SCRoomMsgType.rocketLaunchNotice &&
|
||||||
|
_hasRoomRocketNoticeMessage(msg)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
roomAllMsgList.insert(0, msg);
|
roomAllMsgList.insert(0, msg);
|
||||||
if (roomAllMsgList.length > 250) {
|
if (roomAllMsgList.length > 250) {
|
||||||
@ -1177,6 +1181,12 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
roomChatMsgList.removeAt(roomChatMsgList.length - 1);
|
roomChatMsgList.removeAt(roomChatMsgList.length - 1);
|
||||||
}
|
}
|
||||||
msgChatListener?.call(msg);
|
msgChatListener?.call(msg);
|
||||||
|
} else if (msg.type == SCRoomMsgType.rocketLaunchNotice) {
|
||||||
|
roomChatMsgList.insert(0, msg);
|
||||||
|
if (roomChatMsgList.length > 250) {
|
||||||
|
roomChatMsgList.removeAt(roomChatMsgList.length - 1);
|
||||||
|
}
|
||||||
|
msgChatListener?.call(msg);
|
||||||
} else if (msg.type == SCRoomMsgType.gift ||
|
} else if (msg.type == SCRoomMsgType.gift ||
|
||||||
msg.type == SCRoomMsgType.luckGiftAnimOther) {
|
msg.type == SCRoomMsgType.luckGiftAnimOther) {
|
||||||
roomGiftMsgList.insert(0, msg);
|
roomGiftMsgList.insert(0, msg);
|
||||||
@ -1296,6 +1306,18 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _hasRoomRocketNoticeMessage(Msg msg) {
|
||||||
|
final key = (msg.giftBatchId ?? '').trim();
|
||||||
|
if (key.isEmpty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return roomAllMsgList.any(
|
||||||
|
(item) =>
|
||||||
|
item.type == SCRoomMsgType.rocketLaunchNotice &&
|
||||||
|
(item.giftBatchId ?? '').trim() == key,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
String _roomRocketRewardMessageKey(Msg msg) {
|
String _roomRocketRewardMessageKey(Msg msg) {
|
||||||
final stableKey = (msg.giftBatchId ?? '').trim();
|
final stableKey = (msg.giftBatchId ?? '').trim();
|
||||||
if (stableKey.isNotEmpty) {
|
if (stableKey.isNotEmpty) {
|
||||||
@ -1891,6 +1913,15 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
rtcProvider.shouldShowRoomVisualEffects &&
|
rtcProvider.shouldShowRoomVisualEffects &&
|
||||||
rtcProvider.isVoiceRoomRouteVisible;
|
rtcProvider.isVoiceRoomRouteVisible;
|
||||||
if (isCurrentVisibleRoom) {
|
if (isCurrentVisibleRoom) {
|
||||||
|
addMsg(
|
||||||
|
_buildRoomRocketLaunchNoticeMsg(
|
||||||
|
_firstNonBlank([
|
||||||
|
rtcProvider.currenRoom?.roomProfile?.roomProfile?.roomAccount,
|
||||||
|
launch.roomAccount,
|
||||||
|
]),
|
||||||
|
launch,
|
||||||
|
),
|
||||||
|
);
|
||||||
rtcProvider.handleRoomRocketLaunchBroadcast(launch);
|
rtcProvider.handleRoomRocketLaunchBroadcast(launch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1924,260 +1955,6 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
unawaited(_showRoomRocketRewardPopupDialog(resolvedRoomId));
|
unawaited(_showRoomRocketRewardPopupDialog(resolvedRoomId));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _sendRoomRocketRewardPublicMessages(
|
|
||||||
String roomId,
|
|
||||||
Map<String, dynamic> payload,
|
|
||||||
) async {
|
|
||||||
final groupId = _currentVoiceRoomGroupId();
|
|
||||||
if (groupId.isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final launchNo = _payloadText(payload['launchNo']);
|
|
||||||
var records = _rocketRewardRecordsFromPayload(payload);
|
|
||||||
if (records.isEmpty) {
|
|
||||||
records = await _loadRocketRewardPublicRecords(roomId, launchNo);
|
|
||||||
}
|
|
||||||
if (records.isNotEmpty) {
|
|
||||||
for (final record in records) {
|
|
||||||
await _sendRoomRocketRewardMessage(
|
|
||||||
groupId,
|
|
||||||
_buildRocketRewardRecordMsg(groupId, record),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final userIds = _payloadStringList(payload['userIds']);
|
|
||||||
for (final userId in userIds) {
|
|
||||||
await _sendRoomRocketRewardMessage(
|
|
||||||
groupId,
|
|
||||||
_buildRocketRewardFallbackMsg(groupId, payload, userId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _sendRoomRocketRewardMessage(String groupId, Msg msg) async {
|
|
||||||
final key = _roomRocketRewardMessageKey(msg);
|
|
||||||
if (key.isEmpty || !_sentRoomRocketRewardMessageKeys.add(key)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
final textMsg = await TencentImSDKPlugin.v2TIMManager
|
|
||||||
.getMessageManager()
|
|
||||||
.createCustomMessage(data: jsonEncode(msg.toJson()));
|
|
||||||
if (textMsg.code != 0 || textMsg.data?.id == null) {
|
|
||||||
_sentRoomRocketRewardMessageKeys.remove(key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final sendResult = await TencentImSDKPlugin.v2TIMManager
|
|
||||||
.getMessageManager()
|
|
||||||
.sendMessage(id: textMsg.data!.id!, groupID: groupId, receiver: '');
|
|
||||||
if (sendResult.code == 0) {
|
|
||||||
addMsg(msg);
|
|
||||||
} else {
|
|
||||||
_sentRoomRocketRewardMessageKeys.remove(key);
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
_sentRoomRocketRewardMessageKeys.remove(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<SCRoomRocketRewardRecordRes>> _loadRocketRewardPublicRecords(
|
|
||||||
String roomId,
|
|
||||||
String launchNo,
|
|
||||||
) async {
|
|
||||||
if (roomId.trim().isEmpty || launchNo.trim().isEmpty) {
|
|
||||||
return const <SCRoomRocketRewardRecordRes>[];
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
final res = await SCChatRoomRepository().roomRocketRewardRecords(
|
|
||||||
roomId,
|
|
||||||
cursor: 1,
|
|
||||||
limit: 100,
|
|
||||||
);
|
|
||||||
return res.records
|
|
||||||
.where((item) => item.launchNo.trim() == launchNo.trim())
|
|
||||||
.toList(growable: false);
|
|
||||||
} catch (_) {
|
|
||||||
return const <SCRoomRocketRewardRecordRes>[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<SCRoomRocketRewardRecordRes> _rocketRewardRecordsFromPayload(
|
|
||||||
Map<String, dynamic> payload,
|
|
||||||
) {
|
|
||||||
final candidates = [
|
|
||||||
payload['records'],
|
|
||||||
payload['rewardRecords'],
|
|
||||||
payload['rewards'],
|
|
||||||
_broadcastPayloadMap(payload['body'])['records'],
|
|
||||||
_broadcastPayloadMap(payload['data'])['records'],
|
|
||||||
];
|
|
||||||
for (final candidate in candidates) {
|
|
||||||
if (candidate is Iterable) {
|
|
||||||
final records = candidate
|
|
||||||
.map(SCRoomRocketRewardRecordRes.fromJson)
|
|
||||||
.toList(growable: false);
|
|
||||||
final usableRecords = records
|
|
||||||
.where((item) => item.userId.trim().isNotEmpty)
|
|
||||||
.toList(growable: false);
|
|
||||||
if (usableRecords.isNotEmpty) {
|
|
||||||
return usableRecords;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return const <SCRoomRocketRewardRecordRes>[];
|
|
||||||
}
|
|
||||||
|
|
||||||
Msg _buildRocketRewardRecordMsg(
|
|
||||||
String groupId,
|
|
||||||
SCRoomRocketRewardRecordRes record,
|
|
||||||
) {
|
|
||||||
return Msg(
|
|
||||||
groupId: groupId,
|
|
||||||
msg: record.rewardCover,
|
|
||||||
type: SCRoomMsgType.rocketRewardUser,
|
|
||||||
user: SocialChatUserProfile(
|
|
||||||
id: record.userId,
|
|
||||||
userNickname: record.displayName,
|
|
||||||
userAvatar: record.userAvatar,
|
|
||||||
account: record.account,
|
|
||||||
),
|
|
||||||
role: _rocketRewardRole(record.rewardType, record.rewardCover),
|
|
||||||
number: record.level <= 0 ? 1 : record.level,
|
|
||||||
rocketIconUrl: _firstNonBlank([
|
|
||||||
record.rocketIconUrl,
|
|
||||||
_rocketIconUrlForLevel(record.level),
|
|
||||||
]),
|
|
||||||
giftBatchId: _rocketRewardRecordKey(record),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Msg _buildRocketRewardFallbackMsg(
|
|
||||||
String groupId,
|
|
||||||
Map<String, dynamic> payload,
|
|
||||||
String userId,
|
|
||||||
) {
|
|
||||||
final rewardCover = _firstNonBlank([
|
|
||||||
_payloadText(payload['rewardCover']),
|
|
||||||
_payloadText(payload['rewardIcon']),
|
|
||||||
_payloadText(payload['rewardUrl']),
|
|
||||||
_payloadText(payload['giftUrl']),
|
|
||||||
_payloadText(payload['giftIcon']),
|
|
||||||
]);
|
|
||||||
final rewardType = _firstNonBlank([
|
|
||||||
_payloadText(payload['rewardType']),
|
|
||||||
_payloadText(payload['type']),
|
|
||||||
rewardCover.isEmpty ? 'GOLD' : '',
|
|
||||||
]);
|
|
||||||
final level = (_payloadNum(payload['level']) ?? 1).toInt();
|
|
||||||
return Msg(
|
|
||||||
groupId: groupId,
|
|
||||||
msg: rewardCover,
|
|
||||||
type: SCRoomMsgType.rocketRewardUser,
|
|
||||||
user: _rocketRewardUserFromPayload(payload, userId),
|
|
||||||
role: _rocketRewardRole(rewardType, rewardCover),
|
|
||||||
number: level <= 0 ? 1 : level,
|
|
||||||
rocketIconUrl: _rocketRewardRocketIconUrl(payload, level),
|
|
||||||
giftBatchId: [
|
|
||||||
'rocket_reward_fallback',
|
|
||||||
_payloadText(payload['launchNo']),
|
|
||||||
userId,
|
|
||||||
level,
|
|
||||||
rewardType,
|
|
||||||
].join('|'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
SocialChatUserProfile _rocketRewardUserFromPayload(
|
|
||||||
Map<String, dynamic> payload,
|
|
||||||
String userId,
|
|
||||||
) {
|
|
||||||
final userPayload = _rocketRewardUserPayload(payload, userId);
|
|
||||||
final nickname = _firstNonBlank([
|
|
||||||
_payloadText(userPayload['userNickname']),
|
|
||||||
_payloadText(userPayload['nickname']),
|
|
||||||
_payloadText(userPayload['nickName']),
|
|
||||||
_payloadText(userPayload['account']),
|
|
||||||
userId,
|
|
||||||
]);
|
|
||||||
return SocialChatUserProfile(
|
|
||||||
id: _firstNonBlank([_payloadText(userPayload['userId']), userId]),
|
|
||||||
userNickname: nickname,
|
|
||||||
userAvatar: _firstNonBlank([
|
|
||||||
_payloadText(userPayload['userAvatar']),
|
|
||||||
_payloadText(userPayload['avatar']),
|
|
||||||
_payloadText(userPayload['avatarUrl']),
|
|
||||||
]),
|
|
||||||
account: _payloadText(userPayload['account']),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> _rocketRewardUserPayload(
|
|
||||||
Map<String, dynamic> payload,
|
|
||||||
String userId,
|
|
||||||
) {
|
|
||||||
final candidates = [
|
|
||||||
payload['users'],
|
|
||||||
payload['rewardUsers'],
|
|
||||||
payload['winnerUsers'],
|
|
||||||
payload['winners'],
|
|
||||||
];
|
|
||||||
for (final candidate in candidates) {
|
|
||||||
if (candidate is Map) {
|
|
||||||
final item = candidate[userId] ?? candidate[userId.toString()];
|
|
||||||
final map = _broadcastPayloadMap(item);
|
|
||||||
if (map.isNotEmpty) {
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (candidate is Iterable) {
|
|
||||||
for (final item in candidate) {
|
|
||||||
final map = _broadcastPayloadMap(item);
|
|
||||||
final id = _firstNonBlank([
|
|
||||||
_payloadText(map['userId']),
|
|
||||||
_payloadText(map['id']),
|
|
||||||
_payloadText(map['account']),
|
|
||||||
]);
|
|
||||||
if (id == userId) {
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return <String, dynamic>{'userId': userId, 'userNickname': userId};
|
|
||||||
}
|
|
||||||
|
|
||||||
String _rocketRewardRole(String rewardType, String rewardCover) {
|
|
||||||
final normalized = rewardType.trim().toUpperCase();
|
|
||||||
if (normalized == 'GOLD' || normalized == 'COIN' || normalized == 'COINS') {
|
|
||||||
return 'GOLD';
|
|
||||||
}
|
|
||||||
return rewardCover.trim().isEmpty ? 'GOLD' : normalized;
|
|
||||||
}
|
|
||||||
|
|
||||||
String _rocketRewardRocketIconUrl(Map<String, dynamic> payload, int level) {
|
|
||||||
final rocket = _broadcastPayloadMap(payload['rocket']);
|
|
||||||
final levelInfo = _broadcastPayloadMap(payload['levelInfo']);
|
|
||||||
final config = _broadcastPayloadMap(payload['config']);
|
|
||||||
return _firstNonBlank([
|
|
||||||
_payloadText(payload['rocket_icon_url']),
|
|
||||||
_payloadText(payload['rocketIconUrl']),
|
|
||||||
_payloadText(payload['rocketIcon']),
|
|
||||||
_payloadText(payload['iconUrl']),
|
|
||||||
_payloadText(rocket['rocket_icon_url']),
|
|
||||||
_payloadText(rocket['rocketIconUrl']),
|
|
||||||
_payloadText(rocket['rocketIcon']),
|
|
||||||
_payloadText(levelInfo['rocket_icon_url']),
|
|
||||||
_payloadText(levelInfo['rocketIconUrl']),
|
|
||||||
_payloadText(levelInfo['rocketIcon']),
|
|
||||||
_payloadText(config['rocket_icon_url']),
|
|
||||||
_payloadText(config['rocketIconUrl']),
|
|
||||||
_payloadText(config['rocketIcon']),
|
|
||||||
_rocketIconUrlForLevel(level),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _rocketIconUrlForLevel(int level) {
|
String _rocketIconUrlForLevel(int level) {
|
||||||
final currentContext = context;
|
final currentContext = context;
|
||||||
if (currentContext == null || !currentContext.mounted) {
|
if (currentContext == null || !currentContext.mounted) {
|
||||||
@ -2196,29 +1973,12 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
String _rocketRewardRecordKey(SCRoomRocketRewardRecordRes record) {
|
|
||||||
return [
|
|
||||||
'rocket_reward',
|
|
||||||
record.id,
|
|
||||||
record.launchNo,
|
|
||||||
record.userId,
|
|
||||||
record.rewardScene,
|
|
||||||
record.rewardType,
|
|
||||||
record.rewardItemId,
|
|
||||||
].join('|');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _showRoomRocketRewardPopupDialog(String roomId) async {
|
Future<void> _showRoomRocketRewardPopupDialog(String roomId) async {
|
||||||
try {
|
try {
|
||||||
final res = await SCChatRoomRepository().roomRocketRewardPopups(roomId);
|
final res = await SCChatRoomRepository().roomRocketRewardPopups(roomId);
|
||||||
if (res.records.isEmpty) {
|
if (res.records.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
unawaited(
|
|
||||||
_sendRoomRocketRewardPublicMessages(roomId, {
|
|
||||||
'records': res.records.map((item) => item.toJson()).toList(),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
final recordIds = res.records
|
final recordIds = res.records
|
||||||
.map((item) => item.id)
|
.map((item) => item.id)
|
||||||
.where((item) => item.isNotEmpty)
|
.where((item) => item.isNotEmpty)
|
||||||
@ -2273,6 +2033,29 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Msg _buildRoomRocketLaunchNoticeMsg(
|
||||||
|
String groupId,
|
||||||
|
RoomRocketLaunchBroadcastMessage launch,
|
||||||
|
) {
|
||||||
|
final seconds = launch.durationSeconds <= 0 ? 10 : launch.durationSeconds;
|
||||||
|
return Msg(
|
||||||
|
groupId: groupId,
|
||||||
|
msg: seconds.toString(),
|
||||||
|
type: SCRoomMsgType.rocketLaunchNotice,
|
||||||
|
number: seconds,
|
||||||
|
rocketIconUrl: _firstNonBlank([
|
||||||
|
launch.rocketIconUrl,
|
||||||
|
_rocketIconUrlForLevel(launch.safeLevel),
|
||||||
|
]),
|
||||||
|
giftBatchId: [
|
||||||
|
'rocket_launch_notice',
|
||||||
|
launch.launchNo,
|
||||||
|
launch.roomId,
|
||||||
|
launch.safeLevel,
|
||||||
|
].join('|'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
List<String> _payloadStringList(dynamic value) {
|
List<String> _payloadStringList(dynamic value) {
|
||||||
if (value is Iterable) {
|
if (value is Iterable) {
|
||||||
return value
|
return value
|
||||||
|
|||||||
@ -376,8 +376,8 @@ class SCRoomRocketRewardRes {
|
|||||||
_asMap(data['config'])['rocketIconUrl'],
|
_asMap(data['config'])['rocketIconUrl'],
|
||||||
_asMap(data['config'])['rocketIcon'],
|
_asMap(data['config'])['rocketIcon'],
|
||||||
]),
|
]),
|
||||||
rewardAmount: _numValue(data['rewardAmount']) ?? 0,
|
rewardAmount: _rewardAmountValue(data['rewardAmount']) ?? 0,
|
||||||
expireDays: _intValue(data['expireDays']),
|
expireDays: _dayValue(data['expireDays']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -887,7 +887,7 @@ class SCRoomRocketRewardRecordRes {
|
|||||||
rewardItemId: _stringValue(data['rewardItemId']),
|
rewardItemId: _stringValue(data['rewardItemId']),
|
||||||
rewardName: _stringValue(data['rewardName']),
|
rewardName: _stringValue(data['rewardName']),
|
||||||
rewardCover: _stringValue(data['rewardCover']),
|
rewardCover: _stringValue(data['rewardCover']),
|
||||||
rewardAmount: _numValue(data['rewardAmount']) ?? 0,
|
rewardAmount: _rewardAmountValue(data['rewardAmount']) ?? 0,
|
||||||
rocketIconUrl: _firstAssetString([
|
rocketIconUrl: _firstAssetString([
|
||||||
data['rocket_icon_url'],
|
data['rocket_icon_url'],
|
||||||
data['rocketIconUrl'],
|
data['rocketIconUrl'],
|
||||||
@ -903,7 +903,7 @@ class SCRoomRocketRewardRecordRes {
|
|||||||
_asMap(data['config'])['rocketIconUrl'],
|
_asMap(data['config'])['rocketIconUrl'],
|
||||||
_asMap(data['config'])['rocketIcon'],
|
_asMap(data['config'])['rocketIcon'],
|
||||||
]),
|
]),
|
||||||
expireDays: _intValue(data['expireDays']),
|
expireDays: _dayValue(data['expireDays']),
|
||||||
grantStatus: _stringValue(data['grantStatus']),
|
grantStatus: _stringValue(data['grantStatus']),
|
||||||
popupStatus: _stringValue(data['popupStatus']),
|
popupStatus: _stringValue(data['popupStatus']),
|
||||||
grantTime: _stringValue(data['grantTime']),
|
grantTime: _stringValue(data['grantTime']),
|
||||||
@ -1116,6 +1116,24 @@ num? _numValue(dynamic value) {
|
|||||||
return num.tryParse(_stringValue(value));
|
return num.tryParse(_stringValue(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
num? _rewardAmountValue(dynamic value) {
|
||||||
|
return _numValue(value) ?? _dayNumberValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _dayValue(dynamic value, {int fallback = 0}) {
|
||||||
|
final numeric = _numValue(value) ?? _dayNumberValue(value);
|
||||||
|
return numeric?.toInt() ?? fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
num? _dayNumberValue(dynamic value) {
|
||||||
|
final text = _stringValue(value);
|
||||||
|
final match = RegExp(
|
||||||
|
r'^(\d+(?:\.\d+)?)\s*d$',
|
||||||
|
caseSensitive: false,
|
||||||
|
).firstMatch(text);
|
||||||
|
return match == null ? null : num.tryParse(match.group(1)!);
|
||||||
|
}
|
||||||
|
|
||||||
bool _boolValue(dynamic value, {bool fallback = false}) {
|
bool _boolValue(dynamic value, {bool fallback = false}) {
|
||||||
if (value is bool) {
|
if (value is bool) {
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@ -25,7 +25,17 @@ class RoomRocketLaunchBroadcastMessage {
|
|||||||
level: _intValue(data['level'], fallback: 1),
|
level: _intValue(data['level'], fallback: 1),
|
||||||
rocketIconUrl: _rocketIconUrl(data),
|
rocketIconUrl: _rocketIconUrl(data),
|
||||||
rocketAnimationUrl: _rocketAnimationUrl(data),
|
rocketAnimationUrl: _rocketAnimationUrl(data),
|
||||||
durationSeconds: _intValue(data['durationSeconds'], fallback: 7),
|
durationSeconds: _firstIntValue([
|
||||||
|
data['countdown_seconds'],
|
||||||
|
data['countdownSeconds'],
|
||||||
|
data['launch_delay_seconds'],
|
||||||
|
data['launchDelaySeconds'],
|
||||||
|
data['delaySeconds'],
|
||||||
|
data['durationSeconds'],
|
||||||
|
data['broadcastDurationSeconds'],
|
||||||
|
_asMap(data['config'])['countdownSeconds'],
|
||||||
|
_asMap(data['config'])['launchDelaySeconds'],
|
||||||
|
], fallback: 10),
|
||||||
triggerUser: RoomRocketLaunchTriggerUser.fromJson(data['triggerUser']),
|
triggerUser: RoomRocketLaunchTriggerUser.fromJson(data['triggerUser']),
|
||||||
triggerUserId: _stringValue(data['triggerUserId']),
|
triggerUserId: _stringValue(data['triggerUserId']),
|
||||||
launchNo: _stringValue(data['launchNo']),
|
launchNo: _stringValue(data['launchNo']),
|
||||||
@ -59,7 +69,7 @@ class RoomRocketLaunchBroadcastMessage {
|
|||||||
|
|
||||||
Duration get displayDuration {
|
Duration get displayDuration {
|
||||||
if (durationSeconds <= 0) {
|
if (durationSeconds <= 0) {
|
||||||
return const Duration(seconds: 7);
|
return const Duration(seconds: 10);
|
||||||
}
|
}
|
||||||
return Duration(seconds: durationSeconds);
|
return Duration(seconds: durationSeconds);
|
||||||
}
|
}
|
||||||
@ -195,3 +205,18 @@ int _intValue(dynamic value, {int fallback = 0}) {
|
|||||||
}
|
}
|
||||||
return int.tryParse(_stringValue(value)) ?? fallback;
|
return int.tryParse(_stringValue(value)) ?? fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int _firstIntValue(Iterable<dynamic> values, {int fallback = 0}) {
|
||||||
|
for (final value in values) {
|
||||||
|
final parsed =
|
||||||
|
value is int
|
||||||
|
? value
|
||||||
|
: value is num
|
||||||
|
? value.toInt()
|
||||||
|
: int.tryParse(_stringValue(value));
|
||||||
|
if (parsed != null) {
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|||||||
@ -235,7 +235,28 @@ class RoomRocketRewardItem {
|
|||||||
if (isGoldReward) {
|
if (isGoldReward) {
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
return expireDays > 0 ? '${expireDays}D' : amount;
|
return _formatDayText(expireDays > 0 ? expireDays.toString() : amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _formatDayText(String value) {
|
||||||
|
final text = value.trim();
|
||||||
|
if (text.isEmpty) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
final dayMatch = RegExp(
|
||||||
|
r'^(\d+(?:\.\d+)?)\s*d$',
|
||||||
|
caseSensitive: false,
|
||||||
|
).firstMatch(text);
|
||||||
|
final dayValue = dayMatch?.group(1) ?? _normalizedNumber(text);
|
||||||
|
return dayValue == null ? text : '${dayValue}d';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _normalizedNumber(String value) {
|
||||||
|
final parsed = num.tryParse(value);
|
||||||
|
if (parsed == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parsed % 1 == 0 ? parsed.toInt().toString() : parsed.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,18 +401,20 @@ class _RocketCanvas extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRocketBody() {
|
Widget _buildRocketBody() {
|
||||||
|
final previewImageUrl = selectedLevelItem.previewImageUrl?.trim() ?? '';
|
||||||
return Positioned(
|
return Positioned(
|
||||||
left: s(88),
|
left: s(88),
|
||||||
top: s(111),
|
top: s(111),
|
||||||
width: s(200),
|
width: s(200),
|
||||||
height: s(432),
|
height: s(432),
|
||||||
child: _RocketImage(
|
child: _RocketImage(
|
||||||
asset: selectedLevelItem.asset ?? _RocketAssets.rocketBody,
|
imageUrl: previewImageUrl.isEmpty ? null : previewImageUrl,
|
||||||
imageUrl: selectedLevelItem.previewImageUrl,
|
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
preferSvg: true,
|
preferSvg: true,
|
||||||
pagDisplayScale: 2,
|
pagDisplayScale: 2,
|
||||||
pagClipBehavior: Clip.none,
|
pagClipBehavior: Clip.none,
|
||||||
|
allowStaticFallback: false,
|
||||||
|
emptyBuilder: (_) => const SCRotatingDotsLoading(),
|
||||||
debugName:
|
debugName:
|
||||||
'centerPreview level=${selectedLevelItem.level} '
|
'centerPreview level=${selectedLevelItem.level} '
|
||||||
'preview=${selectedLevelItem.previewImageUrl ?? '-'}',
|
'preview=${selectedLevelItem.previewImageUrl ?? '-'}',
|
||||||
@ -470,11 +493,6 @@ class _RocketCanvas extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildProgress() {
|
Widget _buildProgress() {
|
||||||
final asset = switch (progressStage) {
|
|
||||||
RoomRocketProgressStage.empty => _RocketAssets.progressEmpty,
|
|
||||||
RoomRocketProgressStage.charging => _RocketAssets.progressPartial,
|
|
||||||
RoomRocketProgressStage.full => _RocketAssets.progressFull,
|
|
||||||
};
|
|
||||||
final progressImageUrl = selectedLevelItem.progressImageUrl?.trim() ?? '';
|
final progressImageUrl = selectedLevelItem.progressImageUrl?.trim() ?? '';
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
@ -483,10 +501,10 @@ class _RocketCanvas extends StatelessWidget {
|
|||||||
top: s(227),
|
top: s(227),
|
||||||
width: s(44),
|
width: s(44),
|
||||||
height: s(134),
|
height: s(134),
|
||||||
child: _RocketImage(
|
child: _RocketProgressBar(
|
||||||
asset: asset,
|
percent: percent,
|
||||||
|
progressStage: progressStage,
|
||||||
imageUrl: progressImageUrl.isEmpty ? null : progressImageUrl,
|
imageUrl: progressImageUrl.isEmpty ? null : progressImageUrl,
|
||||||
fit: BoxFit.fill,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
@ -807,6 +825,96 @@ class _RocketCanvas extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _RocketProgressBar extends StatelessWidget {
|
||||||
|
const _RocketProgressBar({
|
||||||
|
required this.percent,
|
||||||
|
required this.progressStage,
|
||||||
|
this.imageUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
final double percent;
|
||||||
|
final RoomRocketProgressStage progressStage;
|
||||||
|
final String? imageUrl;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final width = constraints.maxWidth;
|
||||||
|
final height = constraints.maxHeight;
|
||||||
|
final ratio = _progressRatio();
|
||||||
|
final fillHeight = height * ratio;
|
||||||
|
final showFillCap =
|
||||||
|
fillHeight > 0 &&
|
||||||
|
ratio < 1 &&
|
||||||
|
progressStage != RoomRocketProgressStage.full;
|
||||||
|
final capHeight = math.max(1.0, height * 0.014);
|
||||||
|
|
||||||
|
return Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
const _RocketImage(
|
||||||
|
asset: _RocketAssets.progressEmpty,
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
if (fillHeight > 0)
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
bottom: 0,
|
||||||
|
width: width,
|
||||||
|
height: fillHeight,
|
||||||
|
child: ClipRect(
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: SizedBox(
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
child: _RocketImage(
|
||||||
|
asset: _RocketAssets.progressFull,
|
||||||
|
imageUrl: imageUrl,
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (showFillCap)
|
||||||
|
Positioned(
|
||||||
|
left: width * 0.08,
|
||||||
|
bottom: math.max(0, fillHeight - capHeight / 2),
|
||||||
|
width: width * 0.84,
|
||||||
|
height: capHeight,
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(capHeight),
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
const Color(0xFF30FFC0).withValues(alpha: 0),
|
||||||
|
const Color(0xFFA4FFE3).withValues(alpha: 0.9),
|
||||||
|
const Color(0xFF30FFC0).withValues(alpha: 0),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: const Color(0xFF2DFFC1).withValues(alpha: 0.7),
|
||||||
|
blurRadius: capHeight * 2,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
double _progressRatio() {
|
||||||
|
final value = percent.isFinite ? percent : 0.0;
|
||||||
|
return (value / 100).clamp(0.0, 1.0).toDouble();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _RewardCard extends StatelessWidget {
|
class _RewardCard extends StatelessWidget {
|
||||||
const _RewardCard({required this.scale, required this.item, this.onTap});
|
const _RewardCard({required this.scale, required this.item, this.onTap});
|
||||||
|
|
||||||
@ -978,6 +1086,8 @@ class _RocketImage extends StatelessWidget {
|
|||||||
this.preferSvg = false,
|
this.preferSvg = false,
|
||||||
this.pagDisplayScale = 1,
|
this.pagDisplayScale = 1,
|
||||||
this.pagClipBehavior = Clip.hardEdge,
|
this.pagClipBehavior = Clip.hardEdge,
|
||||||
|
this.allowStaticFallback = true,
|
||||||
|
this.emptyBuilder,
|
||||||
this.debugName,
|
this.debugName,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -987,6 +1097,8 @@ class _RocketImage extends StatelessWidget {
|
|||||||
final bool preferSvg;
|
final bool preferSvg;
|
||||||
final double pagDisplayScale;
|
final double pagDisplayScale;
|
||||||
final Clip pagClipBehavior;
|
final Clip pagClipBehavior;
|
||||||
|
final bool allowStaticFallback;
|
||||||
|
final WidgetBuilder? emptyBuilder;
|
||||||
final String? debugName;
|
final String? debugName;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1035,14 +1147,23 @@ class _RocketImage extends StatelessWidget {
|
|||||||
errorBuilder: (_, error, __) {
|
errorBuilder: (_, error, __) {
|
||||||
_debugImage('network svg failed: $error');
|
_debugImage('network svg failed: $error');
|
||||||
return preferSvg && !_isSvg(source)
|
return preferSvg && !_isSvg(source)
|
||||||
? CustomCachedImage(imageUrl: source, fit: fit)
|
? CustomCachedImage(
|
||||||
|
imageUrl: source,
|
||||||
|
fit: fit,
|
||||||
|
errorWidget:
|
||||||
|
allowStaticFallback ? null : const SizedBox.shrink(),
|
||||||
|
)
|
||||||
: _imageFallback();
|
: _imageFallback();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return CustomCachedImage(imageUrl: source, fit: fit);
|
return CustomCachedImage(
|
||||||
|
imageUrl: source,
|
||||||
|
fit: fit,
|
||||||
|
errorWidget: allowStaticFallback ? null : const SizedBox.shrink(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (asset != null && asset!.trim().isNotEmpty) {
|
if (allowStaticFallback && asset != null && asset!.trim().isNotEmpty) {
|
||||||
final source = asset!.trim();
|
final source = asset!.trim();
|
||||||
_debugImage('load asset ${_shortSource(source)}');
|
_debugImage('load asset ${_shortSource(source)}');
|
||||||
if (SCNetworkSvgaWidget.isSvga(source)) {
|
if (SCNetworkSvgaWidget.isSvga(source)) {
|
||||||
@ -1080,7 +1201,7 @@ class _RocketImage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
return Image.asset(source, fit: fit, gaplessPlayback: true);
|
return Image.asset(source, fit: fit, gaplessPlayback: true);
|
||||||
}
|
}
|
||||||
return const SizedBox.shrink();
|
return emptyBuilder?.call(context) ?? const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _isSvg(String value) {
|
static bool _isSvg(String value) {
|
||||||
@ -1123,6 +1244,9 @@ class _RocketImage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _imageFallback() {
|
Widget _imageFallback() {
|
||||||
|
if (!allowStaticFallback) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
if (asset != null &&
|
if (asset != null &&
|
||||||
asset!.trim().isNotEmpty &&
|
asset!.trim().isNotEmpty &&
|
||||||
!_isSvg(asset!) &&
|
!_isSvg(asset!) &&
|
||||||
@ -1747,8 +1871,6 @@ class _RocketAssets {
|
|||||||
'sc_images/room/rocket/bottom_avatar_ring.png';
|
'sc_images/room/rocket/bottom_avatar_ring.png';
|
||||||
static const String pillBg = 'sc_images/room/rocket/pill_bg.png';
|
static const String pillBg = 'sc_images/room/rocket/pill_bg.png';
|
||||||
static const String progressFull = 'sc_images/room/rocket/progress_full.png';
|
static const String progressFull = 'sc_images/room/rocket/progress_full.png';
|
||||||
static const String progressPartial =
|
|
||||||
'sc_images/room/rocket/progress_partial.png';
|
|
||||||
static const String progressEmpty =
|
static const String progressEmpty =
|
||||||
'sc_images/room/rocket/progress_empty.png';
|
'sc_images/room/rocket/progress_empty.png';
|
||||||
static const String rewardTop = 'sc_images/room/rocket/reward_dialog_top.png';
|
static const String rewardTop = 'sc_images/room/rocket/reward_dialog_top.png';
|
||||||
|
|||||||
@ -469,15 +469,6 @@ class _RewardDialogCanvas extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _handleHorizontalScrollNotification(ScrollNotification notification) {
|
bool _handleHorizontalScrollNotification(ScrollNotification notification) {
|
||||||
if (notification.metrics.axis != Axis.horizontal) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!hasMore || isLoadingMore) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (notification.metrics.extentAfter <= s(96)) {
|
|
||||||
onLoadMore?.call();
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -70,10 +70,12 @@ class _RoomRocketRewardDialogLoaderState
|
|||||||
widget.initialPopupRecords.isNotEmpty &&
|
widget.initialPopupRecords.isNotEmpty &&
|
||||||
!_usedInitialPopupRecords;
|
!_usedInitialPopupRecords;
|
||||||
final nextPopupRecords =
|
final nextPopupRecords =
|
||||||
useInitialPopupRecords
|
reset
|
||||||
? widget.initialPopupRecords
|
? useInitialPopupRecords
|
||||||
: await _loadPopupRecords(requestCursor);
|
? widget.initialPopupRecords
|
||||||
if (useInitialPopupRecords) {
|
: await _loadPopupRecords(requestCursor)
|
||||||
|
: const <SCRoomRocketRewardRecordRes>[];
|
||||||
|
if (reset && useInitialPopupRecords) {
|
||||||
_usedInitialPopupRecords = true;
|
_usedInitialPopupRecords = true;
|
||||||
}
|
}
|
||||||
final nextRoomRecords = await _loadRoomRewardRecords(requestCursor);
|
final nextRoomRecords = await _loadRoomRewardRecords(requestCursor);
|
||||||
@ -89,16 +91,13 @@ class _RoomRocketRewardDialogLoaderState
|
|||||||
..clear()
|
..clear()
|
||||||
..addAll(nextRoomRecords);
|
..addAll(nextRoomRecords);
|
||||||
} else {
|
} else {
|
||||||
_popupRecords.addAll(nextPopupRecords);
|
|
||||||
_roomRecords.addAll(nextRoomRecords);
|
_roomRecords.addAll(nextRoomRecords);
|
||||||
}
|
}
|
||||||
_rewards = RoomRocketApiMapper.rewardItemsFromRecords(_popupRecords);
|
_rewards = RoomRocketApiMapper.rewardItemsFromRecords(_popupRecords);
|
||||||
_winners = RoomRocketApiMapper.winnersFromRecords(
|
_winners = RoomRocketApiMapper.winnersFromRecords(
|
||||||
_visibleRoomRewardRecords(),
|
_visibleRoomRewardRecords(),
|
||||||
);
|
);
|
||||||
_hasMore =
|
_hasMore = nextRoomRecords.length >= _rewardDialogPageSize;
|
||||||
nextPopupRecords.length >= _rewardDialogPageSize ||
|
|
||||||
nextRoomRecords.length >= _rewardDialogPageSize;
|
|
||||||
if (_hasMore) {
|
if (_hasMore) {
|
||||||
_cursor = requestCursor + 1;
|
_cursor = requestCursor + 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -85,6 +85,11 @@ class _MsgItemState extends State<MsgItem> {
|
|||||||
return _buildRocketRewardMsg(context);
|
return _buildRocketRewardMsg(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///火箭燃料加满倒计时
|
||||||
|
if (widget.msg.type == SCRoomMsgType.rocketLaunchNotice) {
|
||||||
|
return _buildRocketLaunchNoticeMsg(context);
|
||||||
|
}
|
||||||
|
|
||||||
///幸运礼物
|
///幸运礼物
|
||||||
if (widget.msg.type == SCRoomMsgType.gameLuckyGift) {
|
if (widget.msg.type == SCRoomMsgType.gameLuckyGift) {
|
||||||
return _buildGameLuckyGiftMsg(context);
|
return _buildGameLuckyGiftMsg(context);
|
||||||
@ -764,6 +769,73 @@ class _MsgItemState extends State<MsgItem> {
|
|||||||
return netImage(url: iconUrl, width: 18.w);
|
return netImage(url: iconUrl, width: 18.w);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildRocketLaunchNoticeMsg(BuildContext context) {
|
||||||
|
final seconds =
|
||||||
|
(widget.msg.number ?? num.tryParse(widget.msg.msg ?? '') ?? 10).toInt();
|
||||||
|
final template =
|
||||||
|
SCAppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)?.roomRocketFuelFullNotice(seconds.toString()) ??
|
||||||
|
'Rocket fuel is full! Launching in ${seconds}s!';
|
||||||
|
final iconUrl = widget.msg.rocketIconUrl?.trim() ?? '';
|
||||||
|
return Container(
|
||||||
|
alignment: AlignmentDirectional.topStart,
|
||||||
|
child: Container(
|
||||||
|
constraints: BoxConstraints(minHeight: 42.w, maxWidth: 304.w),
|
||||||
|
padding: EdgeInsetsDirectional.only(
|
||||||
|
start: 12.w,
|
||||||
|
end: 10.w,
|
||||||
|
top: 8.w,
|
||||||
|
bottom: 8.w,
|
||||||
|
),
|
||||||
|
margin: EdgeInsetsDirectional.only(start: 10.w, end: 36.w, bottom: 8.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFF1F2FF).withValues(alpha: 0.92),
|
||||||
|
borderRadius: BorderRadius.circular(9.w),
|
||||||
|
border: Border.all(color: const Color(0xFF6672FF), width: 1.w),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
template,
|
||||||
|
textScaler: TextScaler.noScaling,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
color: const Color(0xFF6672FF),
|
||||||
|
fontSize: sp(13),
|
||||||
|
height: 1.2,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8.w),
|
||||||
|
SizedBox(
|
||||||
|
width: 42.w,
|
||||||
|
height: 28.w,
|
||||||
|
child:
|
||||||
|
iconUrl.isEmpty
|
||||||
|
? Icon(
|
||||||
|
Icons.rocket_launch_rounded,
|
||||||
|
color: const Color(0xFFB8BEFA),
|
||||||
|
size: 28.w,
|
||||||
|
)
|
||||||
|
: netImage(
|
||||||
|
url: iconUrl,
|
||||||
|
width: 42.w,
|
||||||
|
height: 28.w,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
noDefaultImg: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
///幸运礼物
|
///幸运礼物
|
||||||
_buildGameLuckyGiftMsg(BuildContext context) {
|
_buildGameLuckyGiftMsg(BuildContext context) {
|
||||||
List<InlineSpan> spans = [];
|
List<InlineSpan> spans = [];
|
||||||
|
|||||||
@ -139,6 +139,42 @@ void main() {
|
|||||||
expect(groups[2]?.inRoom.single.expireDays, 7);
|
expect(groups[2]?.inRoom.single.expireDays, 7);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('normalizes non-gold rocket reward days display text', () {
|
||||||
|
final rewards = [
|
||||||
|
SCRoomRocketRewardRes.fromJson({
|
||||||
|
'rewardType': 'PROPS',
|
||||||
|
'rewardAmount': '7d',
|
||||||
|
}),
|
||||||
|
SCRoomRocketRewardRes.fromJson({
|
||||||
|
'rewardType': 'PROPS',
|
||||||
|
'rewardAmount': 1,
|
||||||
|
}),
|
||||||
|
SCRoomRocketRewardRes.fromJson({
|
||||||
|
'rewardType': 'PROPS',
|
||||||
|
'rewardAmount': 1,
|
||||||
|
'expireDays': '30D',
|
||||||
|
}),
|
||||||
|
SCRoomRocketRewardRes.fromJson({
|
||||||
|
'rewardType': 'GOLD',
|
||||||
|
'rewardAmount': 100,
|
||||||
|
}),
|
||||||
|
].map(RoomRocketApiMapper.rewardItemFromPreview).toList(growable: false);
|
||||||
|
|
||||||
|
expect(rewards[0].displayText, '7d');
|
||||||
|
expect(rewards[1].displayText, '1d');
|
||||||
|
expect(rewards[2].displayText, '30d');
|
||||||
|
expect(rewards[3].displayText, '100');
|
||||||
|
|
||||||
|
final recordRewards = RoomRocketApiMapper.rewardItemsFromRecords([
|
||||||
|
SCRoomRocketRewardRecordRes.fromJson({
|
||||||
|
'rewardType': 'PROPS',
|
||||||
|
'rewardAmount': '7d',
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(recordRewards.single.displayText, '7d');
|
||||||
|
});
|
||||||
|
|
||||||
test('parses rocket level contents response from go api', () {
|
test('parses rocket level contents response from go api', () {
|
||||||
final contents = SCRoomRocketLevelContentsRes.fromJson({
|
final contents = SCRoomRocketLevelContentsRes.fromJson({
|
||||||
'configured': true,
|
'configured': true,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user