Merge branch 'feature' into test
This commit is contained in:
commit
d5653afa4f
@ -280,8 +280,8 @@
|
|||||||
"bDLeaderInviteYouToBecomeBDLeader": "ندعوك لتصبح قائد تطوير الأعمال",
|
"bDLeaderInviteYouToBecomeBDLeader": "ندعوك لتصبح قائد تطوير الأعمال",
|
||||||
"win2": "فوز {1}",
|
"win2": "فوز {1}",
|
||||||
"level": "مستوى",
|
"level": "مستوى",
|
||||||
"wealthLevel": "مستوى الثروة",
|
"wealthLevel": "مستوى الثراء",
|
||||||
"userLevel": "مستوى المستخدم",
|
"userLevel": "مستوى الجاذبية",
|
||||||
"themeGoToUploadTips": "1. ستتم مراجعة التحميل خلال 24 ساعة بعد نجاحه.\n2. ستُعاد جميع العملات إذا فشلت المراجعة.",
|
"themeGoToUploadTips": "1. ستتم مراجعة التحميل خلال 24 ساعة بعد نجاحه.\n2. ستُعاد جميع العملات إذا فشلت المراجعة.",
|
||||||
"pleaseUploadAccordingToExample": "يرجى الرفع وفقًا للمثال المعروض.",
|
"pleaseUploadAccordingToExample": "يرجى الرفع وفقًا للمثال المعروض.",
|
||||||
"goToUpload": "الانتقال للرفع",
|
"goToUpload": "الانتقال للرفع",
|
||||||
@ -666,6 +666,8 @@
|
|||||||
"buy": "شراء",
|
"buy": "شراء",
|
||||||
"visitorList": "قائمة الزوار",
|
"visitorList": "قائمة الزوار",
|
||||||
"spendCoinsToGainExperiencePoints": "اصرف العملات لكسب نقاط الخبرة",
|
"spendCoinsToGainExperiencePoints": "اصرف العملات لكسب نقاط الخبرة",
|
||||||
|
"richLevelUpgradeAnswer": "دع ثروتك تغزو كل شيء",
|
||||||
|
"charmLevelUpgradeAnswer": "اجعل الآخرين يخضعون لسحرك",
|
||||||
"howToUpgrade": "كيف يمكن الترقية؟",
|
"howToUpgrade": "كيف يمكن الترقية؟",
|
||||||
"higherLevelFancierAvatarFrame": "مستوى أعلى، شارات/إطار صور أفخم.",
|
"higherLevelFancierAvatarFrame": "مستوى أعلى، شارات/إطار صور أفخم.",
|
||||||
"use": "استخدم",
|
"use": "استخدم",
|
||||||
|
|||||||
@ -267,7 +267,7 @@
|
|||||||
"aboutUs": "আমাদের সম্পর্কে",
|
"aboutUs": "আমাদের সম্পর্কে",
|
||||||
"theme": "থিম",
|
"theme": "থিম",
|
||||||
"wealthLevel": "সম্পদ স্তর",
|
"wealthLevel": "সম্পদ স্তর",
|
||||||
"userLevel": "ব্যবহারকারী স্তর",
|
"userLevel": "আকর্ষণ স্তর",
|
||||||
"goToUpload": "আপলোড করতে যান",
|
"goToUpload": "আপলোড করতে যান",
|
||||||
"logout": "লগআউট",
|
"logout": "লগআউট",
|
||||||
"luck": "ভাগ্য",
|
"luck": "ভাগ্য",
|
||||||
@ -600,6 +600,8 @@
|
|||||||
"toConsume": "খরচ করার জন্য",
|
"toConsume": "খরচ করার জন্য",
|
||||||
"howToUpgrade": "কীভাবে উন্নত করব?",
|
"howToUpgrade": "কীভাবে উন্নত করব?",
|
||||||
"spendCoinsToGainExperiencePoints": "অভিজ্ঞতা পয়েন্ট পাওয়ার জন্য কয়েন খরচ করুন",
|
"spendCoinsToGainExperiencePoints": "অভিজ্ঞতা পয়েন্ট পাওয়ার জন্য কয়েন খরচ করুন",
|
||||||
|
"richLevelUpgradeAnswer": "তোমার সম্পদ সবকিছু জয় করুক",
|
||||||
|
"charmLevelUpgradeAnswer": "অন্যদের তোমার আকর্ষণের কাছে নত করো",
|
||||||
"higherLevelFancierAvatarFrame": "উচ্চতর স্তর, আরও সুন্দর ব্যাজ/প্রোফাইল ফ্রেম",
|
"higherLevelFancierAvatarFrame": "উচ্চতর স্তর, আরও সুন্দর ব্যাজ/প্রোফাইল ফ্রেম",
|
||||||
"all": "সব",
|
"all": "সব",
|
||||||
"gift": "গিফট",
|
"gift": "গিফট",
|
||||||
|
|||||||
@ -236,8 +236,8 @@
|
|||||||
"about": "About",
|
"about": "About",
|
||||||
"aboutUs": "About Us",
|
"aboutUs": "About Us",
|
||||||
"theme": "Theme",
|
"theme": "Theme",
|
||||||
"wealthLevel": "Wealth Level",
|
"wealthLevel": "Rich Level",
|
||||||
"userLevel": "User Level",
|
"userLevel": "Charm Level",
|
||||||
"goToUpload": "Go to upload",
|
"goToUpload": "Go to upload",
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
"luck": "Luck",
|
"luck": "Luck",
|
||||||
@ -569,6 +569,8 @@
|
|||||||
"toConsume": "To consume",
|
"toConsume": "To consume",
|
||||||
"howToUpgrade": "How to upgrade?",
|
"howToUpgrade": "How to upgrade?",
|
||||||
"spendCoinsToGainExperiencePoints": "Spend Coins to gain experience points",
|
"spendCoinsToGainExperiencePoints": "Spend Coins to gain experience points",
|
||||||
|
"richLevelUpgradeAnswer": "Let your wealth conquer everything",
|
||||||
|
"charmLevelUpgradeAnswer": "Make others submit to your charm",
|
||||||
"higherLevelFancierAvatarFrame": "Higher level, fancier badges/avatar frame",
|
"higherLevelFancierAvatarFrame": "Higher level, fancier badges/avatar frame",
|
||||||
"all": "All",
|
"all": "All",
|
||||||
"gift": "Gift",
|
"gift": "Gift",
|
||||||
@ -724,7 +726,7 @@
|
|||||||
"roomRocketNoteTitle": "Note",
|
"roomRocketNoteTitle": "Note",
|
||||||
"roomRocketNoteSectionTitle": "Each time a VIP",
|
"roomRocketNoteSectionTitle": "Each time a VIP",
|
||||||
"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 reward",
|
||||||
"roomRocketRecordEmptyText": "No reward records",
|
"roomRocketRecordEmptyText": "No reward records",
|
||||||
"launchRocket": "Launch Rocket",
|
"launchRocket": "Launch Rocket",
|
||||||
"roomRocketFuelFullNotice": "Rocket fuel is full! Launching in {1}s!"
|
"roomRocketFuelFullNotice": "Rocket fuel is full! Launching in {1}s!"
|
||||||
|
|||||||
@ -237,7 +237,7 @@
|
|||||||
"aboutUs": "À propos de nous",
|
"aboutUs": "À propos de nous",
|
||||||
"theme": "Thème",
|
"theme": "Thème",
|
||||||
"wealthLevel": "Niveau de richesse",
|
"wealthLevel": "Niveau de richesse",
|
||||||
"userLevel": "Niveau utilisateur",
|
"userLevel": "Niveau de charme",
|
||||||
"goToUpload": "Aller au téléchargement",
|
"goToUpload": "Aller au téléchargement",
|
||||||
"logout": "Déconnexion",
|
"logout": "Déconnexion",
|
||||||
"luck": "Chance",
|
"luck": "Chance",
|
||||||
@ -569,6 +569,8 @@
|
|||||||
"toConsume": "Consommer",
|
"toConsume": "Consommer",
|
||||||
"howToUpgrade": "Comment mettre à niveau ?",
|
"howToUpgrade": "Comment mettre à niveau ?",
|
||||||
"spendCoinsToGainExperiencePoints": "Dépensez des pièces pour gagner des points d'expérience",
|
"spendCoinsToGainExperiencePoints": "Dépensez des pièces pour gagner des points d'expérience",
|
||||||
|
"richLevelUpgradeAnswer": "Que ta richesse conquière tout",
|
||||||
|
"charmLevelUpgradeAnswer": "Fais s'incliner les autres devant ton charme",
|
||||||
"higherLevelFancierAvatarFrame": "Cadre de badges/avatar de niveau supérieur et plus sophistiqué",
|
"higherLevelFancierAvatarFrame": "Cadre de badges/avatar de niveau supérieur et plus sophistiqué",
|
||||||
"all": "Tout",
|
"all": "Tout",
|
||||||
"gift": "Cadeau",
|
"gift": "Cadeau",
|
||||||
|
|||||||
@ -237,7 +237,7 @@
|
|||||||
"aboutUs": "Chi siamo",
|
"aboutUs": "Chi siamo",
|
||||||
"theme": "Tema",
|
"theme": "Tema",
|
||||||
"wealthLevel": "Livello di ricchezza",
|
"wealthLevel": "Livello di ricchezza",
|
||||||
"userLevel": "Livello utente",
|
"userLevel": "Livello di fascino",
|
||||||
"goToUpload": "Vai al caricamento",
|
"goToUpload": "Vai al caricamento",
|
||||||
"logout": "Esci",
|
"logout": "Esci",
|
||||||
"luck": "Fortuna",
|
"luck": "Fortuna",
|
||||||
@ -569,6 +569,8 @@
|
|||||||
"toConsume": "Consumare",
|
"toConsume": "Consumare",
|
||||||
"howToUpgrade": "Come aggiornare?",
|
"howToUpgrade": "Come aggiornare?",
|
||||||
"spendCoinsToGainExperiencePoints": "Spendi monete per guadagnare punti esperienza",
|
"spendCoinsToGainExperiencePoints": "Spendi monete per guadagnare punti esperienza",
|
||||||
|
"richLevelUpgradeAnswer": "Lascia che la tua ricchezza conquisti tutto",
|
||||||
|
"charmLevelUpgradeAnswer": "Fa' che gli altri si arrendano al tuo fascino",
|
||||||
"higherLevelFancierAvatarFrame": "Distintivi/cornice avatar di livello superiore e più elaborati",
|
"higherLevelFancierAvatarFrame": "Distintivi/cornice avatar di livello superiore e più elaborati",
|
||||||
"all": "Tutto",
|
"all": "Tutto",
|
||||||
"gift": "Regalo",
|
"gift": "Regalo",
|
||||||
|
|||||||
@ -237,7 +237,7 @@
|
|||||||
"aboutUs": "Sobre nós",
|
"aboutUs": "Sobre nós",
|
||||||
"theme": "Tema",
|
"theme": "Tema",
|
||||||
"wealthLevel": "Nível de riqueza",
|
"wealthLevel": "Nível de riqueza",
|
||||||
"userLevel": "Nível de usuário",
|
"userLevel": "Nível de charme",
|
||||||
"goToUpload": "Ir para carregar",
|
"goToUpload": "Ir para carregar",
|
||||||
"logout": "Sair",
|
"logout": "Sair",
|
||||||
"luck": "Sorte",
|
"luck": "Sorte",
|
||||||
@ -569,6 +569,8 @@
|
|||||||
"toConsume": "Para consumir",
|
"toConsume": "Para consumir",
|
||||||
"howToUpgrade": "Como atualizar?",
|
"howToUpgrade": "Como atualizar?",
|
||||||
"spendCoinsToGainExperiencePoints": "Gaste moedas para ganhar pontos de experiência",
|
"spendCoinsToGainExperiencePoints": "Gaste moedas para ganhar pontos de experiência",
|
||||||
|
"richLevelUpgradeAnswer": "Deixe sua riqueza conquistar tudo",
|
||||||
|
"charmLevelUpgradeAnswer": "Faça os outros se renderem ao seu charme",
|
||||||
"higherLevelFancierAvatarFrame": "Emblemas/moldura de avatar de nível superior e mais sofisticados",
|
"higherLevelFancierAvatarFrame": "Emblemas/moldura de avatar de nível superior e mais sofisticados",
|
||||||
"all": "Todos",
|
"all": "Todos",
|
||||||
"gift": "Presente",
|
"gift": "Presente",
|
||||||
|
|||||||
@ -225,8 +225,8 @@
|
|||||||
"about": "Hakkında",
|
"about": "Hakkında",
|
||||||
"aboutUs": "Hakkımızda",
|
"aboutUs": "Hakkımızda",
|
||||||
"theme": "Tema",
|
"theme": "Tema",
|
||||||
"wealthLevel": "Servet Seviyesi",
|
"wealthLevel": "Zenginlik Seviyesi",
|
||||||
"userLevel": "Kullanıcı Seviyesi",
|
"userLevel": "Çekicilik Seviyesi",
|
||||||
"goToUpload": "Yüklemeye Git",
|
"goToUpload": "Yüklemeye Git",
|
||||||
"logout": "Çıkış Yap",
|
"logout": "Çıkış Yap",
|
||||||
"luck": "Şans",
|
"luck": "Şans",
|
||||||
@ -569,6 +569,8 @@
|
|||||||
"toConsume": "Tüketmek İçin",
|
"toConsume": "Tüketmek İçin",
|
||||||
"howToUpgrade": "Nasıl yükseltirim?",
|
"howToUpgrade": "Nasıl yükseltirim?",
|
||||||
"spendCoinsToGainExperiencePoints": "Deneyim puanı kazanmak için jetton harcayın",
|
"spendCoinsToGainExperiencePoints": "Deneyim puanı kazanmak için jetton harcayın",
|
||||||
|
"richLevelUpgradeAnswer": "Servetin her şeyi fethetsin",
|
||||||
|
"charmLevelUpgradeAnswer": "Başkalarını cazibene boyun eğdir",
|
||||||
"higherLevelFancierAvatarFrame": "Daha yüksek seviye, daha şık rozetler/profil çerçevesi",
|
"higherLevelFancierAvatarFrame": "Daha yüksek seviye, daha şık rozetler/profil çerçevesi",
|
||||||
"all": "Tümü",
|
"all": "Tümü",
|
||||||
"gift": "Hediye",
|
"gift": "Hediye",
|
||||||
|
|||||||
@ -79,6 +79,10 @@ class SCRoomMsgType {
|
|||||||
///用户火箭中奖
|
///用户火箭中奖
|
||||||
static const String rocketRewardUser = "ROCKET_REWARD_USER";
|
static const String rocketRewardUser = "ROCKET_REWARD_USER";
|
||||||
|
|
||||||
|
///语音房火箭用户中奖
|
||||||
|
static const String voiceRoomRocketRewardUser =
|
||||||
|
"VOICE_ROOM_ROCKET_REWARD_USER";
|
||||||
|
|
||||||
///火箭燃料满倒计时发射提示
|
///火箭燃料满倒计时发射提示
|
||||||
static const String rocketLaunchNotice = "ROCKET_LAUNCH_NOTICE";
|
static const String rocketLaunchNotice = "ROCKET_LAUNCH_NOTICE";
|
||||||
|
|
||||||
@ -129,4 +133,8 @@ class SCRoomMsgType {
|
|||||||
static const String gameBurstCrystalSprint = "GAME_BURST_CRYSTAL_SPRINT";
|
static const String gameBurstCrystalSprint = "GAME_BURST_CRYSTAL_SPRINT";
|
||||||
static const String gameBurstCrystalBox = "GAME_BURST_CRYSTAL_BOX";
|
static const String gameBurstCrystalBox = "GAME_BURST_CRYSTAL_BOX";
|
||||||
static const String refreshOnlineUser = "REFRESH_ONLINE_USER";
|
static const String refreshOnlineUser = "REFRESH_ONLINE_USER";
|
||||||
|
|
||||||
|
static bool isRocketRewardUserType(String? type) {
|
||||||
|
return type == rocketRewardUser || type == voiceRoomRocketRewardUser;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1045,6 +1045,10 @@ class SCAppLocalizations {
|
|||||||
String get spendCoinsToGainExperiencePoints =>
|
String get spendCoinsToGainExperiencePoints =>
|
||||||
translate('spendCoinsToGainExperiencePoints');
|
translate('spendCoinsToGainExperiencePoints');
|
||||||
|
|
||||||
|
String get richLevelUpgradeAnswer => translate('richLevelUpgradeAnswer');
|
||||||
|
|
||||||
|
String get charmLevelUpgradeAnswer => translate('charmLevelUpgradeAnswer');
|
||||||
|
|
||||||
String get enterRoomName => translate('enterRoomName');
|
String get enterRoomName => translate('enterRoomName');
|
||||||
|
|
||||||
String get selectYourCountry => translate('selectYourCountry');
|
String get selectYourCountry => translate('selectYourCountry');
|
||||||
|
|||||||
@ -560,7 +560,6 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
///礼物上飘动画
|
///礼物上飘动画
|
||||||
_floatingGiftListener(Msg msg) {
|
_floatingGiftListener(Msg msg) {
|
||||||
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
||||||
rtcProvider.scheduleRoomRocketStatusRefreshForGift();
|
|
||||||
if (!rtcProvider.shouldShowRoomVisualEffects) {
|
if (!rtcProvider.shouldShowRoomVisualEffects) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,13 +88,7 @@ class _StoreChatboxPageState
|
|||||||
height: 45.w,
|
height: 45.w,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
),
|
),
|
||||||
SizedBox(height: 3.w),
|
SizedBox(height: 10.w),
|
||||||
buildStoreBagItemTitle(
|
|
||||||
res.res.propsResources?.name ?? "",
|
|
||||||
textColor: Colors.white,
|
|
||||||
fontSize: 11.sp,
|
|
||||||
),
|
|
||||||
SizedBox(height: 3.w),
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@ -87,13 +87,7 @@ class StoreDataCardPageState
|
|||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 8.w),
|
SizedBox(height: 12.w),
|
||||||
buildStoreBagItemTitle(
|
|
||||||
res.res.propsResources?.name ?? "",
|
|
||||||
textColor: Colors.white,
|
|
||||||
fontSize: 11.sp,
|
|
||||||
),
|
|
||||||
SizedBox(height: 3.w),
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@ -87,13 +87,7 @@ class _StoreHeaddressPageState
|
|||||||
width: 55.w,
|
width: 55.w,
|
||||||
height: 55.w,
|
height: 55.w,
|
||||||
),
|
),
|
||||||
SizedBox(height: 3.w),
|
SizedBox(height: 10.w),
|
||||||
buildStoreBagItemTitle(
|
|
||||||
res.res.propsResources?.name ?? "",
|
|
||||||
textColor: Colors.white,
|
|
||||||
fontSize: 11.sp,
|
|
||||||
),
|
|
||||||
SizedBox(height: 3.w),
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@ -91,13 +91,7 @@ class _StoreMountainsPageState
|
|||||||
width: 55.w,
|
width: 55.w,
|
||||||
height: 55.w,
|
height: 55.w,
|
||||||
),
|
),
|
||||||
SizedBox(height: 3.w),
|
SizedBox(height: 10.w),
|
||||||
buildStoreBagItemTitle(
|
|
||||||
res.res.propsResources?.name ?? "",
|
|
||||||
textColor: Colors.white,
|
|
||||||
fontSize: 11.sp,
|
|
||||||
),
|
|
||||||
SizedBox(height: 3.w),
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@ -91,13 +91,7 @@ class _StoreThemePagePageState
|
|||||||
height: 55.w,
|
height: 55.w,
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8.w)),
|
borderRadius: BorderRadius.all(Radius.circular(8.w)),
|
||||||
),
|
),
|
||||||
SizedBox(height: 3.w),
|
SizedBox(height: 10.w),
|
||||||
buildStoreBagItemTitle(
|
|
||||||
res.res.propsResources?.name ?? "",
|
|
||||||
textColor: Colors.white,
|
|
||||||
fontSize: 11.sp,
|
|
||||||
),
|
|
||||||
SizedBox(height: 3.w),
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@ -120,6 +120,14 @@ class _UserLevelPageState extends State<UserLevelPage> {
|
|||||||
color: Colors.white24,
|
color: Colors.white24,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Container(
|
||||||
|
width: getLevelExperience(185.w),
|
||||||
|
height: 6.w,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
color: SocialChatTheme.primaryLight,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 5.w),
|
SizedBox(height: 5.w),
|
||||||
@ -150,18 +158,6 @@ class _UserLevelPageState extends State<UserLevelPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
text(
|
|
||||||
SCAppLocalizations.of(context)!.medalAndAvatarFrameRewards,
|
|
||||||
fontSize: 20.sp,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
textColor: SocialChatTheme.primaryLight,
|
|
||||||
),
|
|
||||||
text(
|
|
||||||
SCAppLocalizations.of(context)!.higherLevelFancierAvatarFrame,
|
|
||||||
fontSize: 12.sp,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
textColor: Colors.white,
|
|
||||||
),
|
|
||||||
Image.asset("sc_images/level/sc_icon_user_level_center_bg_1.png"),
|
Image.asset("sc_images/level/sc_icon_user_level_center_bg_1.png"),
|
||||||
text(
|
text(
|
||||||
SCAppLocalizations.of(context)!.howToUpgrade,
|
SCAppLocalizations.of(context)!.howToUpgrade,
|
||||||
@ -171,12 +167,13 @@ class _UserLevelPageState extends State<UserLevelPage> {
|
|||||||
),
|
),
|
||||||
SizedBox(height: 3),
|
SizedBox(height: 3),
|
||||||
text(
|
text(
|
||||||
SCAppLocalizations.of(
|
SCAppLocalizations.of(context)!.charmLevelUpgradeAnswer,
|
||||||
context,
|
|
||||||
)!.spendCoinsToGainExperiencePoints,
|
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
|
maxLines: 2,
|
||||||
|
maxWidth: 320.w,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
SizedBox(height: 25.w),
|
SizedBox(height: 25.w),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
@ -209,6 +206,9 @@ class _UserLevelPageState extends State<UserLevelPage> {
|
|||||||
int thatExp = SCStringUtils.convertToInteger(res?.thatExperience ?? "0");
|
int thatExp = SCStringUtils.convertToInteger(res?.thatExperience ?? "0");
|
||||||
int nexExp = SCStringUtils.convertToInteger(res?.nextExperience ?? "0");
|
int nexExp = SCStringUtils.convertToInteger(res?.nextExperience ?? "0");
|
||||||
int levelExp = thatExp + nexExp;
|
int levelExp = thatExp + nexExp;
|
||||||
|
if (levelExp <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
return (thatExp / levelExp) * width;
|
return (thatExp / levelExp) * width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -120,6 +120,14 @@ class _WealthLevelPageState extends State<WealthLevelPage> {
|
|||||||
color: Colors.white24,
|
color: Colors.white24,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Container(
|
||||||
|
width: getLevelExperience(185.w),
|
||||||
|
height: 6.w,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
color: SocialChatTheme.primaryLight,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 5.w),
|
SizedBox(height: 5.w),
|
||||||
@ -150,18 +158,6 @@ class _WealthLevelPageState extends State<WealthLevelPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
text(
|
|
||||||
SCAppLocalizations.of(context)!.medalAndAvatarFrameRewards,
|
|
||||||
fontSize: 20.sp,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
textColor: SocialChatTheme.primaryLight,
|
|
||||||
),
|
|
||||||
text(
|
|
||||||
SCAppLocalizations.of(context)!.higherLevelFancierAvatarFrame,
|
|
||||||
fontSize: 12.sp,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
textColor: Colors.white,
|
|
||||||
),
|
|
||||||
Image.asset("sc_images/level/sc_icon_user_wealth_center_bg_1.png"),
|
Image.asset("sc_images/level/sc_icon_user_wealth_center_bg_1.png"),
|
||||||
text(
|
text(
|
||||||
SCAppLocalizations.of(context)!.howToUpgrade,
|
SCAppLocalizations.of(context)!.howToUpgrade,
|
||||||
@ -171,12 +167,13 @@ class _WealthLevelPageState extends State<WealthLevelPage> {
|
|||||||
),
|
),
|
||||||
SizedBox(height: 3),
|
SizedBox(height: 3),
|
||||||
text(
|
text(
|
||||||
SCAppLocalizations.of(
|
SCAppLocalizations.of(context)!.richLevelUpgradeAnswer,
|
||||||
context,
|
|
||||||
)!.spendCoinsToGainExperiencePoints,
|
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
|
maxLines: 2,
|
||||||
|
maxWidth: 320.w,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
SizedBox(height: 25.w),
|
SizedBox(height: 25.w),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
@ -209,6 +206,9 @@ class _WealthLevelPageState extends State<WealthLevelPage> {
|
|||||||
int thatExp = SCStringUtils.convertToInteger(res?.thatExperience ?? "0");
|
int thatExp = SCStringUtils.convertToInteger(res?.thatExperience ?? "0");
|
||||||
int nexExp = SCStringUtils.convertToInteger(res?.nextExperience ?? "0");
|
int nexExp = SCStringUtils.convertToInteger(res?.nextExperience ?? "0");
|
||||||
int levelExp = thatExp + nexExp;
|
int levelExp = thatExp + nexExp;
|
||||||
|
if (levelExp <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
return (thatExp / levelExp) * width;
|
return (thatExp / levelExp) * width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,6 @@ import 'package:yumi/services/audio/rtm_manager.dart';
|
|||||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
import 'package:yumi/services/auth/user_profile_manager.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/badge/sc_user_badge_strip.dart';
|
import 'package:yumi/ui_kit/widgets/badge/sc_user_badge_strip.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/props/sc_data_card_resource_view.dart';
|
|
||||||
|
|
||||||
import '../../../app/constants/sc_screen.dart';
|
import '../../../app/constants/sc_screen.dart';
|
||||||
import '../../../shared/business_logic/models/res/sc_user_counter_res.dart';
|
import '../../../shared/business_logic/models/res/sc_user_counter_res.dart';
|
||||||
@ -580,31 +579,9 @@ class _PersonDetailPageState extends State<PersonDetailPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildProfileDataCardBackground(PropsResources? dataCard) {
|
Widget _buildProfileDataCardBackground(PropsResources? _) {
|
||||||
if (dataCard == null) {
|
|
||||||
return const ColoredBox(color: _profileBg);
|
return const ColoredBox(color: _profileBg);
|
||||||
}
|
}
|
||||||
return Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
|
||||||
SCDataCardResourceView(resource: dataCard, fit: BoxFit.cover),
|
|
||||||
DecoratedBox(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
|
||||||
colors: [
|
|
||||||
Colors.black.withValues(alpha: 0.26),
|
|
||||||
_profileBg.withValues(alpha: 0.54),
|
|
||||||
_profileBg.withValues(alpha: 0.78),
|
|
||||||
],
|
|
||||||
stops: const [0, 0.45, 1],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
PropsResources? _activeDataCard(SocialChatUserProfile? profile) {
|
PropsResources? _activeDataCard(SocialChatUserProfile? profile) {
|
||||||
final now = DateTime.now().millisecondsSinceEpoch;
|
final now = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
|||||||
@ -151,7 +151,7 @@ class _ProfileWallOfHonorsPageState extends State<ProfileWallOfHonorsPage> {
|
|||||||
),
|
),
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: 3,
|
crossAxisCount: 3,
|
||||||
mainAxisExtent: 132.w,
|
mainAxisExtent: 96.w,
|
||||||
mainAxisSpacing: 14.w,
|
mainAxisSpacing: 14.w,
|
||||||
crossAxisSpacing: 12.w,
|
crossAxisSpacing: 12.w,
|
||||||
),
|
),
|
||||||
@ -161,11 +161,8 @@ class _ProfileWallOfHonorsPageState extends State<ProfileWallOfHonorsPage> {
|
|||||||
|
|
||||||
Widget _buildBadgeItem(WearBadge badge) {
|
Widget _buildBadgeItem(WearBadge badge) {
|
||||||
final url = _badgeUrl(badge);
|
final url = _badgeUrl(badge);
|
||||||
final name = (badge.badgeName ?? "").trim();
|
return Center(
|
||||||
return Column(
|
child: Container(
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 86.w,
|
width: 86.w,
|
||||||
height: 86.w,
|
height: 86.w,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
@ -184,16 +181,6 @@ class _ProfileWallOfHonorsPageState extends State<ProfileWallOfHonorsPage> {
|
|||||||
noDefaultImg: true,
|
noDefaultImg: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 8.w),
|
|
||||||
text(
|
|
||||||
name.isEmpty ? "--" : name,
|
|
||||||
fontSize: 12,
|
|
||||||
textColor: Colors.white,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
maxLines: 2,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
|||||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||||
import 'package:yumi/ui_kit/components/sc_debounce_widget.dart';
|
import 'package:yumi/ui_kit/components/sc_debounce_widget.dart';
|
||||||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||||
|
import 'package:yumi/ui_kit/widgets/props/sc_data_card_resource_view.dart';
|
||||||
|
|
||||||
class VipDetailPage extends StatefulWidget {
|
class VipDetailPage extends StatefulWidget {
|
||||||
const VipDetailPage({super.key});
|
const VipDetailPage({super.key});
|
||||||
@ -58,7 +59,6 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
final status = results[1] as SCVipStatusRes;
|
final status = results[1] as SCVipStatusRes;
|
||||||
final resolvedStatus = _resolveStatus(status, home.state);
|
final resolvedStatus = _resolveStatus(status, home.state);
|
||||||
final selectedLevel = _resolveInitialLevel(home, resolvedStatus);
|
final selectedLevel = _resolveInitialLevel(home, resolvedStatus);
|
||||||
_logLoadedVipData(home, resolvedStatus, selectedLevel);
|
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -141,11 +141,6 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
_selectedLevel = level;
|
_selectedLevel = level;
|
||||||
_preview = null;
|
_preview = null;
|
||||||
});
|
});
|
||||||
_logSelectedLevelResources(
|
|
||||||
level,
|
|
||||||
config: _levelConfigFor(level),
|
|
||||||
status: _status,
|
|
||||||
);
|
|
||||||
_loadPreviewForLevel(level);
|
_loadPreviewForLevel(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,6 +196,7 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
await _loadVipData(showLoading: false);
|
await _loadVipData(showLoading: false);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
debugPrint('VIP purchase failed: $error');
|
||||||
} finally {
|
} finally {
|
||||||
SCLoadingManager.hide();
|
SCLoadingManager.hide();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@ -214,56 +210,6 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
return 'vip-buy-$userId-${DateTime.now().millisecondsSinceEpoch}';
|
return 'vip-buy-$userId-${DateTime.now().millisecondsSinceEpoch}';
|
||||||
}
|
}
|
||||||
|
|
||||||
void _logLoadedVipData(
|
|
||||||
SCVipHomeRes home,
|
|
||||||
SCVipStatusRes? status,
|
|
||||||
int selectedLevel,
|
|
||||||
) {
|
|
||||||
if (home.configured != true) {}
|
|
||||||
if (home.levels.isEmpty) {}
|
|
||||||
_logSelectedLevelResources(
|
|
||||||
selectedLevel,
|
|
||||||
config: _levelConfigFromHome(home, selectedLevel),
|
|
||||||
status: status,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _logSelectedLevelResources(
|
|
||||||
int level, {
|
|
||||||
required SCVipLevelConfigRes? config,
|
|
||||||
required SCVipStatusRes? status,
|
|
||||||
}) {
|
|
||||||
final state = status?.levelInt == level ? status : null;
|
|
||||||
if (config == null && state == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final badge = _preferResource(config?.badge, state?.badge);
|
|
||||||
final avatarFrame = _preferResource(
|
|
||||||
config?.avatarFrame,
|
|
||||||
state?.avatarFrame,
|
|
||||||
);
|
|
||||||
final entryEffect = _preferResource(
|
|
||||||
config?.entryEffect,
|
|
||||||
state?.entryEffect,
|
|
||||||
);
|
|
||||||
final chatBubble = _preferResource(config?.chatBubble, state?.chatBubble);
|
|
||||||
final floatPicture = _preferResource(
|
|
||||||
config?.floatPicture,
|
|
||||||
state?.floatPicture,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _resourceLogValue(SCVipResourceRes? resource) {
|
|
||||||
if (!_hasResource(resource)) {
|
|
||||||
return 'null';
|
|
||||||
}
|
|
||||||
return '{id:${resource?.resourceId},name:${resource?.name},'
|
|
||||||
'hasPreviewUrl:${resource?.hasPreviewUrl},'
|
|
||||||
'hasSourceUrl:${resource?.hasSourceUrl},'
|
|
||||||
'previewUrl:${resource?.previewUrl},'
|
|
||||||
'sourceUrl:${resource?.sourceResourceUrl}}';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@ -524,6 +470,8 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
|
|
||||||
Widget _buildLevelCard() {
|
Widget _buildLevelCard() {
|
||||||
final assetLevel = _assetLevel;
|
final assetLevel = _assetLevel;
|
||||||
|
final backgroundCard = _selectedBackgroundCard;
|
||||||
|
final effectImage = _selectedEffectImage ?? _selectedLongBadge;
|
||||||
return Center(
|
return Center(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 351.w,
|
width: 351.w,
|
||||||
@ -532,6 +480,16 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8.w),
|
||||||
|
child: _buildVipCardBackground(
|
||||||
|
resource: backgroundCard,
|
||||||
|
assetLevel: assetLevel,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned.fill(
|
||||||
|
child: IgnorePointer(
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(8.w),
|
borderRadius: BorderRadius.circular(8.w),
|
||||||
@ -539,11 +497,6 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
color: const Color(0xFFEBCB76),
|
color: const Color(0xFFEBCB76),
|
||||||
width: 1.w,
|
width: 1.w,
|
||||||
),
|
),
|
||||||
image: DecorationImage(
|
|
||||||
image: AssetImage(
|
|
||||||
'sc_images/vip/sc_vip_card_bg_$assetLevel.png',
|
|
||||||
),
|
|
||||||
fit: BoxFit.fill,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -575,8 +528,8 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
PositionedDirectional(
|
PositionedDirectional(
|
||||||
end: 24.w,
|
end: 24.w,
|
||||||
top: -36.w,
|
top: -36.w,
|
||||||
child: _buildResourceImage(
|
child: _buildDynamicResourceVisual(
|
||||||
resource: _selectedBadge,
|
resource: effectImage,
|
||||||
width: 135.w,
|
width: 135.w,
|
||||||
height: 135.w,
|
height: 135.w,
|
||||||
fallbackIcon: Icons.workspace_premium_outlined,
|
fallbackIcon: Icons.workspace_premium_outlined,
|
||||||
@ -588,6 +541,27 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildVipCardBackground({
|
||||||
|
required SCVipResourceRes? resource,
|
||||||
|
required int assetLevel,
|
||||||
|
}) {
|
||||||
|
final fallback = Image.asset(
|
||||||
|
'sc_images/vip/sc_vip_card_bg_$assetLevel.png',
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
width: double.infinity,
|
||||||
|
height: double.infinity,
|
||||||
|
);
|
||||||
|
if (!_hasResourceUrl(resource)) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
return SCDataCardResourceView(
|
||||||
|
sourceUrl: resource?.sourceResourceUrl,
|
||||||
|
coverUrl: resource?.previewUrl,
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
fallback: fallback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildLevelDivider() {
|
Widget _buildLevelDivider() {
|
||||||
final ornamentLayout = _ornamentLayoutForLevel(_assetLevel);
|
final ornamentLayout = _ornamentLayoutForLevel(_assetLevel);
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
@ -713,9 +687,14 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
SCVipResourceRes? get _selectedBadge => _selectedResource(
|
SCVipResourceRes? get _selectedLongBadge => _selectedResource(
|
||||||
fromConfig: (config) => config.badge,
|
fromConfig: (config) => config.longBadge,
|
||||||
fromState: (status) => status.badge,
|
fromState: (status) => status.longBadge,
|
||||||
|
);
|
||||||
|
|
||||||
|
SCVipResourceRes? get _selectedShortBadge => _selectedResource(
|
||||||
|
fromConfig: (config) => config.shortBadge,
|
||||||
|
fromState: (status) => status.shortBadge,
|
||||||
);
|
);
|
||||||
|
|
||||||
SCVipResourceRes? get _selectedAvatarFrame => _selectedResource(
|
SCVipResourceRes? get _selectedAvatarFrame => _selectedResource(
|
||||||
@ -738,6 +717,16 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
fromState: (status) => status.floatPicture,
|
fromState: (status) => status.floatPicture,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
SCVipResourceRes? get _selectedBackgroundCard => _selectedResource(
|
||||||
|
fromConfig: (config) => config.backgroundCard,
|
||||||
|
fromState: (status) => status.backgroundCard,
|
||||||
|
);
|
||||||
|
|
||||||
|
SCVipResourceRes? get _selectedEffectImage => _selectedResource(
|
||||||
|
fromConfig: (config) => config.effectImage,
|
||||||
|
fromState: (status) => status.effectImage,
|
||||||
|
);
|
||||||
|
|
||||||
SCVipResourceRes? _selectedResource({
|
SCVipResourceRes? _selectedResource({
|
||||||
required SCVipResourceRes? Function(SCVipLevelConfigRes config) fromConfig,
|
required SCVipResourceRes? Function(SCVipLevelConfigRes config) fromConfig,
|
||||||
required SCVipResourceRes? Function(SCVipStatusRes status) fromState,
|
required SCVipResourceRes? Function(SCVipStatusRes status) fromState,
|
||||||
@ -768,8 +757,14 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
final items = <_VipFeatureData>[];
|
final items = <_VipFeatureData>[];
|
||||||
_addResourceItem(
|
_addResourceItem(
|
||||||
items,
|
items,
|
||||||
resource: _selectedBadge,
|
resource: _selectedLongBadge,
|
||||||
fallbackTitle: 'VIP Badge',
|
fallbackTitle: 'Long Badge',
|
||||||
|
fallbackIcon: Icons.workspace_premium_outlined,
|
||||||
|
);
|
||||||
|
_addResourceItem(
|
||||||
|
items,
|
||||||
|
resource: _selectedShortBadge,
|
||||||
|
fallbackTitle: 'Short Badge',
|
||||||
fallbackIcon: Icons.workspace_premium_outlined,
|
fallbackIcon: Icons.workspace_premium_outlined,
|
||||||
);
|
);
|
||||||
_addResourceItem(
|
_addResourceItem(
|
||||||
@ -797,12 +792,19 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
fallbackIcon: Icons.airline_seat_recline_extra,
|
fallbackIcon: Icons.airline_seat_recline_extra,
|
||||||
wide: true,
|
wide: true,
|
||||||
);
|
);
|
||||||
|
_addResourceItem(
|
||||||
|
items,
|
||||||
|
resource: _selectedBackgroundCard,
|
||||||
|
fallbackTitle: 'Profile Card',
|
||||||
|
fallbackIcon: Icons.badge_outlined,
|
||||||
|
wide: true,
|
||||||
|
);
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<_VipFeatureData> get _vipPrivilegeItems {
|
List<_VipFeatureData> get _vipPrivilegeItems {
|
||||||
// The VIP home API currently returns resource perks only:
|
// The VIP home API currently returns resource perks only:
|
||||||
// badge/avatarFrame/entryEffect/chatBubble/floatPicture.
|
// badges/avatarFrame/entryEffect/chatBubble/floatPicture/backgroundCard.
|
||||||
// Do not render demo-only functional privileges without backend data.
|
// Do not render demo-only functional privileges without backend data.
|
||||||
return const <_VipFeatureData>[];
|
return const <_VipFeatureData>[];
|
||||||
}
|
}
|
||||||
@ -844,6 +846,40 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
return fallbackTitle;
|
return fallbackTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildDynamicResourceVisual({
|
||||||
|
required SCVipResourceRes? resource,
|
||||||
|
required double width,
|
||||||
|
required double height,
|
||||||
|
required IconData fallbackIcon,
|
||||||
|
}) {
|
||||||
|
final sourceUrl = resource?.sourceResourceUrl?.trim();
|
||||||
|
final previewUrl = resource?.previewUrl?.trim();
|
||||||
|
final hasSource = sourceUrl != null && sourceUrl.isNotEmpty;
|
||||||
|
final hasPreview = previewUrl != null && previewUrl.isNotEmpty;
|
||||||
|
if (hasSource || hasPreview) {
|
||||||
|
return SCDataCardResourceView(
|
||||||
|
sourceUrl: sourceUrl,
|
||||||
|
coverUrl: previewUrl,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
fallback: _buildResourceImage(
|
||||||
|
resource: resource,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
fallbackIcon: fallbackIcon,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _buildResourceImage(
|
||||||
|
resource: resource,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
fallbackIcon: fallbackIcon,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildResourceImage({
|
Widget _buildResourceImage({
|
||||||
required SCVipResourceRes? resource,
|
required SCVipResourceRes? resource,
|
||||||
required double width,
|
required double width,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:yumi/app_localizations.dart';
|
import 'package:yumi/app_localizations.dart';
|
||||||
@ -291,6 +292,20 @@ class _PendingRoomStartupSeatAction {
|
|||||||
final String? inviterId;
|
final String? inviterId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _PersistedVoiceRoomCleanupRequest {
|
||||||
|
const _PersistedVoiceRoomCleanupRequest({
|
||||||
|
required this.staleRoomId,
|
||||||
|
required this.targetRoomId,
|
||||||
|
required this.restoreOnlineHeartbeatWhenIdle,
|
||||||
|
this.clearMarkerOnly = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String staleRoomId;
|
||||||
|
final String targetRoomId;
|
||||||
|
final bool restoreOnlineHeartbeatWhenIdle;
|
||||||
|
final bool clearMarkerOnly;
|
||||||
|
}
|
||||||
|
|
||||||
class RealTimeCommunicationManager extends ChangeNotifier {
|
class RealTimeCommunicationManager extends ChangeNotifier {
|
||||||
static const String _roomRocketRewardDialogTag = 'showRoomRocketRewardDialog';
|
static const String _roomRocketRewardDialogTag = 'showRoomRocketRewardDialog';
|
||||||
static const String _roomRocketPagLaunchDialogTag =
|
static const String _roomRocketPagLaunchDialogTag =
|
||||||
@ -304,6 +319,12 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
Duration(seconds: 5),
|
Duration(seconds: 5),
|
||||||
Duration(seconds: 10),
|
Duration(seconds: 10),
|
||||||
];
|
];
|
||||||
|
static const List<Duration> _roomRocketEntryReadyFallbackDelays = [
|
||||||
|
Duration(milliseconds: 300),
|
||||||
|
Duration(milliseconds: 900),
|
||||||
|
Duration(milliseconds: 1800),
|
||||||
|
Duration(milliseconds: 3200),
|
||||||
|
];
|
||||||
static const int _roomPasswordNotTrueErrorCode = 9005;
|
static const int _roomPasswordNotTrueErrorCode = 9005;
|
||||||
|
|
||||||
static const Duration _micListPollingInterval = Duration(seconds: 2);
|
static const Duration _micListPollingInterval = Duration(seconds: 2);
|
||||||
@ -343,8 +364,10 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
Timer? _onlineUsersPollingTimer;
|
Timer? _onlineUsersPollingTimer;
|
||||||
Timer? _roomEntryEffectTimer;
|
Timer? _roomEntryEffectTimer;
|
||||||
Timer? _roomRocketLaunchAnimationTimer;
|
Timer? _roomRocketLaunchAnimationTimer;
|
||||||
|
ValueNotifier<String?>? _roomRocketLaunchTopAvatarNotifier;
|
||||||
Timer? _roomRocketGiftRefreshTimer;
|
Timer? _roomRocketGiftRefreshTimer;
|
||||||
final List<Timer> _roomRocketPostLaunchRefreshTimers = [];
|
final List<Timer> _roomRocketPostLaunchRefreshTimers = [];
|
||||||
|
final List<Timer> _roomRocketEntryReadyFallbackTimers = [];
|
||||||
Timer? _roomRedPacketPresenceTimer;
|
Timer? _roomRedPacketPresenceTimer;
|
||||||
Future<void>? _rtcEnginePrewarmTask;
|
Future<void>? _rtcEnginePrewarmTask;
|
||||||
Future<void>? _pendingRoomSwitchRtcLeaveTask;
|
Future<void>? _pendingRoomSwitchRtcLeaveTask;
|
||||||
@ -490,8 +513,11 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
_roomEntryEffectTimer = null;
|
_roomEntryEffectTimer = null;
|
||||||
_roomRocketLaunchAnimationTimer?.cancel();
|
_roomRocketLaunchAnimationTimer?.cancel();
|
||||||
_roomRocketLaunchAnimationTimer = null;
|
_roomRocketLaunchAnimationTimer = null;
|
||||||
|
SmartDialog.dismiss(tag: _roomRocketPagLaunchDialogTag);
|
||||||
|
_releaseRoomRocketLaunchTopAvatarNotifier();
|
||||||
_roomRocketGiftRefreshTimer?.cancel();
|
_roomRocketGiftRefreshTimer?.cancel();
|
||||||
_roomRocketGiftRefreshTimer = null;
|
_roomRocketGiftRefreshTimer = null;
|
||||||
|
_cancelRoomRocketEntryReadyFallbacks();
|
||||||
_cancelRoomRocketPostLaunchStatusRefresh();
|
_cancelRoomRocketPostLaunchStatusRefresh();
|
||||||
}
|
}
|
||||||
_roomVisualEffectsEnabled = enabled;
|
_roomVisualEffectsEnabled = enabled;
|
||||||
@ -500,6 +526,12 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
|
|
||||||
void setVoiceRoomRouteVisible(bool visible) {
|
void setVoiceRoomRouteVisible(bool visible) {
|
||||||
_voiceRoomRouteVisible = visible;
|
_voiceRoomRouteVisible = visible;
|
||||||
|
if (visible) {
|
||||||
|
final roomId = (currenRoom?.roomProfile?.roomProfile?.id ?? "").trim();
|
||||||
|
if (roomId.isNotEmpty) {
|
||||||
|
_scheduleRoomRocketEntryReadyFallback(roomId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setRoomStartupLoading() {
|
void _setRoomStartupLoading() {
|
||||||
@ -1780,10 +1812,11 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
if (!context.mounted) {
|
if (!context.mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await cleanupPersistedVoiceRoomSessionBeforeEntry(targetRoomId: roomId);
|
final persistedRoomCleanupRequest =
|
||||||
if (!context.mounted) {
|
_preparePersistedVoiceRoomCleanupBeforeEntry(
|
||||||
return;
|
targetRoomId: roomId,
|
||||||
}
|
restoreOnlineHeartbeatWhenIdle: false,
|
||||||
|
);
|
||||||
if (roomId == currenRoom?.roomProfile?.roomProfile?.id) {
|
if (roomId == currenRoom?.roomProfile?.roomProfile?.id) {
|
||||||
if (_currentRoomIsEntryPreview ||
|
if (_currentRoomIsEntryPreview ||
|
||||||
_roomStartupStatus == RoomStartupStatus.loading) {
|
_roomStartupStatus == RoomStartupStatus.loading) {
|
||||||
@ -1792,6 +1825,9 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
_startVoiceRoomForegroundService();
|
_startVoiceRoomForegroundService();
|
||||||
VoiceRoomRoute.openVoiceRoom(context);
|
VoiceRoomRoute.openVoiceRoom(context);
|
||||||
|
_startPersistedVoiceRoomCleanupInBackground(
|
||||||
|
persistedRoomCleanupRequest,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1830,6 +1866,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
_refreshRoomRedPacketListAfterEntry(roomId: roomId);
|
_refreshRoomRedPacketListAfterEntry(roomId: roomId);
|
||||||
_startVoiceRoomForegroundService();
|
_startVoiceRoomForegroundService();
|
||||||
VoiceRoomRoute.openVoiceRoom(context);
|
VoiceRoomRoute.openVoiceRoom(context);
|
||||||
|
_startPersistedVoiceRoomCleanupInBackground(persistedRoomCleanupRequest);
|
||||||
if (shouldRejoinRoomRtc) {
|
if (shouldRejoinRoomRtc) {
|
||||||
unawaited(_rejoinCurrentRoomRtcForSameRoomReuse(roomId));
|
unawaited(_rejoinCurrentRoomRtcForSameRoomReuse(roomId));
|
||||||
}
|
}
|
||||||
@ -1865,6 +1902,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
_startVoiceRoomForegroundService();
|
_startVoiceRoomForegroundService();
|
||||||
VoiceRoomRoute.openVoiceRoom(context);
|
VoiceRoomRoute.openVoiceRoom(context);
|
||||||
|
_startPersistedVoiceRoomCleanupInBackground(persistedRoomCleanupRequest);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
final entryRequestSerial = ++_roomEntryRequestSerial;
|
final entryRequestSerial = ++_roomEntryRequestSerial;
|
||||||
if (preEnteredRoom != null) {
|
if (preEnteredRoom != null) {
|
||||||
@ -2487,14 +2525,70 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
|
|
||||||
Future<void> cleanupPersistedVoiceRoomSessionBeforeEntry({
|
Future<void> cleanupPersistedVoiceRoomSessionBeforeEntry({
|
||||||
String? targetRoomId,
|
String? targetRoomId,
|
||||||
|
bool restoreOnlineHeartbeatWhenIdle = true,
|
||||||
}) {
|
}) {
|
||||||
|
final cleanupRequest = _preparePersistedVoiceRoomCleanupBeforeEntry(
|
||||||
|
targetRoomId: targetRoomId,
|
||||||
|
restoreOnlineHeartbeatWhenIdle: restoreOnlineHeartbeatWhenIdle,
|
||||||
|
);
|
||||||
|
return _runPersistedVoiceRoomCleanup(cleanupRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
_PersistedVoiceRoomCleanupRequest?
|
||||||
|
_preparePersistedVoiceRoomCleanupBeforeEntry({
|
||||||
|
String? targetRoomId,
|
||||||
|
bool restoreOnlineHeartbeatWhenIdle = true,
|
||||||
|
}) {
|
||||||
|
final staleRoomId = DataPersistence.getLastTimeRoomId().trim();
|
||||||
|
if (staleRoomId.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final currentRoomId =
|
||||||
|
(currenRoom?.roomProfile?.roomProfile?.id ?? "").trim();
|
||||||
|
if (currentRoomId.isNotEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final currentUserId =
|
||||||
|
(AccountStorage().getCurrentUser()?.userProfile?.id ?? "").trim();
|
||||||
|
if (currentUserId.isEmpty || AccountStorage().getToken().trim().isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final normalizedTargetRoomId = (targetRoomId ?? "").trim();
|
||||||
|
if (normalizedTargetRoomId.isNotEmpty &&
|
||||||
|
normalizedTargetRoomId == staleRoomId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final staleRoomUserId = DataPersistence.getLastTimeRoomUserId().trim();
|
||||||
|
return _PersistedVoiceRoomCleanupRequest(
|
||||||
|
staleRoomId: staleRoomId,
|
||||||
|
targetRoomId: normalizedTargetRoomId,
|
||||||
|
restoreOnlineHeartbeatWhenIdle: restoreOnlineHeartbeatWhenIdle,
|
||||||
|
clearMarkerOnly:
|
||||||
|
staleRoomUserId.isNotEmpty && staleRoomUserId != currentUserId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startPersistedVoiceRoomCleanupInBackground(
|
||||||
|
_PersistedVoiceRoomCleanupRequest? cleanupRequest,
|
||||||
|
) {
|
||||||
|
unawaited(_runPersistedVoiceRoomCleanup(cleanupRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _runPersistedVoiceRoomCleanup(
|
||||||
|
_PersistedVoiceRoomCleanupRequest? cleanupRequest,
|
||||||
|
) {
|
||||||
|
if (cleanupRequest == null) {
|
||||||
|
return Future<void>.value();
|
||||||
|
}
|
||||||
final runningTask = _persistedRoomCleanupTask;
|
final runningTask = _persistedRoomCleanupTask;
|
||||||
if (runningTask != null) {
|
if (runningTask != null) {
|
||||||
return runningTask;
|
return runningTask;
|
||||||
}
|
}
|
||||||
final task = _cleanupPersistedVoiceRoomSessionBeforeEntry(
|
final task = _cleanupPersistedVoiceRoomSessionBeforeEntry(cleanupRequest);
|
||||||
targetRoomId: targetRoomId,
|
|
||||||
);
|
|
||||||
_persistedRoomCleanupTask = task;
|
_persistedRoomCleanupTask = task;
|
||||||
return task.whenComplete(() {
|
return task.whenComplete(() {
|
||||||
if (identical(_persistedRoomCleanupTask, task)) {
|
if (identical(_persistedRoomCleanupTask, task)) {
|
||||||
@ -2503,28 +2597,11 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _cleanupPersistedVoiceRoomSessionBeforeEntry({
|
Future<void> _cleanupPersistedVoiceRoomSessionBeforeEntry(
|
||||||
String? targetRoomId,
|
_PersistedVoiceRoomCleanupRequest cleanupRequest,
|
||||||
}) async {
|
) async {
|
||||||
final staleRoomId = DataPersistence.getLastTimeRoomId().trim();
|
final staleRoomId = cleanupRequest.staleRoomId;
|
||||||
if (staleRoomId.isEmpty) {
|
if (cleanupRequest.clearMarkerOnly) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final currentRoomId =
|
|
||||||
(currenRoom?.roomProfile?.roomProfile?.id ?? "").trim();
|
|
||||||
if (currentRoomId.isNotEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final currentUserId =
|
|
||||||
(AccountStorage().getCurrentUser()?.userProfile?.id ?? "").trim();
|
|
||||||
if (currentUserId.isEmpty || AccountStorage().getToken().trim().isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final staleRoomUserId = DataPersistence.getLastTimeRoomUserId().trim();
|
|
||||||
if (staleRoomUserId.isNotEmpty && staleRoomUserId != currentUserId) {
|
|
||||||
await _clearPersistedRoomMarkerIfMatches(staleRoomId);
|
await _clearPersistedRoomMarkerIfMatches(staleRoomId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2545,7 +2622,11 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
if (didClean) {
|
if (didClean) {
|
||||||
await _clearPersistedRoomMarkerIfMatches(staleRoomId);
|
await _clearPersistedRoomMarkerIfMatches(staleRoomId);
|
||||||
if ((targetRoomId ?? "").trim() != staleRoomId) {
|
final currentRoomId =
|
||||||
|
(currenRoom?.roomProfile?.roomProfile?.id ?? "").trim();
|
||||||
|
if (cleanupRequest.restoreOnlineHeartbeatWhenIdle &&
|
||||||
|
cleanupRequest.targetRoomId != staleRoomId &&
|
||||||
|
currentRoomId.isEmpty) {
|
||||||
await SCHeartbeatUtils.scheduleHeartbeat(
|
await SCHeartbeatUtils.scheduleHeartbeat(
|
||||||
SCHeartbeatStatus.ONLINE.name,
|
SCHeartbeatStatus.ONLINE.name,
|
||||||
false,
|
false,
|
||||||
@ -2594,6 +2675,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
setRoomVisualEffectsEnabled(true);
|
setRoomVisualEffectsEnabled(true);
|
||||||
if (shouldOpenRoomPage) {
|
if (shouldOpenRoomPage) {
|
||||||
VoiceRoomRoute.openVoiceRoom(context!);
|
VoiceRoomRoute.openVoiceRoom(context!);
|
||||||
|
setVoiceRoomRouteVisible(true);
|
||||||
}
|
}
|
||||||
unawaited(
|
unawaited(
|
||||||
_bootstrapEnteredVoiceRoomSession(
|
_bootstrapEnteredVoiceRoomSession(
|
||||||
@ -2694,6 +2776,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
roomId: roomId,
|
roomId: roomId,
|
||||||
);
|
);
|
||||||
_loadRoomSecondaryData();
|
_loadRoomSecondaryData();
|
||||||
|
_scheduleRoomRocketEntryReadyFallback(roomId);
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
if (!_isActiveRoomStartup(entryRequestSerial, roomId)) {
|
if (!_isActiveRoomStartup(entryRequestSerial, roomId)) {
|
||||||
return;
|
return;
|
||||||
@ -2806,18 +2889,19 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
|
|
||||||
void _loadRoomSecondaryData() {
|
void _loadRoomSecondaryData() {
|
||||||
final roomId = currenRoom?.roomProfile?.roomProfile?.id ?? "";
|
final roomId = currenRoom?.roomProfile?.roomProfile?.id ?? "";
|
||||||
SCChatRoomRepository().rocketClaim(roomId).catchError((e) {
|
|
||||||
return true; // 错误已处理
|
|
||||||
});
|
|
||||||
SCChatRoomRepository()
|
SCChatRoomRepository()
|
||||||
.rocketStatus(roomId)
|
.rocketStatus(roomId)
|
||||||
.then((res) {
|
.then((res) {
|
||||||
roomRocketStatus = res;
|
roomRocketStatus = res;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
if (!res.isEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
_scheduleRoomRocketAssetPreload(roomId, res, afterRoomEntry: true);
|
_scheduleRoomRocketAssetPreload(roomId, res, afterRoomEntry: true);
|
||||||
|
_scheduleRoomRocketEntryReadyFallback(roomId);
|
||||||
|
_loadRoomRocketRewardPopups(roomId);
|
||||||
})
|
})
|
||||||
.catchError((e) {});
|
.catchError((e) {});
|
||||||
_loadRoomRocketRewardPopups(roomId);
|
|
||||||
_refreshRoomRedPacketListAfterEntry();
|
_refreshRoomRedPacketListAfterEntry();
|
||||||
|
|
||||||
fetchRoomTaskClaimableCount();
|
fetchRoomTaskClaimableCount();
|
||||||
@ -2873,6 +2957,9 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
void updateRoomRocketConfigurationStatus(SCRoomRocketStatusRes res) {
|
void updateRoomRocketConfigurationStatus(SCRoomRocketStatusRes res) {
|
||||||
roomRocketStatus = res;
|
roomRocketStatus = res;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
if (!res.isEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
_scheduleRoomRocketAssetPreload(
|
_scheduleRoomRocketAssetPreload(
|
||||||
(res.roomId ?? currenRoom?.roomProfile?.roomProfile?.id ?? "").trim(),
|
(res.roomId ?? currenRoom?.roomProfile?.roomProfile?.id ?? "").trim(),
|
||||||
res,
|
res,
|
||||||
@ -2893,6 +2980,9 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
roomRocketStatus = res;
|
roomRocketStatus = res;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
if (!res.isEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
_scheduleRoomRocketAssetPreload(resolvedRoomId, res);
|
_scheduleRoomRocketAssetPreload(resolvedRoomId, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2905,6 +2995,9 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
if (resolvedRoomId.isEmpty) {
|
if (resolvedRoomId.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!status.isEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (afterRoomEntry) {
|
if (afterRoomEntry) {
|
||||||
RoomRocketAssetPreloader.preloadAfterRoomEntry(
|
RoomRocketAssetPreloader.preloadAfterRoomEntry(
|
||||||
roomId: resolvedRoomId,
|
roomId: resolvedRoomId,
|
||||||
@ -2937,6 +3030,42 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _scheduleRoomRocketEntryReadyFallback(String roomId) {
|
||||||
|
final normalizedRoomId = roomId.trim();
|
||||||
|
if (normalizedRoomId.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_notifyRoomRocketEntryReadyIfCurrent(normalizedRoomId);
|
||||||
|
for (final delay in _roomRocketEntryReadyFallbackDelays) {
|
||||||
|
final timer = Timer(delay, () {
|
||||||
|
_roomRocketEntryReadyFallbackTimers.removeWhere(
|
||||||
|
(item) => !item.isActive,
|
||||||
|
);
|
||||||
|
_notifyRoomRocketEntryReadyIfCurrent(normalizedRoomId);
|
||||||
|
});
|
||||||
|
_roomRocketEntryReadyFallbackTimers.add(timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _notifyRoomRocketEntryReadyIfCurrent(String roomId) {
|
||||||
|
final normalizedRoomId = roomId.trim();
|
||||||
|
final currentRoomId =
|
||||||
|
currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? "";
|
||||||
|
if (normalizedRoomId.isEmpty ||
|
||||||
|
currentRoomId != normalizedRoomId ||
|
||||||
|
!_voiceRoomRouteVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rtmProvider?.handleVoiceRoomReadyForRocketRewards(normalizedRoomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _cancelRoomRocketEntryReadyFallbacks() {
|
||||||
|
for (final timer in _roomRocketEntryReadyFallbackTimers) {
|
||||||
|
timer.cancel();
|
||||||
|
}
|
||||||
|
_roomRocketEntryReadyFallbackTimers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
void scheduleRoomRocketPostLaunchStatusRefresh({String? roomId}) {
|
void scheduleRoomRocketPostLaunchStatusRefresh({String? roomId}) {
|
||||||
final resolvedRoomId =
|
final resolvedRoomId =
|
||||||
(roomId ?? currenRoom?.roomProfile?.roomProfile?.id ?? "").trim();
|
(roomId ?? currenRoom?.roomProfile?.roomProfile?.id ?? "").trim();
|
||||||
@ -2973,6 +3102,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_roomRocketLaunchAnimationTimer?.cancel();
|
_roomRocketLaunchAnimationTimer?.cancel();
|
||||||
|
_releaseRoomRocketLaunchTopAvatarNotifier();
|
||||||
|
|
||||||
if (shouldShowRoomVisualEffects && isVoiceRoomRouteVisible) {
|
if (shouldShowRoomVisualEffects && isVoiceRoomRouteVisible) {
|
||||||
final animationUrl =
|
final animationUrl =
|
||||||
@ -2990,6 +3120,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
_roomRocketLaunchAnimationTimer = Timer(launch.displayDuration, () {
|
_roomRocketLaunchAnimationTimer = Timer(launch.displayDuration, () {
|
||||||
_roomRocketLaunchAnimationTimer = null;
|
_roomRocketLaunchAnimationTimer = null;
|
||||||
SmartDialog.dismiss(tag: _roomRocketPagLaunchDialogTag);
|
SmartDialog.dismiss(tag: _roomRocketPagLaunchDialogTag);
|
||||||
|
_releaseRoomRocketLaunchTopAvatarNotifier();
|
||||||
unawaited(refreshRoomRocketStatus(roomId: launch.roomId));
|
unawaited(refreshRoomRocketStatus(roomId: launch.roomId));
|
||||||
scheduleRoomRocketPostLaunchStatusRefresh(roomId: launch.roomId);
|
scheduleRoomRocketPostLaunchStatusRefresh(roomId: launch.roomId);
|
||||||
});
|
});
|
||||||
@ -3015,6 +3146,11 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
required RoomRocketLaunchBroadcastMessage launch,
|
required RoomRocketLaunchBroadcastMessage launch,
|
||||||
}) {
|
}) {
|
||||||
if (RoomRocketPagEffectOverlay.isPag(animationUrl)) {
|
if (RoomRocketPagEffectOverlay.isPag(animationUrl)) {
|
||||||
|
final topAvatarNotifier = ValueNotifier<String?>(
|
||||||
|
_roomRocketTop1AvatarUrl(launch.safeLevel),
|
||||||
|
);
|
||||||
|
_roomRocketLaunchTopAvatarNotifier = topAvatarNotifier;
|
||||||
|
unawaited(_loadRoomRocketTop1Avatar(launch, topAvatarNotifier));
|
||||||
SmartDialog.dismiss(tag: _roomRocketPagLaunchDialogTag);
|
SmartDialog.dismiss(tag: _roomRocketPagLaunchDialogTag);
|
||||||
SmartDialog.show(
|
SmartDialog.show(
|
||||||
tag: _roomRocketPagLaunchDialogTag,
|
tag: _roomRocketPagLaunchDialogTag,
|
||||||
@ -3026,11 +3162,12 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
builder:
|
builder:
|
||||||
(_) => RoomRocketPagEffectOverlay(
|
(_) => RoomRocketPagEffectOverlay(
|
||||||
resource: animationUrl,
|
resource: animationUrl,
|
||||||
topAvatarUrl: _roomRocketTop1AvatarUrl(),
|
topAvatarUrlListenable: topAvatarNotifier,
|
||||||
onCompleted: () {
|
onCompleted: () {
|
||||||
SmartDialog.dismiss(tag: _roomRocketPagLaunchDialogTag);
|
SmartDialog.dismiss(tag: _roomRocketPagLaunchDialogTag);
|
||||||
_roomRocketLaunchAnimationTimer?.cancel();
|
_roomRocketLaunchAnimationTimer?.cancel();
|
||||||
_roomRocketLaunchAnimationTimer = null;
|
_roomRocketLaunchAnimationTimer = null;
|
||||||
|
_releaseRoomRocketLaunchTopAvatarNotifier(topAvatarNotifier);
|
||||||
unawaited(refreshRoomRocketStatus(roomId: launch.roomId));
|
unawaited(refreshRoomRocketStatus(roomId: launch.roomId));
|
||||||
scheduleRoomRocketPostLaunchStatusRefresh(
|
scheduleRoomRocketPostLaunchStatusRefresh(
|
||||||
roomId: launch.roomId,
|
roomId: launch.roomId,
|
||||||
@ -3047,8 +3184,21 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String? _roomRocketTop1AvatarUrl() {
|
String? _roomRocketTop1AvatarUrl(int level) {
|
||||||
final kings = roomRocketStatus?.rocketKings ?? const [];
|
final kings =
|
||||||
|
roomRocketStatus?.rocketKingsForLevel(level) ??
|
||||||
|
const <SCRoomRocketKingUserRes>[];
|
||||||
|
return _roomRocketTop1AvatarUrlFromUsers(kings);
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _roomRocketTop1AvatarUrlFromKing(SCRoomRocketKingRes king) {
|
||||||
|
final source = king.podium.isNotEmpty ? king.podium : king.displayRecords;
|
||||||
|
return _roomRocketTop1AvatarUrlFromUsers(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _roomRocketTop1AvatarUrlFromUsers(
|
||||||
|
List<SCRoomRocketKingUserRes> kings,
|
||||||
|
) {
|
||||||
if (kings.isEmpty) {
|
if (kings.isEmpty) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -3065,6 +3215,90 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _loadRoomRocketTop1Avatar(
|
||||||
|
RoomRocketLaunchBroadcastMessage launch,
|
||||||
|
ValueNotifier<String?> notifier,
|
||||||
|
) async {
|
||||||
|
final roomId = launch.roomId.trim();
|
||||||
|
if (roomId.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
_roomRocketDebug(
|
||||||
|
'request launch top1 avatar roomId=$roomId '
|
||||||
|
'level=${launch.safeLevel} roundNo=${launch.roundNo}',
|
||||||
|
);
|
||||||
|
var king = await SCChatRoomRepository().roomRocketKing(
|
||||||
|
roomId,
|
||||||
|
level: launch.safeLevel,
|
||||||
|
roundNo: launch.roundNo,
|
||||||
|
cursor: 1,
|
||||||
|
limit: 3,
|
||||||
|
);
|
||||||
|
var avatar = _roomRocketTop1AvatarUrlFromKing(king);
|
||||||
|
if (avatar == null && launch.roundNo > 0) {
|
||||||
|
_roomRocketDebug(
|
||||||
|
'launch top1 avatar retry without roundNo '
|
||||||
|
'level=${launch.safeLevel} roundNo=${launch.roundNo}',
|
||||||
|
);
|
||||||
|
king = await SCChatRoomRepository().roomRocketKing(
|
||||||
|
roomId,
|
||||||
|
level: launch.safeLevel,
|
||||||
|
cursor: 1,
|
||||||
|
limit: 3,
|
||||||
|
);
|
||||||
|
avatar = _roomRocketTop1AvatarUrlFromKing(king);
|
||||||
|
}
|
||||||
|
if (_roomRocketLaunchTopAvatarNotifier != notifier) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_roomRocketDebug(
|
||||||
|
'launch top1 avatar loaded level=${launch.safeLevel} '
|
||||||
|
'roundNo=${launch.roundNo} avatar=${_shortRoomRocketDebugUrl(avatar)} '
|
||||||
|
'podium=${king.podium.length} records=${king.records.length}',
|
||||||
|
);
|
||||||
|
if (avatar != null && avatar != notifier.value) {
|
||||||
|
notifier.value = avatar;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
_roomRocketDebug(
|
||||||
|
'launch top1 avatar load failed level=${launch.safeLevel} '
|
||||||
|
'roundNo=${launch.roundNo}: $error',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _releaseRoomRocketLaunchTopAvatarNotifier([
|
||||||
|
ValueNotifier<String?>? expected,
|
||||||
|
]) {
|
||||||
|
final notifier = _roomRocketLaunchTopAvatarNotifier;
|
||||||
|
if (notifier == null || (expected != null && notifier != expected)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_roomRocketLaunchTopAvatarNotifier = null;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
notifier.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _roomRocketDebug(String message) {
|
||||||
|
if (!kDebugMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debugPrint('[RoomRocket] $message');
|
||||||
|
}
|
||||||
|
|
||||||
|
String _shortRoomRocketDebugUrl(String? url) {
|
||||||
|
final value = url?.trim() ?? '';
|
||||||
|
if (value.isEmpty) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
if (value.length <= 96) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return '${value.substring(0, 48)}...${value.substring(value.length - 24)}';
|
||||||
|
}
|
||||||
|
|
||||||
RoomRocketRewardUserProfile? roomRocketRewardUserProfileForRecord(
|
RoomRocketRewardUserProfile? roomRocketRewardUserProfileForRecord(
|
||||||
SCRoomRocketRewardRecordRes record,
|
SCRoomRocketRewardRecordRes record,
|
||||||
) {
|
) {
|
||||||
@ -3367,9 +3601,6 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
final exitRtmProvider = rtmProvider;
|
final exitRtmProvider = rtmProvider;
|
||||||
final navigationContext = context;
|
final navigationContext = context;
|
||||||
try {
|
try {
|
||||||
exitRtmProvider?.msgAllListener = null;
|
|
||||||
exitRtmProvider?.msgChatListener = null;
|
|
||||||
exitRtmProvider?.msgGiftListener = null;
|
|
||||||
SCGiftVapSvgaManager().stopPlayback();
|
SCGiftVapSvgaManager().stopPlayback();
|
||||||
setRoomVisualEffectsEnabled(false);
|
setRoomVisualEffectsEnabled(false);
|
||||||
SCHeartbeatUtils.cancelTimer();
|
SCHeartbeatUtils.cancelTimer();
|
||||||
@ -3405,9 +3636,6 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
final isPreviewOnlyExit = _currentRoomIsEntryPreview;
|
final isPreviewOnlyExit = _currentRoomIsEntryPreview;
|
||||||
final exitRtmProvider = rtmProvider;
|
final exitRtmProvider = rtmProvider;
|
||||||
try {
|
try {
|
||||||
exitRtmProvider?.msgAllListener = null;
|
|
||||||
exitRtmProvider?.msgChatListener = null;
|
|
||||||
exitRtmProvider?.msgGiftListener = null;
|
|
||||||
SCGiftVapSvgaManager().stopPlayback();
|
SCGiftVapSvgaManager().stopPlayback();
|
||||||
setRoomVisualEffectsEnabled(false);
|
setRoomVisualEffectsEnabled(false);
|
||||||
SCHeartbeatUtils.cancelTimer();
|
SCHeartbeatUtils.cancelTimer();
|
||||||
@ -3613,6 +3841,8 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
_roomEntryEffectTimer = null;
|
_roomEntryEffectTimer = null;
|
||||||
_roomRocketLaunchAnimationTimer?.cancel();
|
_roomRocketLaunchAnimationTimer?.cancel();
|
||||||
_roomRocketLaunchAnimationTimer = null;
|
_roomRocketLaunchAnimationTimer = null;
|
||||||
|
SmartDialog.dismiss(tag: _roomRocketPagLaunchDialogTag);
|
||||||
|
_releaseRoomRocketLaunchTopAvatarNotifier();
|
||||||
_roomRocketGiftRefreshTimer?.cancel();
|
_roomRocketGiftRefreshTimer?.cancel();
|
||||||
_roomRocketGiftRefreshTimer = null;
|
_roomRocketGiftRefreshTimer = null;
|
||||||
_cancelRoomRocketPostLaunchStatusRefresh();
|
_cancelRoomRocketPostLaunchStatusRefresh();
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -37,6 +37,8 @@ class SCRoomRocketStatusRes {
|
|||||||
bool? hasClaimed,
|
bool? hasClaimed,
|
||||||
num? myContribution,
|
num? myContribution,
|
||||||
num? myContributionRate,
|
num? myContributionRate,
|
||||||
|
bool? configured,
|
||||||
|
bool? enabled,
|
||||||
int? roundNo,
|
int? roundNo,
|
||||||
bool? displayRound,
|
bool? displayRound,
|
||||||
int? currentLevel,
|
int? currentLevel,
|
||||||
@ -69,6 +71,8 @@ class SCRoomRocketStatusRes {
|
|||||||
_hasClaimed = hasClaimed;
|
_hasClaimed = hasClaimed;
|
||||||
_myContribution = myContribution;
|
_myContribution = myContribution;
|
||||||
_myContributionRate = myContributionRate;
|
_myContributionRate = myContributionRate;
|
||||||
|
_configured = configured;
|
||||||
|
_enabled = enabled;
|
||||||
_roundNo = roundNo;
|
_roundNo = roundNo;
|
||||||
_displayRound = displayRound;
|
_displayRound = displayRound;
|
||||||
_currentLevel = currentLevel;
|
_currentLevel = currentLevel;
|
||||||
@ -87,6 +91,15 @@ class SCRoomRocketStatusRes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SCRoomRocketStatusRes.fromJson(dynamic json) {
|
SCRoomRocketStatusRes.fromJson(dynamic json) {
|
||||||
|
_configured = _boolValue(json['configured']);
|
||||||
|
_enabled =
|
||||||
|
_boolValue(
|
||||||
|
json['enabled'] ??
|
||||||
|
json['enable'] ??
|
||||||
|
json['rocketEnabled'] ??
|
||||||
|
json['rocket_enabled'],
|
||||||
|
) ??
|
||||||
|
true;
|
||||||
_roomId = json['roomId'] ?? json['room_id'];
|
_roomId = json['roomId'] ?? json['room_id'];
|
||||||
_date = json['date'] ?? json['dayKey'] ?? json['day_key'];
|
_date = json['date'] ?? json['dayKey'] ?? json['day_key'];
|
||||||
_roundNo = _intValue(json['roundNo'] ?? json['round_no']);
|
_roundNo = _intValue(json['roundNo'] ?? json['round_no']);
|
||||||
@ -219,6 +232,8 @@ class SCRoomRocketStatusRes {
|
|||||||
bool? _hasClaimed;
|
bool? _hasClaimed;
|
||||||
num? _myContribution;
|
num? _myContribution;
|
||||||
num? _myContributionRate;
|
num? _myContributionRate;
|
||||||
|
bool? _configured;
|
||||||
|
bool? _enabled;
|
||||||
int? _roundNo;
|
int? _roundNo;
|
||||||
bool? _displayRound;
|
bool? _displayRound;
|
||||||
int? _currentLevel;
|
int? _currentLevel;
|
||||||
@ -267,6 +282,17 @@ class SCRoomRocketStatusRes {
|
|||||||
|
|
||||||
num? get myContributionRate => _myContributionRate;
|
num? get myContributionRate => _myContributionRate;
|
||||||
|
|
||||||
|
bool? get configured => _configured;
|
||||||
|
|
||||||
|
bool? get enabled => _enabled;
|
||||||
|
|
||||||
|
bool get isEnabled {
|
||||||
|
final normalizedStatus = (_status ?? '').trim().toUpperCase();
|
||||||
|
return _configured != false &&
|
||||||
|
_enabled != false &&
|
||||||
|
normalizedStatus != 'DISABLED';
|
||||||
|
}
|
||||||
|
|
||||||
int? get roundNo => _roundNo;
|
int? get roundNo => _roundNo;
|
||||||
|
|
||||||
bool? get displayRound => _displayRound;
|
bool? get displayRound => _displayRound;
|
||||||
@ -371,6 +397,8 @@ class SCRoomRocketStatusRes {
|
|||||||
map['hasClaimed'] = _hasClaimed;
|
map['hasClaimed'] = _hasClaimed;
|
||||||
map['myContribution'] = _myContribution;
|
map['myContribution'] = _myContribution;
|
||||||
map['myContributionRate'] = _myContributionRate;
|
map['myContributionRate'] = _myContributionRate;
|
||||||
|
map['configured'] = _configured;
|
||||||
|
map['enabled'] = _enabled;
|
||||||
map['roundNo'] = _roundNo;
|
map['roundNo'] = _roundNo;
|
||||||
map['displayRound'] = _displayRound;
|
map['displayRound'] = _displayRound;
|
||||||
map['currentLevel'] = _currentLevel;
|
map['currentLevel'] = _currentLevel;
|
||||||
|
|||||||
@ -110,10 +110,14 @@ class SCVipStatusRes {
|
|||||||
String? startAt,
|
String? startAt,
|
||||||
String? expireAt,
|
String? expireAt,
|
||||||
SCVipResourceRes? badge,
|
SCVipResourceRes? badge,
|
||||||
|
SCVipResourceRes? longBadge,
|
||||||
|
SCVipResourceRes? shortBadge,
|
||||||
SCVipResourceRes? avatarFrame,
|
SCVipResourceRes? avatarFrame,
|
||||||
SCVipResourceRes? entryEffect,
|
SCVipResourceRes? entryEffect,
|
||||||
SCVipResourceRes? chatBubble,
|
SCVipResourceRes? chatBubble,
|
||||||
SCVipResourceRes? floatPicture,
|
SCVipResourceRes? floatPicture,
|
||||||
|
SCVipResourceRes? backgroundCard,
|
||||||
|
SCVipResourceRes? effectImage,
|
||||||
}) {
|
}) {
|
||||||
_userId = userId;
|
_userId = userId;
|
||||||
_sysOrigin = sysOrigin;
|
_sysOrigin = sysOrigin;
|
||||||
@ -125,10 +129,14 @@ class SCVipStatusRes {
|
|||||||
_startAt = startAt;
|
_startAt = startAt;
|
||||||
_expireAt = expireAt;
|
_expireAt = expireAt;
|
||||||
_badge = badge;
|
_badge = badge;
|
||||||
|
_longBadge = longBadge;
|
||||||
|
_shortBadge = shortBadge;
|
||||||
_avatarFrame = avatarFrame;
|
_avatarFrame = avatarFrame;
|
||||||
_entryEffect = entryEffect;
|
_entryEffect = entryEffect;
|
||||||
_chatBubble = chatBubble;
|
_chatBubble = chatBubble;
|
||||||
_floatPicture = floatPicture;
|
_floatPicture = floatPicture;
|
||||||
|
_backgroundCard = backgroundCard;
|
||||||
|
_effectImage = effectImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
SCVipStatusRes.fromJson(dynamic json) {
|
SCVipStatusRes.fromJson(dynamic json) {
|
||||||
@ -144,10 +152,14 @@ class SCVipStatusRes {
|
|||||||
_startAt = _stringValue(map['startAt']);
|
_startAt = _stringValue(map['startAt']);
|
||||||
_expireAt = _stringValue(map['expireAt']);
|
_expireAt = _stringValue(map['expireAt']);
|
||||||
_badge = _resourceValue(map['badge']);
|
_badge = _resourceValue(map['badge']);
|
||||||
|
_longBadge = _resourceValue(map['longBadge']) ?? _badge;
|
||||||
|
_shortBadge = _resourceValue(map['shortBadge']);
|
||||||
_avatarFrame = _resourceValue(map['avatarFrame']);
|
_avatarFrame = _resourceValue(map['avatarFrame']);
|
||||||
_entryEffect = _resourceValue(map['entryEffect']);
|
_entryEffect = _resourceValue(map['entryEffect']);
|
||||||
_chatBubble = _resourceValue(map['chatBubble']);
|
_chatBubble = _resourceValue(map['chatBubble']);
|
||||||
_floatPicture = _resourceValue(map['floatPicture']);
|
_floatPicture = _resourceValue(map['floatPicture']);
|
||||||
|
_backgroundCard = _resourceValue(map['backgroundCard']);
|
||||||
|
_effectImage = _resourceValue(map['effectImage']);
|
||||||
}
|
}
|
||||||
|
|
||||||
String? _userId;
|
String? _userId;
|
||||||
@ -160,10 +172,14 @@ class SCVipStatusRes {
|
|||||||
String? _startAt;
|
String? _startAt;
|
||||||
String? _expireAt;
|
String? _expireAt;
|
||||||
SCVipResourceRes? _badge;
|
SCVipResourceRes? _badge;
|
||||||
|
SCVipResourceRes? _longBadge;
|
||||||
|
SCVipResourceRes? _shortBadge;
|
||||||
SCVipResourceRes? _avatarFrame;
|
SCVipResourceRes? _avatarFrame;
|
||||||
SCVipResourceRes? _entryEffect;
|
SCVipResourceRes? _entryEffect;
|
||||||
SCVipResourceRes? _chatBubble;
|
SCVipResourceRes? _chatBubble;
|
||||||
SCVipResourceRes? _floatPicture;
|
SCVipResourceRes? _floatPicture;
|
||||||
|
SCVipResourceRes? _backgroundCard;
|
||||||
|
SCVipResourceRes? _effectImage;
|
||||||
|
|
||||||
String? get userId => _userId;
|
String? get userId => _userId;
|
||||||
|
|
||||||
@ -183,7 +199,11 @@ class SCVipStatusRes {
|
|||||||
|
|
||||||
String? get expireAt => _expireAt;
|
String? get expireAt => _expireAt;
|
||||||
|
|
||||||
SCVipResourceRes? get badge => _badge;
|
SCVipResourceRes? get badge => longBadge;
|
||||||
|
|
||||||
|
SCVipResourceRes? get longBadge => _longBadge ?? _badge;
|
||||||
|
|
||||||
|
SCVipResourceRes? get shortBadge => _shortBadge;
|
||||||
|
|
||||||
SCVipResourceRes? get avatarFrame => _avatarFrame;
|
SCVipResourceRes? get avatarFrame => _avatarFrame;
|
||||||
|
|
||||||
@ -193,6 +213,10 @@ class SCVipStatusRes {
|
|||||||
|
|
||||||
SCVipResourceRes? get floatPicture => _floatPicture;
|
SCVipResourceRes? get floatPicture => _floatPicture;
|
||||||
|
|
||||||
|
SCVipResourceRes? get backgroundCard => _backgroundCard;
|
||||||
|
|
||||||
|
SCVipResourceRes? get effectImage => _effectImage;
|
||||||
|
|
||||||
bool get isActive => _active == true && (_level ?? 0) > 0;
|
bool get isActive => _active == true && (_level ?? 0) > 0;
|
||||||
|
|
||||||
int get levelInt => (_level ?? 0).toInt();
|
int get levelInt => (_level ?? 0).toInt();
|
||||||
@ -216,11 +240,17 @@ class SCVipStatusRes {
|
|||||||
map['displayName'] = _displayName;
|
map['displayName'] = _displayName;
|
||||||
map['startAt'] = _startAt;
|
map['startAt'] = _startAt;
|
||||||
map['expireAt'] = _expireAt;
|
map['expireAt'] = _expireAt;
|
||||||
if (_badge != null) map['badge'] = _badge?.toJson();
|
if (badge != null) map['badge'] = badge?.toJson();
|
||||||
|
if (longBadge != null) map['longBadge'] = longBadge?.toJson();
|
||||||
|
if (_shortBadge != null) map['shortBadge'] = _shortBadge?.toJson();
|
||||||
if (_avatarFrame != null) map['avatarFrame'] = _avatarFrame?.toJson();
|
if (_avatarFrame != null) map['avatarFrame'] = _avatarFrame?.toJson();
|
||||||
if (_entryEffect != null) map['entryEffect'] = _entryEffect?.toJson();
|
if (_entryEffect != null) map['entryEffect'] = _entryEffect?.toJson();
|
||||||
if (_chatBubble != null) map['chatBubble'] = _chatBubble?.toJson();
|
if (_chatBubble != null) map['chatBubble'] = _chatBubble?.toJson();
|
||||||
if (_floatPicture != null) map['floatPicture'] = _floatPicture?.toJson();
|
if (_floatPicture != null) map['floatPicture'] = _floatPicture?.toJson();
|
||||||
|
if (_backgroundCard != null) {
|
||||||
|
map['backgroundCard'] = _backgroundCard?.toJson();
|
||||||
|
}
|
||||||
|
if (_effectImage != null) map['effectImage'] = _effectImage?.toJson();
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -234,10 +264,14 @@ class SCVipLevelConfigRes {
|
|||||||
num? durationDays,
|
num? durationDays,
|
||||||
num? priceGold,
|
num? priceGold,
|
||||||
SCVipResourceRes? badge,
|
SCVipResourceRes? badge,
|
||||||
|
SCVipResourceRes? longBadge,
|
||||||
|
SCVipResourceRes? shortBadge,
|
||||||
SCVipResourceRes? avatarFrame,
|
SCVipResourceRes? avatarFrame,
|
||||||
SCVipResourceRes? entryEffect,
|
SCVipResourceRes? entryEffect,
|
||||||
SCVipResourceRes? chatBubble,
|
SCVipResourceRes? chatBubble,
|
||||||
SCVipResourceRes? floatPicture,
|
SCVipResourceRes? floatPicture,
|
||||||
|
SCVipResourceRes? backgroundCard,
|
||||||
|
SCVipResourceRes? effectImage,
|
||||||
bool? canPurchase,
|
bool? canPurchase,
|
||||||
num? payableGold,
|
num? payableGold,
|
||||||
}) {
|
}) {
|
||||||
@ -248,10 +282,14 @@ class SCVipLevelConfigRes {
|
|||||||
_durationDays = durationDays;
|
_durationDays = durationDays;
|
||||||
_priceGold = priceGold;
|
_priceGold = priceGold;
|
||||||
_badge = badge;
|
_badge = badge;
|
||||||
|
_longBadge = longBadge;
|
||||||
|
_shortBadge = shortBadge;
|
||||||
_avatarFrame = avatarFrame;
|
_avatarFrame = avatarFrame;
|
||||||
_entryEffect = entryEffect;
|
_entryEffect = entryEffect;
|
||||||
_chatBubble = chatBubble;
|
_chatBubble = chatBubble;
|
||||||
_floatPicture = floatPicture;
|
_floatPicture = floatPicture;
|
||||||
|
_backgroundCard = backgroundCard;
|
||||||
|
_effectImage = effectImage;
|
||||||
_canPurchase = canPurchase;
|
_canPurchase = canPurchase;
|
||||||
_payableGold = payableGold;
|
_payableGold = payableGold;
|
||||||
}
|
}
|
||||||
@ -266,10 +304,14 @@ class SCVipLevelConfigRes {
|
|||||||
_durationDays = _numValue(map['durationDays']);
|
_durationDays = _numValue(map['durationDays']);
|
||||||
_priceGold = _numValue(map['priceGold']);
|
_priceGold = _numValue(map['priceGold']);
|
||||||
_badge = _resourceValue(map['badge']);
|
_badge = _resourceValue(map['badge']);
|
||||||
|
_longBadge = _resourceValue(map['longBadge']) ?? _badge;
|
||||||
|
_shortBadge = _resourceValue(map['shortBadge']);
|
||||||
_avatarFrame = _resourceValue(map['avatarFrame']);
|
_avatarFrame = _resourceValue(map['avatarFrame']);
|
||||||
_entryEffect = _resourceValue(map['entryEffect']);
|
_entryEffect = _resourceValue(map['entryEffect']);
|
||||||
_chatBubble = _resourceValue(map['chatBubble']);
|
_chatBubble = _resourceValue(map['chatBubble']);
|
||||||
_floatPicture = _resourceValue(map['floatPicture']);
|
_floatPicture = _resourceValue(map['floatPicture']);
|
||||||
|
_backgroundCard = _resourceValue(map['backgroundCard']);
|
||||||
|
_effectImage = _resourceValue(map['effectImage']);
|
||||||
_canPurchase = _boolValue(map['canPurchase']);
|
_canPurchase = _boolValue(map['canPurchase']);
|
||||||
_payableGold = _numValue(map['payableGold']);
|
_payableGold = _numValue(map['payableGold']);
|
||||||
}
|
}
|
||||||
@ -281,10 +323,14 @@ class SCVipLevelConfigRes {
|
|||||||
num? _durationDays;
|
num? _durationDays;
|
||||||
num? _priceGold;
|
num? _priceGold;
|
||||||
SCVipResourceRes? _badge;
|
SCVipResourceRes? _badge;
|
||||||
|
SCVipResourceRes? _longBadge;
|
||||||
|
SCVipResourceRes? _shortBadge;
|
||||||
SCVipResourceRes? _avatarFrame;
|
SCVipResourceRes? _avatarFrame;
|
||||||
SCVipResourceRes? _entryEffect;
|
SCVipResourceRes? _entryEffect;
|
||||||
SCVipResourceRes? _chatBubble;
|
SCVipResourceRes? _chatBubble;
|
||||||
SCVipResourceRes? _floatPicture;
|
SCVipResourceRes? _floatPicture;
|
||||||
|
SCVipResourceRes? _backgroundCard;
|
||||||
|
SCVipResourceRes? _effectImage;
|
||||||
bool? _canPurchase;
|
bool? _canPurchase;
|
||||||
num? _payableGold;
|
num? _payableGold;
|
||||||
|
|
||||||
@ -300,7 +346,11 @@ class SCVipLevelConfigRes {
|
|||||||
|
|
||||||
num? get priceGold => _priceGold;
|
num? get priceGold => _priceGold;
|
||||||
|
|
||||||
SCVipResourceRes? get badge => _badge;
|
SCVipResourceRes? get badge => longBadge;
|
||||||
|
|
||||||
|
SCVipResourceRes? get longBadge => _longBadge ?? _badge;
|
||||||
|
|
||||||
|
SCVipResourceRes? get shortBadge => _shortBadge;
|
||||||
|
|
||||||
SCVipResourceRes? get avatarFrame => _avatarFrame;
|
SCVipResourceRes? get avatarFrame => _avatarFrame;
|
||||||
|
|
||||||
@ -310,6 +360,10 @@ class SCVipLevelConfigRes {
|
|||||||
|
|
||||||
SCVipResourceRes? get floatPicture => _floatPicture;
|
SCVipResourceRes? get floatPicture => _floatPicture;
|
||||||
|
|
||||||
|
SCVipResourceRes? get backgroundCard => _backgroundCard;
|
||||||
|
|
||||||
|
SCVipResourceRes? get effectImage => _effectImage;
|
||||||
|
|
||||||
bool? get canPurchase => _canPurchase;
|
bool? get canPurchase => _canPurchase;
|
||||||
|
|
||||||
num? get payableGold => _payableGold;
|
num? get payableGold => _payableGold;
|
||||||
@ -332,11 +386,17 @@ class SCVipLevelConfigRes {
|
|||||||
map['enabled'] = _enabled;
|
map['enabled'] = _enabled;
|
||||||
map['durationDays'] = _durationDays;
|
map['durationDays'] = _durationDays;
|
||||||
map['priceGold'] = _priceGold;
|
map['priceGold'] = _priceGold;
|
||||||
if (_badge != null) map['badge'] = _badge?.toJson();
|
if (badge != null) map['badge'] = badge?.toJson();
|
||||||
|
if (longBadge != null) map['longBadge'] = longBadge?.toJson();
|
||||||
|
if (_shortBadge != null) map['shortBadge'] = _shortBadge?.toJson();
|
||||||
if (_avatarFrame != null) map['avatarFrame'] = _avatarFrame?.toJson();
|
if (_avatarFrame != null) map['avatarFrame'] = _avatarFrame?.toJson();
|
||||||
if (_entryEffect != null) map['entryEffect'] = _entryEffect?.toJson();
|
if (_entryEffect != null) map['entryEffect'] = _entryEffect?.toJson();
|
||||||
if (_chatBubble != null) map['chatBubble'] = _chatBubble?.toJson();
|
if (_chatBubble != null) map['chatBubble'] = _chatBubble?.toJson();
|
||||||
if (_floatPicture != null) map['floatPicture'] = _floatPicture?.toJson();
|
if (_floatPicture != null) map['floatPicture'] = _floatPicture?.toJson();
|
||||||
|
if (_backgroundCard != null) {
|
||||||
|
map['backgroundCard'] = _backgroundCard?.toJson();
|
||||||
|
}
|
||||||
|
if (_effectImage != null) map['effectImage'] = _effectImage?.toJson();
|
||||||
map['canPurchase'] = _canPurchase;
|
map['canPurchase'] = _canPurchase;
|
||||||
map['payableGold'] = _payableGold;
|
map['payableGold'] = _payableGold;
|
||||||
return map;
|
return map;
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
|
||||||
enum SCPropsType {
|
enum SCPropsType {
|
||||||
AVATAR_FRAME,
|
AVATAR_FRAME,
|
||||||
FLOAT_PICTURE,
|
FLOAT_PICTURE,
|
||||||
@ -8,6 +10,7 @@ enum SCPropsType {
|
|||||||
THEME,
|
THEME,
|
||||||
BADGE,
|
BADGE,
|
||||||
NOBLE_VIP,
|
NOBLE_VIP,
|
||||||
|
VIP_EFFECT_IMAGE,
|
||||||
FRAGMENTS,
|
FRAGMENTS,
|
||||||
RED_PACKET,
|
RED_PACKET,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,15 +57,18 @@ Widget head({
|
|||||||
final String headdressCoverUrl = headdressCover?.trim() ?? "";
|
final String headdressCoverUrl = headdressCover?.trim() ?? "";
|
||||||
final String headdressExtension =
|
final String headdressExtension =
|
||||||
SCPathUtils.getFileExtension(headdressUrl).toLowerCase();
|
SCPathUtils.getFileExtension(headdressUrl).toLowerCase();
|
||||||
|
final bool hasLocalVirtualHeaddress = _isLocalAssetsVirtualUrl(headdressUrl);
|
||||||
final bool hasPictureHeaddress =
|
final bool hasPictureHeaddress =
|
||||||
headdressUrl.isNotEmpty && SCPathUtils.fileTypeIsPic2(headdressUrl);
|
headdressUrl.isNotEmpty && SCPathUtils.fileTypeIsPic2(headdressUrl);
|
||||||
final bool hasAnimatedHeaddress =
|
final bool hasAnimatedHeaddress =
|
||||||
headdressExtension == ".svga" || headdressExtension == ".mp4";
|
headdressExtension == ".svga" || headdressExtension == ".mp4";
|
||||||
final bool shouldShowAnimatedHeaddress =
|
final bool shouldShowAnimatedHeaddress =
|
||||||
hasAnimatedHeaddress && SCGlobalConfig.allowsHighCostAnimations;
|
hasAnimatedHeaddress &&
|
||||||
|
SCGlobalConfig.allowsHighCostAnimations &&
|
||||||
|
!hasLocalVirtualHeaddress;
|
||||||
final bool shouldShowStaticHeaddressFallback =
|
final bool shouldShowStaticHeaddressFallback =
|
||||||
hasAnimatedHeaddress &&
|
hasAnimatedHeaddress &&
|
||||||
!SCGlobalConfig.allowsHighCostAnimations &&
|
(!SCGlobalConfig.allowsHighCostAnimations || hasLocalVirtualHeaddress) &&
|
||||||
headdressCoverUrl.isNotEmpty;
|
headdressCoverUrl.isNotEmpty;
|
||||||
final bool hasHeaddress =
|
final bool hasHeaddress =
|
||||||
headdressUrl.isNotEmpty || shouldShowStaticHeaddressFallback;
|
headdressUrl.isNotEmpty || shouldShowStaticHeaddressFallback;
|
||||||
@ -205,6 +208,11 @@ Widget head({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isLocalAssetsVirtualUrl(String value) {
|
||||||
|
final uri = Uri.tryParse(value.trim());
|
||||||
|
return uri?.host.toLowerCase() == "local.assets";
|
||||||
|
}
|
||||||
|
|
||||||
Widget netImage({
|
Widget netImage({
|
||||||
required String url,
|
required String url,
|
||||||
String? defaultImg,
|
String? defaultImg,
|
||||||
|
|||||||
@ -23,6 +23,8 @@ class SCDataCardResourceView extends StatelessWidget {
|
|||||||
this.fit = BoxFit.cover,
|
this.fit = BoxFit.cover,
|
||||||
this.loop = true,
|
this.loop = true,
|
||||||
this.fallback,
|
this.fallback,
|
||||||
|
this.showCoverFallback = true,
|
||||||
|
this.allowCoverAsResource = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
final PropsResources? resource;
|
final PropsResources? resource;
|
||||||
@ -33,6 +35,8 @@ class SCDataCardResourceView extends StatelessWidget {
|
|||||||
final BoxFit fit;
|
final BoxFit fit;
|
||||||
final bool loop;
|
final bool loop;
|
||||||
final Widget? fallback;
|
final Widget? fallback;
|
||||||
|
final bool showCoverFallback;
|
||||||
|
final bool allowCoverAsResource;
|
||||||
|
|
||||||
static bool isSupported(String? url) {
|
static bool isSupported(String? url) {
|
||||||
return _isSvga(url) ||
|
return _isSvga(url) ||
|
||||||
@ -50,15 +54,22 @@ class SCDataCardResourceView extends StatelessWidget {
|
|||||||
final resolvedHeight = _resolvedSize(height, constraints.maxHeight);
|
final resolvedHeight = _resolvedSize(height, constraints.maxHeight);
|
||||||
final cover = _firstNonBlank([coverUrl, resource?.cover]);
|
final cover = _firstNonBlank([coverUrl, resource?.cover]);
|
||||||
final source = _firstNonBlank([sourceUrl, resource?.sourceUrl]);
|
final source = _firstNonBlank([sourceUrl, resource?.sourceUrl]);
|
||||||
final resourceUrl = source.isNotEmpty ? source : cover;
|
final resourceUrl =
|
||||||
|
source.isNotEmpty || !allowCoverAsResource ? source : cover;
|
||||||
|
final emptyFallback = SizedBox(
|
||||||
|
width: resolvedWidth,
|
||||||
|
height: resolvedHeight,
|
||||||
|
);
|
||||||
final fallbackView =
|
final fallbackView =
|
||||||
fallback ??
|
fallback ??
|
||||||
_buildImage(
|
(showCoverFallback
|
||||||
|
? _buildImage(
|
||||||
cover,
|
cover,
|
||||||
width: resolvedWidth,
|
width: resolvedWidth,
|
||||||
height: resolvedHeight,
|
height: resolvedHeight,
|
||||||
fit: fit,
|
fit: fit,
|
||||||
);
|
)
|
||||||
|
: emptyFallback);
|
||||||
|
|
||||||
if (resourceUrl.isEmpty) {
|
if (resourceUrl.isEmpty) {
|
||||||
return fallbackView;
|
return fallbackView;
|
||||||
@ -330,10 +341,12 @@ class _LoopingDataCardVapVideo extends StatefulWidget {
|
|||||||
|
|
||||||
class _LoopingDataCardVapVideoState extends State<_LoopingDataCardVapVideo> {
|
class _LoopingDataCardVapVideoState extends State<_LoopingDataCardVapVideo> {
|
||||||
static const int _legacyRgbLeftAlphaRightMode = 3;
|
static const int _legacyRgbLeftAlphaRightMode = 3;
|
||||||
|
static const int _longRunningLoopCount = 999999;
|
||||||
|
|
||||||
VapController? _controller;
|
VapController? _controller;
|
||||||
int _playToken = 0;
|
int _playToken = 0;
|
||||||
bool _failed = false;
|
bool _failed = false;
|
||||||
|
bool _started = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant _LoopingDataCardVapVideo oldWidget) {
|
void didUpdateWidget(covariant _LoopingDataCardVapVideo oldWidget) {
|
||||||
@ -343,8 +356,11 @@ class _LoopingDataCardVapVideoState extends State<_LoopingDataCardVapVideo> {
|
|||||||
oldWidget.fit != widget.fit) {
|
oldWidget.fit != widget.fit) {
|
||||||
_playToken++;
|
_playToken++;
|
||||||
_stop();
|
_stop();
|
||||||
if (_failed) {
|
if (_failed || _started) {
|
||||||
setState(() => _failed = false);
|
setState(() {
|
||||||
|
_failed = false;
|
||||||
|
_started = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
_play();
|
_play();
|
||||||
}
|
}
|
||||||
@ -362,15 +378,32 @@ class _LoopingDataCardVapVideoState extends State<_LoopingDataCardVapVideo> {
|
|||||||
_controller = controller;
|
_controller = controller;
|
||||||
controller.setAnimListener(
|
controller.setAnimListener(
|
||||||
onFailed: (_, __, ___) => _markFailed(),
|
onFailed: (_, __, ___) => _markFailed(),
|
||||||
|
onVideoComplete: _handleVideoComplete,
|
||||||
onVideoStart: () {
|
onVideoStart: () {
|
||||||
if (mounted && _failed) {
|
if (mounted && (_failed || !_started)) {
|
||||||
setState(() => _failed = false);
|
setState(() {
|
||||||
|
_failed = false;
|
||||||
|
_started = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
_play();
|
_play();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleVideoComplete() {
|
||||||
|
if (!mounted || !widget.loop || _failed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unawaited(
|
||||||
|
Future<void>.delayed(Duration.zero, () {
|
||||||
|
if (mounted && widget.loop && !_failed) {
|
||||||
|
return _play();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _play() async {
|
Future<void> _play() async {
|
||||||
final controller = _controller;
|
final controller = _controller;
|
||||||
final url = widget.url.trim();
|
final url = widget.url.trim();
|
||||||
@ -390,7 +423,7 @@ class _LoopingDataCardVapVideoState extends State<_LoopingDataCardVapVideo> {
|
|||||||
await controller.setMute(true);
|
await controller.setMute(true);
|
||||||
await controller.enableVersion1(true);
|
await controller.enableVersion1(true);
|
||||||
await controller.setVideoMode(_legacyRgbLeftAlphaRightMode);
|
await controller.setVideoMode(_legacyRgbLeftAlphaRightMode);
|
||||||
await controller.setLoop(widget.loop ? -1 : 0);
|
await controller.setLoop(widget.loop ? _longRunningLoopCount : 0);
|
||||||
await controller.setScaleType(_scaleTypeForFit(widget.fit).key);
|
await controller.setScaleType(_scaleTypeForFit(widget.fit).key);
|
||||||
if (!_isCurrent(token, controller)) {
|
if (!_isCurrent(token, controller)) {
|
||||||
return;
|
return;
|
||||||
@ -451,7 +484,10 @@ class _LoopingDataCardVapVideoState extends State<_LoopingDataCardVapVideo> {
|
|||||||
if (!mounted || _failed) {
|
if (!mounted || _failed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(() => _failed = true);
|
setState(() {
|
||||||
|
_failed = true;
|
||||||
|
_started = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ScaleType _scaleTypeForFit(BoxFit fit) {
|
ScaleType _scaleTypeForFit(BoxFit fit) {
|
||||||
@ -482,7 +518,7 @@ class _LoopingDataCardVapVideoState extends State<_LoopingDataCardVapVideo> {
|
|||||||
if (!_failed)
|
if (!_failed)
|
||||||
VapView(
|
VapView(
|
||||||
scaleType: _scaleTypeForFit(widget.fit),
|
scaleType: _scaleTypeForFit(widget.fit),
|
||||||
repeat: widget.loop ? -1 : 0,
|
repeat: widget.loop ? _longRunningLoopCount : 0,
|
||||||
mute: true,
|
mute: true,
|
||||||
onViewCreated: _handleViewCreated,
|
onViewCreated: _handleViewCreated,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -209,6 +209,20 @@ class RoomRocketDialog extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
String debugRoomRocketTitleText({required int level, required int round}) {
|
||||||
|
return _roomRocketTitleText(level: level, round: round);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _roomRocketTitleText({required int level, required int round}) {
|
||||||
|
final normalizedLevel = level.clamp(1, 99).toInt();
|
||||||
|
final normalizedRound = round.clamp(1, 99).toInt();
|
||||||
|
if (normalizedRound <= 1) {
|
||||||
|
return 'Lv.$normalizedLevel Today';
|
||||||
|
}
|
||||||
|
return 'Lv.$normalizedLevel · Round $normalizedRound Today';
|
||||||
|
}
|
||||||
|
|
||||||
class RoomRocketRewardItem {
|
class RoomRocketRewardItem {
|
||||||
const RoomRocketRewardItem({
|
const RoomRocketRewardItem({
|
||||||
this.asset,
|
this.asset,
|
||||||
@ -449,7 +463,7 @@ class _RocketCanvas extends StatelessWidget {
|
|||||||
top: s(120),
|
top: s(120),
|
||||||
width: s(_designWidth),
|
width: s(_designWidth),
|
||||||
child: _GradientText(
|
child: _GradientText(
|
||||||
'Round $round Today',
|
_roomRocketTitleText(level: selectedLevelItem.level, round: round),
|
||||||
fontSize: s(16),
|
fontSize: s(16),
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
@ -463,31 +477,11 @@ class _RocketCanvas extends StatelessWidget {
|
|||||||
top: s(212),
|
top: s(212),
|
||||||
width: s(40),
|
width: s(40),
|
||||||
height: s(190),
|
height: s(190),
|
||||||
child: ScrollConfiguration(
|
child: _LevelRail(
|
||||||
behavior: const _NoGlowScrollBehavior(),
|
|
||||||
child: ListView.separated(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
primary: false,
|
|
||||||
physics: const BouncingScrollPhysics(),
|
|
||||||
itemCount: levels.length,
|
|
||||||
separatorBuilder: (_, __) => SizedBox(height: s(4)),
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final item = levels[index];
|
|
||||||
return GestureDetector(
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
onTap: item.enabled ? () => onLevelSelected?.call(index) : null,
|
|
||||||
child: SizedBox(
|
|
||||||
width: s(40),
|
|
||||||
height: s(52),
|
|
||||||
child: _LevelBadge(
|
|
||||||
scale: scale,
|
scale: scale,
|
||||||
item: item,
|
levels: levels,
|
||||||
selected: index == selectedLevel,
|
selectedLevel: selectedLevel,
|
||||||
),
|
onLevelSelected: onLevelSelected,
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1031,9 +1025,12 @@ class _LevelBadge extends StatelessWidget {
|
|||||||
item.label,
|
item.label,
|
||||||
fontSize: 10 * scale,
|
fontSize: 10 * scale,
|
||||||
color:
|
color:
|
||||||
item.enabled
|
selected
|
||||||
|
? const Color(0xFFA5FFE3)
|
||||||
|
: item.enabled
|
||||||
? Colors.white
|
? Colors.white
|
||||||
: Colors.white.withValues(alpha: 0.52),
|
: Colors.white.withValues(alpha: 0.52),
|
||||||
|
fontWeight: selected ? FontWeight.w700 : FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -1042,6 +1039,118 @@ class _LevelBadge extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _LevelRail extends StatefulWidget {
|
||||||
|
const _LevelRail({
|
||||||
|
required this.scale,
|
||||||
|
required this.levels,
|
||||||
|
required this.selectedLevel,
|
||||||
|
this.onLevelSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
final double scale;
|
||||||
|
final List<RoomRocketLevelItem> levels;
|
||||||
|
final int selectedLevel;
|
||||||
|
final ValueChanged<int>? onLevelSelected;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_LevelRail> createState() => _LevelRailState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LevelRailState extends State<_LevelRail> {
|
||||||
|
late final ScrollController _controller;
|
||||||
|
|
||||||
|
double get _itemHeight => 52 * widget.scale;
|
||||||
|
double get _separatorHeight => 4 * widget.scale;
|
||||||
|
double get _viewportHeight => 190 * widget.scale;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = ScrollController(
|
||||||
|
initialScrollOffset: _targetOffset(widget.selectedLevel),
|
||||||
|
);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => _scrollToSelected());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant _LevelRail oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (oldWidget.selectedLevel != widget.selectedLevel ||
|
||||||
|
oldWidget.levels.length != widget.levels.length ||
|
||||||
|
oldWidget.scale != widget.scale) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => _scrollToSelected());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
double _targetOffset(int selectedLevel) {
|
||||||
|
if (widget.levels.isEmpty) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
final index = selectedLevel.clamp(0, widget.levels.length - 1).toInt();
|
||||||
|
final totalHeight =
|
||||||
|
widget.levels.length * _itemHeight +
|
||||||
|
math.max(0, widget.levels.length - 1) * _separatorHeight;
|
||||||
|
final maxOffset = math.max(0, totalHeight - _viewportHeight).toDouble();
|
||||||
|
final centered =
|
||||||
|
index * (_itemHeight + _separatorHeight) -
|
||||||
|
(_viewportHeight - _itemHeight) / 2;
|
||||||
|
return centered.clamp(0, maxOffset).toDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _scrollToSelected() {
|
||||||
|
if (!mounted || !_controller.hasClients) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final target = _targetOffset(widget.selectedLevel);
|
||||||
|
if ((_controller.offset - target).abs() < 0.5) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_controller.animateTo(
|
||||||
|
target,
|
||||||
|
duration: const Duration(milliseconds: 220),
|
||||||
|
curve: Curves.easeOutCubic,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ScrollConfiguration(
|
||||||
|
behavior: const _NoGlowScrollBehavior(),
|
||||||
|
child: ListView.separated(
|
||||||
|
controller: _controller,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
primary: false,
|
||||||
|
physics: const BouncingScrollPhysics(),
|
||||||
|
itemCount: widget.levels.length,
|
||||||
|
separatorBuilder: (_, __) => SizedBox(height: _separatorHeight),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = widget.levels[index];
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap:
|
||||||
|
item.enabled ? () => widget.onLevelSelected?.call(index) : null,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 40 * widget.scale,
|
||||||
|
height: _itemHeight,
|
||||||
|
child: _LevelBadge(
|
||||||
|
scale: widget.scale,
|
||||||
|
item: item,
|
||||||
|
selected: index == widget.selectedLevel,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _CrewAvatar extends StatelessWidget {
|
class _CrewAvatar extends StatelessWidget {
|
||||||
const _CrewAvatar({
|
const _CrewAvatar({
|
||||||
required this.scale,
|
required this.scale,
|
||||||
@ -1399,28 +1508,45 @@ class _LevelBadgePainter extends CustomPainter {
|
|||||||
size,
|
size,
|
||||||
const Size(40, 52),
|
const Size(40, 52),
|
||||||
);
|
);
|
||||||
|
if (selected) {
|
||||||
canvas.drawPath(
|
canvas.drawPath(
|
||||||
path,
|
path,
|
||||||
Paint()
|
Paint()
|
||||||
..shader = const LinearGradient(
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = size.width / 7
|
||||||
|
..maskFilter = MaskFilter.blur(BlurStyle.normal, size.width / 12)
|
||||||
|
..color = const Color(0xFF56FFD0).withValues(alpha: 0.38),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
canvas.drawPath(
|
||||||
|
path,
|
||||||
|
Paint()
|
||||||
|
..shader = LinearGradient(
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter,
|
end: Alignment.bottomCenter,
|
||||||
colors: [Color(0xFF077C60), Color(0xFF08251E)],
|
colors:
|
||||||
|
selected
|
||||||
|
? const [Color(0xFF10DFA8), Color(0xFF07392E)]
|
||||||
|
: const [Color(0xFF077C60), Color(0xFF08251E)],
|
||||||
).createShader(Offset.zero & size),
|
).createShader(Offset.zero & size),
|
||||||
);
|
);
|
||||||
canvas.drawPath(
|
canvas.drawPath(
|
||||||
path,
|
path,
|
||||||
Paint()
|
Paint()
|
||||||
..style = PaintingStyle.stroke
|
..style = PaintingStyle.stroke
|
||||||
..strokeWidth = selected ? size.width / 22 : size.width / 40
|
..strokeWidth = selected ? size.width / 14 : size.width / 40
|
||||||
..shader = LinearGradient(
|
..shader = LinearGradient(
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter,
|
end: Alignment.bottomCenter,
|
||||||
colors: [
|
colors: [
|
||||||
enabled
|
selected
|
||||||
|
? Colors.white
|
||||||
|
: enabled
|
||||||
? const Color(0xFFB2FBCC)
|
? const Color(0xFFB2FBCC)
|
||||||
: const Color(0xFFB2FBCC).withValues(alpha: 0.42),
|
: const Color(0xFFB2FBCC).withValues(alpha: 0.42),
|
||||||
const Color(0xFFB2FBCC).withValues(alpha: 0),
|
selected
|
||||||
|
? const Color(0xFF56FFD0)
|
||||||
|
: const Color(0xFFB2FBCC).withValues(alpha: 0),
|
||||||
],
|
],
|
||||||
).createShader(Offset.zero & size),
|
).createShader(Offset.zero & size),
|
||||||
);
|
);
|
||||||
@ -1554,12 +1680,14 @@ class _RocketText extends StatelessWidget {
|
|||||||
required this.fontSize,
|
required this.fontSize,
|
||||||
required this.color,
|
required this.color,
|
||||||
this.textAlign = TextAlign.center,
|
this.textAlign = TextAlign.center,
|
||||||
|
this.fontWeight = FontWeight.w400,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String text;
|
final String text;
|
||||||
final double fontSize;
|
final double fontSize;
|
||||||
final Color color;
|
final Color color;
|
||||||
final TextAlign textAlign;
|
final TextAlign textAlign;
|
||||||
|
final FontWeight fontWeight;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -1572,6 +1700,7 @@ class _RocketText extends StatelessWidget {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: color,
|
color: color,
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
|
fontWeight: fontWeight,
|
||||||
height: 1,
|
height: 1,
|
||||||
decoration: TextDecoration.none,
|
decoration: TextDecoration.none,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -34,19 +34,24 @@ class RoomRocketFloatingEntry extends StatelessWidget {
|
|||||||
return Selector<RtcProvider, SCRoomRocketStatusRes?>(
|
return Selector<RtcProvider, SCRoomRocketStatusRes?>(
|
||||||
selector: (_, provider) => provider.roomRocketStatus,
|
selector: (_, provider) => provider.roomRocketStatus,
|
||||||
builder: (context, status, child) {
|
builder: (context, status, child) {
|
||||||
|
if (status == null || !status.isEnabled) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
final level = _currentEntryLevel(status);
|
final level = _currentEntryLevel(status);
|
||||||
|
if (level == null || level.rocketIconUrl.trim().isEmpty) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
final percent = _entryPercent(status);
|
final percent = _entryPercent(status);
|
||||||
final shouldShake =
|
final shouldShake =
|
||||||
(status?.shake ?? false) ||
|
(status.shake ?? false) || percent > level.shakeThresholdPercent;
|
||||||
(level != null && percent > level.shakeThresholdPercent);
|
|
||||||
return SCDebounceWidget(
|
return SCDebounceWidget(
|
||||||
onTap: () => _showRocketDialog(context, status),
|
onTap: () => _showRocketDialog(context, status),
|
||||||
child: _RoomRocketEntryButton(
|
child: _RoomRocketEntryButton(
|
||||||
iconUrl: level?.rocketIconUrl ?? '',
|
iconUrl: level.rocketIconUrl,
|
||||||
progressUrl: level?.progressBarUrl ?? '',
|
progressUrl: level.progressBarUrl,
|
||||||
progressPercent: percent,
|
progressPercent: percent,
|
||||||
shake: shouldShake,
|
shake: shouldShake,
|
||||||
loading: status == null,
|
loading: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -108,6 +113,55 @@ class RoomRocketFloatingEntry extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
int debugResolveRoomRocketSelectedLevelIndex({
|
||||||
|
required List<RoomRocketLevelItem>? levelItems,
|
||||||
|
required SCRoomRocketStatusRes? status,
|
||||||
|
required int selectedLevel,
|
||||||
|
required bool levelTouched,
|
||||||
|
}) {
|
||||||
|
return _resolveRoomRocketSelectedLevelIndex(
|
||||||
|
levelItems: levelItems,
|
||||||
|
status: status,
|
||||||
|
selectedLevel: selectedLevel,
|
||||||
|
levelTouched: levelTouched,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _resolveRoomRocketSelectedLevelIndex({
|
||||||
|
required List<RoomRocketLevelItem>? levelItems,
|
||||||
|
required SCRoomRocketStatusRes? status,
|
||||||
|
required int selectedLevel,
|
||||||
|
required bool levelTouched,
|
||||||
|
}) {
|
||||||
|
final items = levelItems ?? const <RoomRocketLevelItem>[];
|
||||||
|
final maxIndex = math.max(0, (items.isEmpty ? 6 : items.length) - 1);
|
||||||
|
if (levelTouched) {
|
||||||
|
return selectedLevel.clamp(0, maxIndex).toInt();
|
||||||
|
}
|
||||||
|
final currentLevel = _roomRocketLevelNumberFromStatus(status);
|
||||||
|
final matchedIndex = items.indexWhere((item) => item.level == currentLevel);
|
||||||
|
if (matchedIndex >= 0) {
|
||||||
|
return matchedIndex;
|
||||||
|
}
|
||||||
|
return (currentLevel - 1).clamp(0, maxIndex).toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
int _roomRocketLevelNumberFromStatus(SCRoomRocketStatusRes? status) {
|
||||||
|
return (status?.currentLevel ?? status?.level ?? 1).clamp(1, 99).toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
int _roomRocketLevelNumberForIndex(
|
||||||
|
List<RoomRocketLevelItem>? levelItems,
|
||||||
|
int index,
|
||||||
|
) {
|
||||||
|
final items = levelItems ?? const <RoomRocketLevelItem>[];
|
||||||
|
if (index >= 0 && index < items.length) {
|
||||||
|
return items[index].level.clamp(1, 99).toInt();
|
||||||
|
}
|
||||||
|
return (index + 1).clamp(1, 99).toInt();
|
||||||
|
}
|
||||||
|
|
||||||
class _RoomRocketEntryButton extends StatefulWidget {
|
class _RoomRocketEntryButton extends StatefulWidget {
|
||||||
const _RoomRocketEntryButton({
|
const _RoomRocketEntryButton({
|
||||||
required this.iconUrl,
|
required this.iconUrl,
|
||||||
@ -369,9 +423,16 @@ class _RoomRocketDialogLoaderState extends State<_RoomRocketDialogLoader> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
_status = widget.initialStatus;
|
_status = widget.initialStatus;
|
||||||
_selectedLevel = _levelIndexFromStatus(widget.initialStatus);
|
_selectedLevel = _levelIndexFromStatus(widget.initialStatus);
|
||||||
_cacheRewardGroupsFromStatus(widget.initialStatus, _selectedLevel + 1);
|
final initialLevelItems = RoomRocketApiMapper.levelsFromStatus(
|
||||||
|
widget.initialStatus,
|
||||||
|
);
|
||||||
|
final initialLevelNumber = _levelNumberForIndex(
|
||||||
|
initialLevelItems,
|
||||||
|
_selectedLevel,
|
||||||
|
);
|
||||||
|
_cacheRewardGroupsFromStatus(widget.initialStatus, initialLevelNumber);
|
||||||
unawaited(_load());
|
unawaited(_load());
|
||||||
unawaited(_loadRewardForLevel(_selectedLevel + 1));
|
unawaited(_loadRewardForLevel(initialLevelNumber));
|
||||||
unawaited(_loadConfigs());
|
unawaited(_loadConfigs());
|
||||||
unawaited(_loadRule());
|
unawaited(_loadRule());
|
||||||
}
|
}
|
||||||
@ -387,16 +448,29 @@ class _RoomRocketDialogLoaderState extends State<_RoomRocketDialogLoader> {
|
|||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!res.isEnabled) {
|
||||||
|
try {
|
||||||
|
Provider.of<RtcProvider>(
|
||||||
|
context,
|
||||||
|
listen: false,
|
||||||
|
).updateRoomRocketConfigurationStatus(res);
|
||||||
|
} catch (_) {}
|
||||||
|
widget.onClose?.call();
|
||||||
|
return;
|
||||||
|
}
|
||||||
_debugStatusAssets(res);
|
_debugStatusAssets(res);
|
||||||
|
final levelItems = RoomRocketApiMapper.levelsFromStatus(res);
|
||||||
|
late final int selectedLevelNumber;
|
||||||
setState(() {
|
setState(() {
|
||||||
_status = res;
|
_status = res;
|
||||||
if (!_levelTouched) {
|
if (!_levelTouched) {
|
||||||
_selectedLevel = _levelIndexFromStatus(res);
|
_selectedLevel = _levelIndexFromStatus(res);
|
||||||
}
|
}
|
||||||
_cacheRewardGroupsFromStatus(res, _selectedLevel + 1);
|
selectedLevelNumber = _levelNumberForIndex(levelItems, _selectedLevel);
|
||||||
|
_cacheRewardGroupsFromStatus(res, selectedLevelNumber);
|
||||||
});
|
});
|
||||||
unawaited(_loadCrewForLevel(_selectedLevel + 1));
|
unawaited(_loadCrewForLevel(selectedLevelNumber));
|
||||||
unawaited(_loadRewardForLevel(_selectedLevel + 1));
|
unawaited(_loadRewardForLevel(selectedLevelNumber));
|
||||||
try {
|
try {
|
||||||
Provider.of<RtcProvider>(
|
Provider.of<RtcProvider>(
|
||||||
context,
|
context,
|
||||||
@ -430,13 +504,29 @@ class _RoomRocketDialogLoaderState extends State<_RoomRocketDialogLoader> {
|
|||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!contents.enabled) {
|
||||||
|
setState(() {
|
||||||
|
_levelContents = contents;
|
||||||
|
_configRewardGroupsByLevel = {};
|
||||||
|
});
|
||||||
|
_rocketUiDebug('rocket level contents disabled');
|
||||||
|
return;
|
||||||
|
}
|
||||||
final groups = RoomRocketApiMapper.rewardGroupsByLevelFromLevelContents(
|
final groups = RoomRocketApiMapper.rewardGroupsByLevelFromLevelContents(
|
||||||
contents,
|
contents,
|
||||||
);
|
);
|
||||||
|
final levelItems = RoomRocketApiMapper.levelsFromLevelContents(contents);
|
||||||
|
late final int selectedLevelNumber;
|
||||||
setState(() {
|
setState(() {
|
||||||
_levelContents = contents;
|
_levelContents = contents;
|
||||||
_configRewardGroupsByLevel = groups;
|
_configRewardGroupsByLevel = groups;
|
||||||
|
if (!_levelTouched) {
|
||||||
|
_selectedLevel = _resolvedSelectedLevelIndex(levelItems, _status);
|
||||||
|
}
|
||||||
|
selectedLevelNumber = _levelNumberForIndex(levelItems, _selectedLevel);
|
||||||
});
|
});
|
||||||
|
unawaited(_loadCrewForLevel(selectedLevelNumber));
|
||||||
|
unawaited(_loadRewardForLevel(selectedLevelNumber));
|
||||||
_rocketUiDebug(
|
_rocketUiDebug(
|
||||||
'rocket level contents loaded levels=${contents.levels.length} '
|
'rocket level contents loaded levels=${contents.levels.length} '
|
||||||
'rewardLevels=${groups.keys.toList()}',
|
'rewardLevels=${groups.keys.toList()}',
|
||||||
@ -471,8 +561,11 @@ class _RoomRocketDialogLoaderState extends State<_RoomRocketDialogLoader> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final status = _status;
|
final status = _status;
|
||||||
final selectedLevel = _selectedLevel.clamp(0, 5).toInt();
|
final levelItems =
|
||||||
final selectedLevelNumber = selectedLevel + 1;
|
RoomRocketApiMapper.levelsFromLevelContents(_levelContents) ??
|
||||||
|
RoomRocketApiMapper.levelsFromStatus(status);
|
||||||
|
final selectedLevel = _resolvedSelectedLevelIndex(levelItems, status);
|
||||||
|
final selectedLevelNumber = _levelNumberForIndex(levelItems, selectedLevel);
|
||||||
final currentLevelNumber = _levelNumberFromStatus(status);
|
final currentLevelNumber = _levelNumberFromStatus(status);
|
||||||
final statusRewardGroups = RoomRocketApiMapper.rewardGroupsFromStatus(
|
final statusRewardGroups = RoomRocketApiMapper.rewardGroupsFromStatus(
|
||||||
status,
|
status,
|
||||||
@ -487,9 +580,6 @@ class _RoomRocketDialogLoaderState extends State<_RoomRocketDialogLoader> {
|
|||||||
: rewardGroupsFromStatus ??
|
: rewardGroupsFromStatus ??
|
||||||
configRewardGroups ??
|
configRewardGroups ??
|
||||||
const RoomRocketRewardGroups();
|
const RoomRocketRewardGroups();
|
||||||
final levelItems =
|
|
||||||
RoomRocketApiMapper.levelsFromLevelContents(_levelContents) ??
|
|
||||||
RoomRocketApiMapper.levelsFromStatus(status);
|
|
||||||
return RoomRocketDialog(
|
return RoomRocketDialog(
|
||||||
status: status,
|
status: status,
|
||||||
levels: levelItems,
|
levels: levelItems,
|
||||||
@ -536,13 +626,32 @@ class _RoomRocketDialogLoaderState extends State<_RoomRocketDialogLoader> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int _levelIndexFromStatus(SCRoomRocketStatusRes? status) {
|
int _levelIndexFromStatus(SCRoomRocketStatusRes? status) {
|
||||||
return ((status?.currentLevel ?? status?.level ?? 1) - 1)
|
return _resolveRoomRocketSelectedLevelIndex(
|
||||||
.clamp(0, 5)
|
levelItems: RoomRocketApiMapper.levelsFromStatus(status),
|
||||||
.toInt();
|
status: status,
|
||||||
|
selectedLevel: _selectedLevel,
|
||||||
|
levelTouched: false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
int _levelNumberFromStatus(SCRoomRocketStatusRes? status) {
|
int _levelNumberFromStatus(SCRoomRocketStatusRes? status) {
|
||||||
return (status?.currentLevel ?? status?.level ?? 1).clamp(1, 99).toInt();
|
return _roomRocketLevelNumberFromStatus(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _resolvedSelectedLevelIndex(
|
||||||
|
List<RoomRocketLevelItem>? levelItems,
|
||||||
|
SCRoomRocketStatusRes? status,
|
||||||
|
) {
|
||||||
|
return _resolveRoomRocketSelectedLevelIndex(
|
||||||
|
levelItems: levelItems,
|
||||||
|
status: status,
|
||||||
|
selectedLevel: _selectedLevel,
|
||||||
|
levelTouched: _levelTouched,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _levelNumberForIndex(List<RoomRocketLevelItem>? levelItems, int index) {
|
||||||
|
return _roomRocketLevelNumberForIndex(levelItems, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _cacheRewardGroupsFromStatus(SCRoomRocketStatusRes? status, int level) {
|
void _cacheRewardGroupsFromStatus(SCRoomRocketStatusRes? status, int level) {
|
||||||
@ -576,13 +685,17 @@ class _RoomRocketDialogLoaderState extends State<_RoomRocketDialogLoader> {
|
|||||||
|
|
||||||
void _onLevelSelected(int index) {
|
void _onLevelSelected(int index) {
|
||||||
final selectedIndex = index.clamp(0, 5).toInt();
|
final selectedIndex = index.clamp(0, 5).toInt();
|
||||||
_rocketUiDebug('select rocket level=${selectedIndex + 1}');
|
final levelItems =
|
||||||
|
RoomRocketApiMapper.levelsFromLevelContents(_levelContents) ??
|
||||||
|
RoomRocketApiMapper.levelsFromStatus(_status);
|
||||||
|
final selectedLevelNumber = _levelNumberForIndex(levelItems, selectedIndex);
|
||||||
|
_rocketUiDebug('select rocket level=$selectedLevelNumber');
|
||||||
setState(() {
|
setState(() {
|
||||||
_levelTouched = true;
|
_levelTouched = true;
|
||||||
_selectedLevel = selectedIndex;
|
_selectedLevel = selectedIndex;
|
||||||
});
|
});
|
||||||
unawaited(_loadRewardForLevel(selectedIndex + 1));
|
unawaited(_loadRewardForLevel(selectedLevelNumber));
|
||||||
unawaited(_loadCrewForLevel(selectedIndex + 1));
|
unawaited(_loadCrewForLevel(selectedLevelNumber));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadRewardForLevel(int level) async {
|
Future<void> _loadRewardForLevel(int level) async {
|
||||||
@ -801,22 +914,25 @@ class _RoomRocketRecordDialogLoaderState
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
unawaited(_load(reset: true));
|
unawaited(_loadAllRecords());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _load({bool reset = false}) async {
|
Future<void> _loadAllRecords() async {
|
||||||
if (_loadingMore || (!reset && (!_hasMore || _loading))) {
|
if (_loadingMore) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final requestCursor = reset ? 1 : _cursor;
|
|
||||||
setState(() {
|
setState(() {
|
||||||
if (reset) {
|
|
||||||
_loading = true;
|
_loading = true;
|
||||||
} else {
|
_loadingMore = false;
|
||||||
_loadingMore = true;
|
_hasMore = true;
|
||||||
}
|
_cursor = 1;
|
||||||
|
_rawRecords.clear();
|
||||||
|
_records = null;
|
||||||
});
|
});
|
||||||
|
var requestCursor = 1;
|
||||||
|
final allRecords = <SCRoomRocketRewardRecordRes>[];
|
||||||
try {
|
try {
|
||||||
|
while (mounted) {
|
||||||
_rocketUiDebug(
|
_rocketUiDebug(
|
||||||
'request reward-records roomId=${widget.roomId} '
|
'request reward-records roomId=${widget.roomId} '
|
||||||
'cursor=$requestCursor limit=$_rocketPageSize',
|
'cursor=$requestCursor limit=$_rocketPageSize',
|
||||||
@ -830,31 +946,43 @@ class _RoomRocketRecordDialogLoaderState
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final nextRecords = res.records;
|
final nextRecords = res.records;
|
||||||
|
allRecords.addAll(nextRecords);
|
||||||
|
_rocketUiDebug(
|
||||||
|
'reward-records loaded cursor=$requestCursor '
|
||||||
|
'count=${nextRecords.length} total=${allRecords.length}',
|
||||||
|
);
|
||||||
|
if (nextRecords.length < _rocketPageSize) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
requestCursor += 1;
|
||||||
|
}
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
if (reset) {
|
|
||||||
_rawRecords
|
_rawRecords
|
||||||
..clear()
|
..clear()
|
||||||
..addAll(nextRecords);
|
..addAll(allRecords);
|
||||||
} else {
|
|
||||||
_rawRecords.addAll(nextRecords);
|
|
||||||
}
|
|
||||||
_records = RoomRocketApiMapper.recordItemsFromRecords(_rawRecords);
|
_records = RoomRocketApiMapper.recordItemsFromRecords(_rawRecords);
|
||||||
_hasMore = nextRecords.length >= _rocketPageSize;
|
_hasMore = false;
|
||||||
if (_hasMore) {
|
_cursor = requestCursor;
|
||||||
_cursor = requestCursor + 1;
|
|
||||||
}
|
|
||||||
_loading = false;
|
_loading = false;
|
||||||
_loadingMore = false;
|
_loadingMore = false;
|
||||||
});
|
});
|
||||||
_rocketUiDebug(
|
_rocketUiDebug(
|
||||||
'reward-records loaded cursor=$requestCursor '
|
'reward-records all loaded total=${_rawRecords.length} '
|
||||||
'count=${nextRecords.length} total=${_rawRecords.length} '
|
'lastCursor=$_cursor',
|
||||||
'hasMore=$_hasMore nextCursor=$_cursor',
|
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
_rocketUiDebug('reward-records request failed: $error');
|
_rocketUiDebug('reward-records request failed: $error');
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
if (allRecords.isNotEmpty) {
|
||||||
|
_rawRecords
|
||||||
|
..clear()
|
||||||
|
..addAll(allRecords);
|
||||||
|
_records = RoomRocketApiMapper.recordItemsFromRecords(_rawRecords);
|
||||||
|
}
|
||||||
_loading = false;
|
_loading = false;
|
||||||
_loadingMore = false;
|
_loadingMore = false;
|
||||||
_hasMore = false;
|
_hasMore = false;
|
||||||
@ -871,7 +999,7 @@ class _RoomRocketRecordDialogLoaderState
|
|||||||
loadingText: _loading ? 'Loading data......' : '',
|
loadingText: _loading ? 'Loading data......' : '',
|
||||||
isLoadingMore: _loadingMore,
|
isLoadingMore: _loadingMore,
|
||||||
hasMore: _hasMore,
|
hasMore: _hasMore,
|
||||||
onLoadMore: () => unawaited(_load()),
|
onLoadMore: null,
|
||||||
limitText: 'Show only records from the last 35 days',
|
limitText: 'Show only records from the last 35 days',
|
||||||
onClose: widget.onClose,
|
onClose: widget.onClose,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -11,17 +11,21 @@ import 'package:yumi/ui_kit/components/custom_cached_image.dart';
|
|||||||
import 'package:yumi/ui_kit/components/sc_rotating_dots_loading.dart';
|
import 'package:yumi/ui_kit/components/sc_rotating_dots_loading.dart';
|
||||||
|
|
||||||
const Duration _pagMemoryTtl = Duration(minutes: 5);
|
const Duration _pagMemoryTtl = Duration(minutes: 5);
|
||||||
|
const Size _roomRocketFallbackPagRawSize = Size(750, 1624);
|
||||||
|
const Rect _roomRocketTopAvatarRawRect = Rect.fromLTWH(291, 164, 168, 168);
|
||||||
|
|
||||||
class RoomRocketPagEffectOverlay extends StatefulWidget {
|
class RoomRocketPagEffectOverlay extends StatefulWidget {
|
||||||
const RoomRocketPagEffectOverlay({
|
const RoomRocketPagEffectOverlay({
|
||||||
super.key,
|
super.key,
|
||||||
required this.resource,
|
required this.resource,
|
||||||
this.topAvatarUrl,
|
this.topAvatarUrl,
|
||||||
|
this.topAvatarUrlListenable,
|
||||||
this.onCompleted,
|
this.onCompleted,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String resource;
|
final String resource;
|
||||||
final String? topAvatarUrl;
|
final String? topAvatarUrl;
|
||||||
|
final ValueListenable<String?>? topAvatarUrlListenable;
|
||||||
final VoidCallback? onCompleted;
|
final VoidCallback? onCompleted;
|
||||||
|
|
||||||
static bool isPag(String? resource) {
|
static bool isPag(String? resource) {
|
||||||
@ -50,6 +54,7 @@ class RoomRocketPagEffectOverlay extends StatefulWidget {
|
|||||||
class _RoomRocketPagEffectOverlayState
|
class _RoomRocketPagEffectOverlayState
|
||||||
extends State<RoomRocketPagEffectOverlay> {
|
extends State<RoomRocketPagEffectOverlay> {
|
||||||
bool _completed = false;
|
bool _completed = false;
|
||||||
|
Size? _pagRawSize;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -65,10 +70,7 @@ class _RoomRocketPagEffectOverlayState
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
_RoomRocketPagTopAvatar(
|
_buildTopAvatar(size),
|
||||||
avatarUrl: widget.topAvatarUrl,
|
|
||||||
canvasSize: size,
|
|
||||||
),
|
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: _RoomRocketPagView(
|
child: _RoomRocketPagView(
|
||||||
resource: resource,
|
resource: resource,
|
||||||
@ -79,6 +81,7 @@ class _RoomRocketPagEffectOverlayState
|
|||||||
displayScale: 1,
|
displayScale: 1,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
memoryTtl: _pagMemoryTtl,
|
memoryTtl: _pagMemoryTtl,
|
||||||
|
onReady: _updatePagRawSize,
|
||||||
onCompleted: _complete,
|
onCompleted: _complete,
|
||||||
defaultBuilder:
|
defaultBuilder:
|
||||||
(_) => const Center(child: SCRotatingDotsLoading()),
|
(_) => const Center(child: SCRotatingDotsLoading()),
|
||||||
@ -99,16 +102,48 @@ class _RoomRocketPagEffectOverlayState
|
|||||||
_completed = true;
|
_completed = true;
|
||||||
widget.onCompleted?.call();
|
widget.onCompleted?.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _updatePagRawSize(Size rawSize) {
|
||||||
|
if (_pagRawSize == rawSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() => _pagRawSize = rawSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTopAvatar(Size canvasSize) {
|
||||||
|
final listenable = widget.topAvatarUrlListenable;
|
||||||
|
if (listenable == null) {
|
||||||
|
return _RoomRocketPagTopAvatar(
|
||||||
|
avatarUrl: widget.topAvatarUrl,
|
||||||
|
canvasSize: canvasSize,
|
||||||
|
pagRawSize: _pagRawSize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ValueListenableBuilder<String?>(
|
||||||
|
valueListenable: listenable,
|
||||||
|
builder:
|
||||||
|
(context, avatarUrl, _) => _RoomRocketPagTopAvatar(
|
||||||
|
avatarUrl: _firstNonBlankPagAvatar([
|
||||||
|
avatarUrl,
|
||||||
|
widget.topAvatarUrl,
|
||||||
|
]),
|
||||||
|
canvasSize: canvasSize,
|
||||||
|
pagRawSize: _pagRawSize,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RoomRocketPagTopAvatar extends StatelessWidget {
|
class _RoomRocketPagTopAvatar extends StatelessWidget {
|
||||||
const _RoomRocketPagTopAvatar({
|
const _RoomRocketPagTopAvatar({
|
||||||
required this.avatarUrl,
|
required this.avatarUrl,
|
||||||
required this.canvasSize,
|
required this.canvasSize,
|
||||||
|
required this.pagRawSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String? avatarUrl;
|
final String? avatarUrl;
|
||||||
final Size canvasSize;
|
final Size canvasSize;
|
||||||
|
final Size? pagRawSize;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -116,12 +151,13 @@ class _RoomRocketPagTopAvatar extends StatelessWidget {
|
|||||||
if (url.isEmpty) {
|
if (url.isEmpty) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
final size = (canvasSize.width * 0.15).clamp(46.0, 66.0).toDouble();
|
final rect = _mapRoomRocketPagRawRectToCoverCanvas(
|
||||||
return Positioned(
|
rawRect: _roomRocketTopAvatarRawRect,
|
||||||
left: (canvasSize.width - size) / 2,
|
rawSize: pagRawSize ?? _roomRocketFallbackPagRawSize,
|
||||||
top: canvasSize.height * 0.098,
|
canvasSize: canvasSize,
|
||||||
width: size,
|
);
|
||||||
height: size,
|
return Positioned.fromRect(
|
||||||
|
rect: rect,
|
||||||
child: ClipOval(
|
child: ClipOval(
|
||||||
child: CustomCachedImage(
|
child: CustomCachedImage(
|
||||||
imageUrl: url,
|
imageUrl: url,
|
||||||
@ -200,6 +236,7 @@ class _RoomRocketPagView extends StatefulWidget {
|
|||||||
required this.clipBehavior,
|
required this.clipBehavior,
|
||||||
required this.memoryTtl,
|
required this.memoryTtl,
|
||||||
this.underlayBuilder,
|
this.underlayBuilder,
|
||||||
|
this.onReady,
|
||||||
this.onCompleted,
|
this.onCompleted,
|
||||||
this.defaultBuilder,
|
this.defaultBuilder,
|
||||||
});
|
});
|
||||||
@ -213,6 +250,7 @@ class _RoomRocketPagView extends StatefulWidget {
|
|||||||
final Clip clipBehavior;
|
final Clip clipBehavior;
|
||||||
final Duration memoryTtl;
|
final Duration memoryTtl;
|
||||||
final WidgetBuilder? underlayBuilder;
|
final WidgetBuilder? underlayBuilder;
|
||||||
|
final ValueChanged<Size>? onReady;
|
||||||
final VoidCallback? onCompleted;
|
final VoidCallback? onCompleted;
|
||||||
final WidgetBuilder? defaultBuilder;
|
final WidgetBuilder? defaultBuilder;
|
||||||
|
|
||||||
@ -414,6 +452,7 @@ class _RoomRocketPagViewState extends State<_RoomRocketPagView> {
|
|||||||
'viewSize=${widget.width?.toStringAsFixed(1)}x'
|
'viewSize=${widget.width?.toStringAsFixed(1)}x'
|
||||||
'${widget.height?.toStringAsFixed(1)} fit=${widget.fit.name}',
|
'${widget.height?.toStringAsFixed(1)} fit=${widget.fit.name}',
|
||||||
);
|
);
|
||||||
|
widget.onReady?.call(Size(rawWidth, rawHeight));
|
||||||
unawaited(_debugLayers(state, rawWidth, rawHeight));
|
unawaited(_debugLayers(state, rawWidth, rawHeight));
|
||||||
if (!_initialized) {
|
if (!_initialized) {
|
||||||
setState(() => _initialized = true);
|
setState(() => _initialized = true);
|
||||||
@ -587,6 +626,55 @@ Timer? _schedulePagMemoryReleaseTimer(
|
|||||||
return Timer(memoryTtl, onRelease);
|
return Timer(memoryTtl, onRelease);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
Rect debugMapRoomRocketPagRawRectToCoverCanvas({
|
||||||
|
required Rect rawRect,
|
||||||
|
required Size rawSize,
|
||||||
|
required Size canvasSize,
|
||||||
|
}) {
|
||||||
|
return _mapRoomRocketPagRawRectToCoverCanvas(
|
||||||
|
rawRect: rawRect,
|
||||||
|
rawSize: rawSize,
|
||||||
|
canvasSize: canvasSize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect _mapRoomRocketPagRawRectToCoverCanvas({
|
||||||
|
required Rect rawRect,
|
||||||
|
required Size rawSize,
|
||||||
|
required Size canvasSize,
|
||||||
|
}) {
|
||||||
|
if (rawSize.width <= 0 ||
|
||||||
|
rawSize.height <= 0 ||
|
||||||
|
canvasSize.width <= 0 ||
|
||||||
|
canvasSize.height <= 0) {
|
||||||
|
return Rect.zero;
|
||||||
|
}
|
||||||
|
final scaleX = canvasSize.width / rawSize.width;
|
||||||
|
final scaleY = canvasSize.height / rawSize.height;
|
||||||
|
final scale = scaleX > scaleY ? scaleX : scaleY;
|
||||||
|
final fittedWidth = rawSize.width * scale;
|
||||||
|
final fittedHeight = rawSize.height * scale;
|
||||||
|
final offsetX = (canvasSize.width - fittedWidth) / 2;
|
||||||
|
final offsetY = (canvasSize.height - fittedHeight) / 2;
|
||||||
|
return Rect.fromLTWH(
|
||||||
|
offsetX + rawRect.left * scale,
|
||||||
|
offsetY + rawRect.top * scale,
|
||||||
|
rawRect.width * scale,
|
||||||
|
rawRect.height * scale,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _firstNonBlankPagAvatar(Iterable<String?> values) {
|
||||||
|
for (final value in values) {
|
||||||
|
final text = value?.trim() ?? '';
|
||||||
|
if (text.isNotEmpty) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
String debugNormalizeRoomRocketPagResource(String resource) {
|
String debugNormalizeRoomRocketPagResource(String resource) {
|
||||||
return _normalizePagResource(resource);
|
return _normalizePagResource(resource);
|
||||||
|
|||||||
@ -35,7 +35,7 @@ class RoomRocketRewardDialog extends StatelessWidget {
|
|||||||
final emptyText = _translate(
|
final emptyText = _translate(
|
||||||
localizations,
|
localizations,
|
||||||
'roomRocketRewardEmptyText',
|
'roomRocketRewardEmptyText',
|
||||||
'No rewards earned. Better luck next time.',
|
'No reward',
|
||||||
);
|
);
|
||||||
final contentDirection = Directionality.of(context);
|
final contentDirection = Directionality.of(context);
|
||||||
final resolvedRewards = _normalizeRewards(
|
final resolvedRewards = _normalizeRewards(
|
||||||
@ -257,7 +257,7 @@ class _RewardDialogCanvas extends StatelessWidget {
|
|||||||
if (isEmpty)
|
if (isEmpty)
|
||||||
_buildEmptyState()
|
_buildEmptyState()
|
||||||
else ...[
|
else ...[
|
||||||
_buildRewardGrid(),
|
rewards.isEmpty ? _buildRewardEmptyState() : _buildRewardGrid(),
|
||||||
_buildWinnerList(),
|
_buildWinnerList(),
|
||||||
],
|
],
|
||||||
_buildLoadingText(),
|
_buildLoadingText(),
|
||||||
@ -380,9 +380,6 @@ class _RewardDialogCanvas extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRewardGrid() {
|
Widget _buildRewardGrid() {
|
||||||
if (rewards.isEmpty) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
final columns = math.max(3, (rewards.length / 2).ceil());
|
final columns = math.max(3, (rewards.length / 2).ceil());
|
||||||
return Positioned(
|
return Positioned(
|
||||||
left: s(28),
|
left: s(28),
|
||||||
@ -425,6 +422,45 @@ class _RewardDialogCanvas extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildRewardEmptyState() {
|
||||||
|
return Positioned(
|
||||||
|
left: s(0),
|
||||||
|
top: s(268),
|
||||||
|
width: s(_designWidth),
|
||||||
|
height: s(178),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
_RewardDialogAssets.empty,
|
||||||
|
width: s(72),
|
||||||
|
height: s(72),
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
SizedBox(height: s(10)),
|
||||||
|
SizedBox(
|
||||||
|
width: s(250),
|
||||||
|
child: Text(
|
||||||
|
emptyText,
|
||||||
|
textDirection: contentDirection,
|
||||||
|
textScaler: TextScaler.noScaling,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white.withValues(alpha: 0.72),
|
||||||
|
fontSize: s(13),
|
||||||
|
height: 1.25,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildWinnerList() {
|
Widget _buildWinnerList() {
|
||||||
return Positioned(
|
return Positioned(
|
||||||
left: s(12),
|
left: s(12),
|
||||||
|
|||||||
@ -81,7 +81,7 @@ class _MsgItemState extends State<MsgItem> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
///火箭用户中奖
|
///火箭用户中奖
|
||||||
if (widget.msg.type == SCRoomMsgType.rocketRewardUser) {
|
if (SCRoomMsgType.isRocketRewardUserType(widget.msg.type)) {
|
||||||
return _buildRocketRewardMsg(context);
|
return _buildRocketRewardMsg(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -692,10 +692,17 @@ class _MsgItemState extends State<MsgItem> {
|
|||||||
///火箭用户中奖
|
///火箭用户中奖
|
||||||
_buildRocketRewardMsg(BuildContext context) {
|
_buildRocketRewardMsg(BuildContext context) {
|
||||||
List<InlineSpan> spans = [];
|
List<InlineSpan> spans = [];
|
||||||
|
final nickname = Msg._firstNonBlankString([
|
||||||
|
widget.msg.user?.userNickname,
|
||||||
|
widget.msg.user?.account,
|
||||||
|
widget.msg.user?.id,
|
||||||
|
SCAppLocalizations.of(context)?.member,
|
||||||
|
'Member',
|
||||||
|
]);
|
||||||
|
|
||||||
spans.add(
|
spans.add(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "${widget.msg.user?.userNickname} ",
|
text: "$nickname ",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: sp(12),
|
fontSize: sp(12),
|
||||||
color: SocialChatTheme.primaryColor,
|
color: SocialChatTheme.primaryColor,
|
||||||
@ -1995,6 +2002,118 @@ class Msg {
|
|||||||
|
|
||||||
gift =
|
gift =
|
||||||
json['gift'] != null ? SocialChatGiftRes.fromJson(json['gift']) : null;
|
json['gift'] != null ? SocialChatGiftRes.fromJson(json['gift']) : null;
|
||||||
|
_hydrateRocketRewardPayload(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _hydrateRocketRewardPayload(dynamic json) {
|
||||||
|
if (!SCRoomMsgType.isRocketRewardUserType(type)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final root = _stringKeyMap(json);
|
||||||
|
final payload = _stringKeyMap(root['data']);
|
||||||
|
final source = payload.isEmpty ? root : payload;
|
||||||
|
groupId = _firstNonBlankString([
|
||||||
|
groupId,
|
||||||
|
source['groupId'],
|
||||||
|
source['roomAccount'],
|
||||||
|
source['room_account'],
|
||||||
|
source['roomId'],
|
||||||
|
source['room_id'],
|
||||||
|
]);
|
||||||
|
role = _firstNonBlankString([
|
||||||
|
role,
|
||||||
|
source['rewardType'],
|
||||||
|
source['reward_type'],
|
||||||
|
]);
|
||||||
|
rocketIconUrl = _firstNonBlankString([
|
||||||
|
rocketIconUrl,
|
||||||
|
source['rocketIconUrl'],
|
||||||
|
source['rocket_icon_url'],
|
||||||
|
source['rocketIcon'],
|
||||||
|
source['rewardCover'],
|
||||||
|
source['reward_cover'],
|
||||||
|
]);
|
||||||
|
msg = _firstNonBlankString([
|
||||||
|
msg,
|
||||||
|
source['rewardCover'],
|
||||||
|
source['reward_cover'],
|
||||||
|
source['rewardIconUrl'],
|
||||||
|
source['reward_icon_url'],
|
||||||
|
source['cover'],
|
||||||
|
rocketIconUrl,
|
||||||
|
]);
|
||||||
|
user ??= _rocketRewardUserProfile(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SocialChatUserProfile? _rocketRewardUserProfile(
|
||||||
|
Map<String, dynamic> source,
|
||||||
|
) {
|
||||||
|
final userId = _firstNonBlankString([
|
||||||
|
source['userId'],
|
||||||
|
source['user_id'],
|
||||||
|
source['rewardUserId'],
|
||||||
|
source['reward_user_id'],
|
||||||
|
source['receiverUserId'],
|
||||||
|
source['receiver_user_id'],
|
||||||
|
source['targetUserId'],
|
||||||
|
source['target_user_id'],
|
||||||
|
]);
|
||||||
|
final nickname = _firstNonBlankString([
|
||||||
|
source['userNickname'],
|
||||||
|
source['user_nickname'],
|
||||||
|
source['nickname'],
|
||||||
|
source['receiverNickname'],
|
||||||
|
source['receiver_nickname'],
|
||||||
|
source['account'],
|
||||||
|
userId,
|
||||||
|
]);
|
||||||
|
final account = _firstNonBlankString([
|
||||||
|
source['account'],
|
||||||
|
source['userAccount'],
|
||||||
|
source['user_account'],
|
||||||
|
source['actualAccount'],
|
||||||
|
source['actual_account'],
|
||||||
|
userId,
|
||||||
|
]);
|
||||||
|
final avatar = _firstNonBlankString([
|
||||||
|
source['userAvatar'],
|
||||||
|
source['user_avatar'],
|
||||||
|
source['avatar'],
|
||||||
|
source['receiverAvatar'],
|
||||||
|
source['receiver_avatar'],
|
||||||
|
]);
|
||||||
|
if (userId.isEmpty &&
|
||||||
|
nickname.isEmpty &&
|
||||||
|
account.isEmpty &&
|
||||||
|
avatar.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return SocialChatUserProfile(
|
||||||
|
id: userId,
|
||||||
|
account: account,
|
||||||
|
userNickname: nickname,
|
||||||
|
userAvatar: avatar,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, dynamic> _stringKeyMap(dynamic value) {
|
||||||
|
if (value is Map<String, dynamic>) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (value is Map) {
|
||||||
|
return value.map((key, item) => MapEntry(key.toString(), item));
|
||||||
|
}
|
||||||
|
return const <String, dynamic>{};
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _firstNonBlankString(Iterable<dynamic> values) {
|
||||||
|
for (final value in values) {
|
||||||
|
final text = value?.toString().trim() ?? '';
|
||||||
|
if (text.isNotEmpty && text.toLowerCase() != 'null') {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
|
|||||||
@ -49,6 +49,10 @@ class RoomUserInfoCard extends StatefulWidget {
|
|||||||
class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
||||||
static const String _leaveMicActionIconAsset =
|
static const String _leaveMicActionIconAsset =
|
||||||
"sc_images/room/sc_icon_room_user_card_leave_mic.png";
|
"sc_images/room/sc_icon_room_user_card_leave_mic.png";
|
||||||
|
static const String _defaultProfileCardBackgroundAsset =
|
||||||
|
"sc_images/room/sc_bg_room_user_profile_card_default.png";
|
||||||
|
static const double _profileSheetHeightFactor = 0.4;
|
||||||
|
static const double _dataCardBackgroundTranslateFactor = 0.8;
|
||||||
|
|
||||||
SocialChatUserProfileManager? userProvider;
|
SocialChatUserProfileManager? userProvider;
|
||||||
String roomId = "";
|
String roomId = "";
|
||||||
@ -85,7 +89,8 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final sheetHeight = MediaQuery.of(context).size.height * 0.6;
|
final screenHeight = MediaQuery.of(context).size.height;
|
||||||
|
final defaultSheetHeight = screenHeight * _profileSheetHeightFactor;
|
||||||
|
|
||||||
return Consumer<SocialChatUserProfileManager>(
|
return Consumer<SocialChatUserProfileManager>(
|
||||||
builder: (context, ref, child) {
|
builder: (context, ref, child) {
|
||||||
@ -100,14 +105,18 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
final canLeaveMic = currentMicIndex > -1;
|
final canLeaveMic = currentMicIndex > -1;
|
||||||
|
|
||||||
if (ref.userCardInfo == null) {
|
if (ref.userCardInfo == null) {
|
||||||
return _buildLoadingSheet(sheetHeight);
|
return _buildLoadingSheet(defaultSheetHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final dataCard = _activeDataCard(ref.userCardInfo);
|
||||||
|
|
||||||
return _buildProfileSheet(
|
return _buildProfileSheet(
|
||||||
context: context,
|
context: context,
|
||||||
ref: ref,
|
ref: ref,
|
||||||
rtcProvider: rtcProvider,
|
rtcProvider: rtcProvider,
|
||||||
sheetHeight: sheetHeight,
|
sheetHeight: defaultSheetHeight,
|
||||||
|
dataCardBackgroundHeight: screenHeight,
|
||||||
|
dataCard: dataCard,
|
||||||
isSelf: isSelf,
|
isSelf: isSelf,
|
||||||
currentMicIndex: currentMicIndex,
|
currentMicIndex: currentMicIndex,
|
||||||
canLeaveMic: canLeaveMic,
|
canLeaveMic: canLeaveMic,
|
||||||
@ -123,9 +132,10 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
height: sheetHeight,
|
height: sheetHeight,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.vertical(top: Radius.circular(18.w)),
|
borderRadius: BorderRadius.vertical(top: Radius.circular(18.w)),
|
||||||
child: DecoratedBox(
|
child: Stack(
|
||||||
decoration: _sheetDecoration(),
|
children: [
|
||||||
child: Center(
|
Positioned.fill(child: _buildDefaultSheetBackground()),
|
||||||
|
Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 55.w,
|
width: 55.w,
|
||||||
height: 55.w,
|
height: 55.w,
|
||||||
@ -136,6 +146,7 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
child: CupertinoActivityIndicator(color: Colors.white24),
|
child: CupertinoActivityIndicator(color: Colors.white24),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -146,6 +157,8 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
required SocialChatUserProfileManager ref,
|
required SocialChatUserProfileManager ref,
|
||||||
required RtcProvider rtcProvider,
|
required RtcProvider rtcProvider,
|
||||||
required double sheetHeight,
|
required double sheetHeight,
|
||||||
|
required double dataCardBackgroundHeight,
|
||||||
|
required PropsResources? dataCard,
|
||||||
required bool isSelf,
|
required bool isSelf,
|
||||||
required num currentMicIndex,
|
required num currentMicIndex,
|
||||||
required bool canLeaveMic,
|
required bool canLeaveMic,
|
||||||
@ -153,13 +166,26 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
}) {
|
}) {
|
||||||
final bottomPadding = MediaQuery.of(context).padding.bottom;
|
final bottomPadding = MediaQuery.of(context).padding.bottom;
|
||||||
final profile = ref.userCardInfo?.userProfile;
|
final profile = ref.userCardInfo?.userProfile;
|
||||||
final dataCard = _activeDataCard(ref.userCardInfo);
|
|
||||||
final canShowReport = currentUserId != widget.userId;
|
final canShowReport = currentUserId != widget.userId;
|
||||||
final canShowSetting =
|
final canShowSetting =
|
||||||
canShowReport &&
|
canShowReport &&
|
||||||
ref.userCardInfo?.roomRole != SCRoomRolesType.HOMEOWNER.name &&
|
ref.userCardInfo?.roomRole != SCRoomRolesType.HOMEOWNER.name &&
|
||||||
(rtcProvider.isFz() || rtcProvider.isGL());
|
(rtcProvider.isFz() || rtcProvider.isGL());
|
||||||
|
|
||||||
|
final profileContent = _buildProfileContentLayer(
|
||||||
|
context: context,
|
||||||
|
ref: ref,
|
||||||
|
rtcProvider: rtcProvider,
|
||||||
|
profile: profile,
|
||||||
|
bottomPadding: bottomPadding,
|
||||||
|
isSelf: isSelf,
|
||||||
|
canLeaveMic: canLeaveMic,
|
||||||
|
currentMicIndex: currentMicIndex,
|
||||||
|
canShowReport: canShowReport,
|
||||||
|
canShowSetting: canShowSetting,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (dataCard == null) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: sheetHeight,
|
height: sheetHeight,
|
||||||
@ -167,29 +193,102 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
borderRadius: BorderRadius.vertical(top: Radius.circular(18.w)),
|
borderRadius: BorderRadius.vertical(top: Radius.circular(18.w)),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(child: _buildSheetBackground(dataCard)),
|
Positioned.fill(child: _buildDefaultSheetBackground()),
|
||||||
|
Positioned.fill(child: profileContent),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _buildDataCardFrame(
|
||||||
|
sheetHeight: sheetHeight,
|
||||||
|
backgroundHeight: dataCardBackgroundHeight,
|
||||||
|
dataCard: dataCard,
|
||||||
|
child: profileContent,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDataCardFrame({
|
||||||
|
required double sheetHeight,
|
||||||
|
required double backgroundHeight,
|
||||||
|
required PropsResources dataCard,
|
||||||
|
required Widget child,
|
||||||
|
}) {
|
||||||
|
return SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: sheetHeight,
|
||||||
|
child: Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
children: [
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: SingleChildScrollView(
|
child: ClipRRect(
|
||||||
physics: const ClampingScrollPhysics(),
|
borderRadius: BorderRadius.vertical(top: Radius.circular(18.w)),
|
||||||
|
child: DecoratedBox(decoration: _sheetDecoration()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top:
|
||||||
|
-(backgroundHeight - sheetHeight) +
|
||||||
|
sheetHeight * _dataCardBackgroundTranslateFactor,
|
||||||
|
height: backgroundHeight,
|
||||||
|
child: IgnorePointer(child: _buildSheetBackground(dataCard)),
|
||||||
|
),
|
||||||
|
Positioned.fill(
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(18.w)),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildProfileContentLayer({
|
||||||
|
required BuildContext context,
|
||||||
|
required SocialChatUserProfileManager ref,
|
||||||
|
required RtcProvider rtcProvider,
|
||||||
|
required SocialChatUserProfile? profile,
|
||||||
|
required double bottomPadding,
|
||||||
|
required bool isSelf,
|
||||||
|
required bool canLeaveMic,
|
||||||
|
required num currentMicIndex,
|
||||||
|
required bool canShowReport,
|
||||||
|
required bool canShowSetting,
|
||||||
|
}) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Positioned.fill(
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
child: SizedBox(
|
||||||
|
width: constraints.maxWidth,
|
||||||
|
child: Padding(
|
||||||
padding: EdgeInsets.fromLTRB(
|
padding: EdgeInsets.fromLTRB(
|
||||||
18.w,
|
18.w,
|
||||||
16.w,
|
10.w,
|
||||||
18.w,
|
18.w,
|
||||||
bottomPadding + 10.w,
|
bottomPadding > 6.w ? bottomPadding : 6.w,
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
_buildProfileHeader(context, profile, ref),
|
_buildProfileHeader(context, profile, ref),
|
||||||
SizedBox(height: 6.w),
|
SizedBox(height: 6.w),
|
||||||
_buildInfoChips(context, profile),
|
|
||||||
SizedBox(height: 9.w),
|
|
||||||
_buildBadgeStrip(
|
_buildBadgeStrip(
|
||||||
profile?.id ?? widget.userId,
|
profile?.id ?? widget.userId,
|
||||||
ref,
|
ref,
|
||||||
profile,
|
profile,
|
||||||
),
|
),
|
||||||
SizedBox(height: 10.w),
|
SizedBox(height: 8.w),
|
||||||
_buildActions(
|
_buildActions(
|
||||||
context: context,
|
context: context,
|
||||||
ref: ref,
|
ref: ref,
|
||||||
@ -202,6 +301,11 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
if (canShowReport)
|
if (canShowReport)
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 14.w,
|
top: 14.w,
|
||||||
@ -225,8 +329,7 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
top: 14.w,
|
top: 14.w,
|
||||||
right: 52.w,
|
right: 52.w,
|
||||||
child: _buildHeaderIconButton(
|
child: _buildHeaderIconButton(
|
||||||
iconAsset:
|
iconAsset: "sc_images/room/sc_icon_room_user_card_setting.png",
|
||||||
"sc_images/room/sc_icon_room_user_card_setting.png",
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (userProvider?.userCardInfo == null) {
|
if (userProvider?.userCardInfo == null) {
|
||||||
return;
|
return;
|
||||||
@ -243,48 +346,35 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
BoxDecoration _sheetDecoration() {
|
BoxDecoration _sheetDecoration() {
|
||||||
return const BoxDecoration(
|
return const BoxDecoration(color: Color(0xff061F1E));
|
||||||
gradient: LinearGradient(
|
}
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
Widget _buildDefaultSheetBackground() {
|
||||||
colors: [
|
return DecoratedBox(
|
||||||
Color(0xff18F2B1),
|
decoration: _sheetDecoration(),
|
||||||
Color(0xffE4FFF6),
|
child: SizedBox.expand(
|
||||||
Color(0xffF7FFFC),
|
child: Image.asset(
|
||||||
Color(0xffFFFFFF),
|
_defaultProfileCardBackgroundAsset,
|
||||||
],
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSheetBackground(PropsResources? dataCard) {
|
Widget _buildSheetBackground(PropsResources? dataCard) {
|
||||||
if (dataCard == null) {
|
if (dataCard == null) {
|
||||||
return DecoratedBox(decoration: _sheetDecoration());
|
return _buildDefaultSheetBackground();
|
||||||
}
|
}
|
||||||
return Stack(
|
|
||||||
fit: StackFit.expand,
|
return SCDataCardResourceView(
|
||||||
children: [
|
resource: dataCard,
|
||||||
SCDataCardResourceView(resource: dataCard, fit: BoxFit.cover),
|
fit: BoxFit.cover,
|
||||||
DecoratedBox(
|
showCoverFallback: false,
|
||||||
decoration: BoxDecoration(
|
allowCoverAsResource: false,
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
|
||||||
colors: [
|
|
||||||
Colors.white.withValues(alpha: 0.08),
|
|
||||||
Colors.white.withValues(alpha: 0.22),
|
|
||||||
Colors.white.withValues(alpha: 0.58),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,8 +415,8 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
onTap: () => _openProfile(context),
|
onTap: () => _openProfile(context),
|
||||||
child: head(
|
child: head(
|
||||||
url: profile?.userAvatar ?? "",
|
url: profile?.userAvatar ?? "",
|
||||||
width: 70.w,
|
width: 105.w,
|
||||||
border: Border.all(color: Colors.white, width: 2),
|
border: Border.all(color: Colors.white, width: 3),
|
||||||
headdress: profile?.getHeaddress()?.sourceUrl,
|
headdress: profile?.getHeaddress()?.sourceUrl,
|
||||||
headdressCover: profile?.getHeaddress()?.cover,
|
headdressCover: profile?.getHeaddress()?.cover,
|
||||||
),
|
),
|
||||||
@ -347,7 +437,7 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
maxWidth: 116.w,
|
maxWidth: 116.w,
|
||||||
profile?.userNickname ?? "",
|
profile?.userNickname ?? "",
|
||||||
fontSize: 18.sp,
|
fontSize: 18.sp,
|
||||||
textColor: Colors.black,
|
textColor: Colors.white,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
type: "",
|
type: "",
|
||||||
needScroll: (profile?.userNickname?.characters.length ?? 0) > 12,
|
needScroll: (profile?.userNickname?.characters.length ?? 0) > 12,
|
||||||
@ -381,6 +471,22 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 2.w),
|
SizedBox(height: 2.w),
|
||||||
|
_buildSpecialIdMetaRow(context, profile),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSpecialIdMetaRow(
|
||||||
|
BuildContext context,
|
||||||
|
SocialChatUserProfile? profile,
|
||||||
|
) {
|
||||||
|
final flagUrl = _countryFlagUrl(context, profile);
|
||||||
|
final hasSex = profile?.userSex != null;
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
SCSpecialIdBadge(
|
SCSpecialIdBadge(
|
||||||
idText: profile?.getID() ?? "",
|
idText: profile?.getID() ?? "",
|
||||||
showAnimated: profile?.hasSpecialId() ?? false,
|
showAnimated: profile?.hasSpecialId() ?? false,
|
||||||
@ -392,106 +498,62 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
showAnimatedGradientText:
|
showAnimatedGradientText:
|
||||||
profile?.shouldShowColoredSpecialIdText() ?? false,
|
profile?.shouldShowColoredSpecialIdText() ?? false,
|
||||||
animationTextStyle: TextStyle(
|
animationTextStyle: TextStyle(
|
||||||
color: Colors.black,
|
color: Colors.white,
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
normalTextStyle: TextStyle(
|
normalTextStyle: TextStyle(
|
||||||
color: Colors.black,
|
color: Colors.white,
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
animationFit: BoxFit.contain,
|
animationFit: BoxFit.contain,
|
||||||
),
|
),
|
||||||
|
if (flagUrl.isNotEmpty) ...[
|
||||||
|
SizedBox(width: 8.w),
|
||||||
|
_buildCountryFlag(flagUrl),
|
||||||
|
],
|
||||||
|
if (hasSex) ...[SizedBox(width: 7.w), _buildSexIcon(profile)],
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildInfoChips(BuildContext context, SocialChatUserProfile? profile) {
|
String _countryFlagUrl(BuildContext context, SocialChatUserProfile? profile) {
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
_buildCountryChip(context, profile),
|
|
||||||
SizedBox(width: 7.w),
|
|
||||||
_buildSexAgeChip(profile),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildCountryChip(
|
|
||||||
BuildContext context,
|
|
||||||
SocialChatUserProfile? profile,
|
|
||||||
) {
|
|
||||||
final countryName = profile?.countryName ?? "";
|
final countryName = profile?.countryName ?? "";
|
||||||
final flagUrl =
|
try {
|
||||||
Provider.of<SCAppGeneralManager>(
|
return Provider.of<SCAppGeneralManager>(
|
||||||
context,
|
context,
|
||||||
listen: false,
|
listen: false,
|
||||||
).findCountryByName(countryName)?.nationalFlag ??
|
).findCountryByName(countryName)?.nationalFlag ??
|
||||||
"";
|
"";
|
||||||
|
} catch (_) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Container(
|
Widget _buildCountryFlag(String flagUrl) {
|
||||||
constraints: BoxConstraints(maxWidth: 145.w),
|
final trimmedUrl = flagUrl.trim();
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 5.w),
|
if (trimmedUrl.isEmpty) {
|
||||||
decoration: BoxDecoration(
|
return const SizedBox.shrink();
|
||||||
color: Colors.white.withValues(alpha: 0.55),
|
}
|
||||||
borderRadius: BorderRadius.circular(16.w),
|
|
||||||
border: Border.all(
|
return ClipRRect(
|
||||||
color: const Color(0xff18F2B1).withValues(alpha: 0.18),
|
borderRadius: BorderRadius.circular(2.w),
|
||||||
),
|
child: netImage(
|
||||||
),
|
url: trimmedUrl,
|
||||||
child: Row(
|
width: 22.w,
|
||||||
mainAxisSize: MainAxisSize.min,
|
height: 15.w,
|
||||||
children: [
|
fit: BoxFit.cover,
|
||||||
netImage(
|
noDefaultImg: true,
|
||||||
url: flagUrl,
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(3.w)),
|
|
||||||
width: 19.w,
|
|
||||||
height: 14.w,
|
|
||||||
),
|
|
||||||
SizedBox(width: 5.w),
|
|
||||||
Flexible(
|
|
||||||
child: text(
|
|
||||||
countryName,
|
|
||||||
maxLines: 1,
|
|
||||||
textColor: Colors.black,
|
|
||||||
fontSize: 12.sp,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSexAgeChip(SocialChatUserProfile? profile) {
|
Widget _buildSexIcon(SocialChatUserProfile? profile) {
|
||||||
return Container(
|
if (profile?.userSex == null) {
|
||||||
height: 26.w,
|
return const SizedBox.shrink();
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10.w),
|
}
|
||||||
decoration: BoxDecoration(
|
return xb(profile?.userSex, height: 15.w, color: null);
|
||||||
image: DecorationImage(
|
|
||||||
image: AssetImage(
|
|
||||||
profile?.userSex == 0
|
|
||||||
? "sc_images/login/sc_icon_sex_woman_bg.png"
|
|
||||||
: "sc_images/login/sc_icon_sex_man_bg.png",
|
|
||||||
),
|
|
||||||
fit: BoxFit.fill,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
xb(profile?.userSex),
|
|
||||||
SizedBox(width: 3.w),
|
|
||||||
text(
|
|
||||||
"${profile?.age ?? 0}",
|
|
||||||
textColor: Colors.white,
|
|
||||||
fontSize: 12.sp,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildHeaderLongBadgeStrip(
|
Widget _buildHeaderLongBadgeStrip(
|
||||||
@ -547,7 +609,7 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
return wearBadges;
|
return wearBadges;
|
||||||
}
|
}
|
||||||
return ref.userCardInfo?.useBadge
|
return ref.userCardInfo?.useBadge
|
||||||
?.map(_roomUseBadgeToWearBadge)
|
?.map<WearBadge>(_roomUseBadgeToWearBadge)
|
||||||
.where(_hasBadgeUrl)
|
.where(_hasBadgeUrl)
|
||||||
.toList() ??
|
.toList() ??
|
||||||
const <WearBadge>[];
|
const <WearBadge>[];
|
||||||
@ -662,7 +724,7 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
label,
|
label,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
textColor: const Color(0xff666666),
|
textColor: Colors.white,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
lineHeight: 1.1,
|
lineHeight: 1.1,
|
||||||
@ -717,7 +779,7 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
: SCAppLocalizations.of(context)!.follow,
|
: SCAppLocalizations.of(context)!.follow,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
textColor: const Color(0xff666666),
|
textColor: Colors.white,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
lineHeight: 1.1,
|
lineHeight: 1.1,
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import 'package:extended_image/extended_image.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
@ -70,11 +68,6 @@ class _PropsStoreChatboxDetailDialogState
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(width: 15.w),
|
SizedBox(width: 15.w),
|
||||||
text(
|
|
||||||
widget.res.propsResources?.name ?? "",
|
|
||||||
textColor: Colors.white,
|
|
||||||
fontSize: 12.sp,
|
|
||||||
),
|
|
||||||
Spacer(),
|
Spacer(),
|
||||||
Image.asset(
|
Image.asset(
|
||||||
"sc_images/general/sc_icon_jb.png",
|
"sc_images/general/sc_icon_jb.png",
|
||||||
|
|||||||
@ -74,14 +74,7 @@ class _PropsStoreDataCardDetailDialogState
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(width: 15.w),
|
SizedBox(width: 15.w),
|
||||||
Expanded(
|
const Spacer(),
|
||||||
child: text(
|
|
||||||
widget.res.propsResources?.name ?? "",
|
|
||||||
textColor: Colors.white,
|
|
||||||
fontSize: 12.sp,
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Image.asset(
|
Image.asset(
|
||||||
"sc_images/general/sc_icon_jb.png",
|
"sc_images/general/sc_icon_jb.png",
|
||||||
width: 22.w,
|
width: 22.w,
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
@ -77,11 +76,6 @@ class _PropsStoreHeaddressDetailDialogState
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(width: 15.w),
|
SizedBox(width: 15.w),
|
||||||
text(
|
|
||||||
widget.res.propsResources?.name ?? "",
|
|
||||||
textColor: Colors.white,
|
|
||||||
fontSize: 12.sp,
|
|
||||||
),
|
|
||||||
Spacer(),
|
Spacer(),
|
||||||
Image.asset(
|
Image.asset(
|
||||||
"sc_images/general/sc_icon_jb.png",
|
"sc_images/general/sc_icon_jb.png",
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
@ -16,7 +15,6 @@ import '../../../app/constants/sc_global_config.dart';
|
|||||||
import '../../../app/routes/sc_fluro_navigator.dart';
|
import '../../../app/routes/sc_fluro_navigator.dart';
|
||||||
import '../../../shared/data_sources/models/enum/sc_currency_type.dart';
|
import '../../../shared/data_sources/models/enum/sc_currency_type.dart';
|
||||||
import '../../../shared/data_sources/models/enum/sc_props_type.dart';
|
import '../../../shared/data_sources/models/enum/sc_props_type.dart';
|
||||||
import '../../../shared/data_sources/sources/local/user_manager.dart';
|
|
||||||
import '../../../modules/wallet/wallet_route.dart';
|
import '../../../modules/wallet/wallet_route.dart';
|
||||||
import '../../../main.dart';
|
import '../../../main.dart';
|
||||||
import '../../components/sc_debounce_widget.dart';
|
import '../../components/sc_debounce_widget.dart';
|
||||||
@ -76,11 +74,6 @@ class _PropsStoreMountainsDetailDialogState
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(width: 15.w),
|
SizedBox(width: 15.w),
|
||||||
text(
|
|
||||||
widget.res.propsResources?.name ?? "",
|
|
||||||
textColor: Colors.white,
|
|
||||||
fontSize: 12.sp,
|
|
||||||
),
|
|
||||||
Spacer(),
|
Spacer(),
|
||||||
Image.asset(
|
Image.asset(
|
||||||
"sc_images/general/sc_icon_jb.png",
|
"sc_images/general/sc_icon_jb.png",
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
@ -76,11 +75,6 @@ class _PropsStoreThemeDetailDialogState
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(width: 15.w),
|
SizedBox(width: 15.w),
|
||||||
text(
|
|
||||||
widget.res.propsResources?.name ?? "",
|
|
||||||
textColor: Colors.white,
|
|
||||||
fontSize: 12.sp,
|
|
||||||
),
|
|
||||||
Spacer(),
|
Spacer(),
|
||||||
Image.asset(
|
Image.asset(
|
||||||
"sc_images/general/sc_icon_jb.png",
|
"sc_images/general/sc_icon_jb.png",
|
||||||
|
|||||||
BIN
sc_images/room/sc_bg_room_user_profile_card_default.png
Normal file
BIN
sc_images/room/sc_bg_room_user_profile_card_default.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 101 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 95 KiB |
45
test/room_rocket_dialog_test.dart
Normal file
45
test/room_rocket_dialog_test.dart
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:yumi/shared/business_logic/models/res/sc_room_rocket_status_res.dart';
|
||||||
|
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_dialog.dart';
|
||||||
|
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_floating_entry.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('rocket dialog title displays current level before daily round', () {
|
||||||
|
expect(debugRoomRocketTitleText(level: 3, round: 1), 'Lv.3 Today');
|
||||||
|
expect(
|
||||||
|
debugRoomRocketTitleText(level: 2, round: 4),
|
||||||
|
'Lv.2 · Round 4 Today',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'rocket level rail follows current level unless user selected manually',
|
||||||
|
() {
|
||||||
|
final status = SCRoomRocketStatusRes.fromJson({'currentLevel': 4});
|
||||||
|
const levels = [
|
||||||
|
RoomRocketLevelItem(label: 'Lv.2', level: 2),
|
||||||
|
RoomRocketLevelItem(label: 'Lv.4', level: 4),
|
||||||
|
RoomRocketLevelItem(label: 'Lv.5', level: 5),
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(
|
||||||
|
debugResolveRoomRocketSelectedLevelIndex(
|
||||||
|
levelItems: levels,
|
||||||
|
status: status,
|
||||||
|
selectedLevel: 0,
|
||||||
|
levelTouched: false,
|
||||||
|
),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
debugResolveRoomRocketSelectedLevelIndex(
|
||||||
|
levelItems: levels,
|
||||||
|
status: status,
|
||||||
|
selectedLevel: 2,
|
||||||
|
levelTouched: true,
|
||||||
|
),
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
43
test/room_rocket_message_test.dart
Normal file
43
test/room_rocket_message_test.dart
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:yumi/app/constants/sc_room_msg_type.dart';
|
||||||
|
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('hydrates rocket reward user from nested im payload', () {
|
||||||
|
final msg = Msg.fromJson({
|
||||||
|
'type': SCRoomMsgType.voiceRoomRocketRewardUser,
|
||||||
|
'data': {
|
||||||
|
'roomId': '9001',
|
||||||
|
'userId': '1001',
|
||||||
|
'userNickname': 'Winner',
|
||||||
|
'account': '223456',
|
||||||
|
'userAvatar': 'https://cdn.example.com/avatar.png',
|
||||||
|
'rewardType': 'GOLD',
|
||||||
|
'rewardCover': 'https://cdn.example.com/reward.png',
|
||||||
|
'rocketIconUrl': 'https://cdn.example.com/rocket.png',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(msg.type, SCRoomMsgType.voiceRoomRocketRewardUser);
|
||||||
|
expect(msg.groupId, '9001');
|
||||||
|
expect(msg.user?.id, '1001');
|
||||||
|
expect(msg.user?.userNickname, 'Winner');
|
||||||
|
expect(msg.user?.account, '223456');
|
||||||
|
expect(msg.user?.userAvatar, 'https://cdn.example.com/avatar.png');
|
||||||
|
expect(msg.role, 'GOLD');
|
||||||
|
expect(msg.msg, 'https://cdn.example.com/reward.png');
|
||||||
|
expect(msg.rocketIconUrl, 'https://cdn.example.com/rocket.png');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('rocket reward user nickname falls back to account', () {
|
||||||
|
final msg = Msg.fromJson({
|
||||||
|
'type': SCRoomMsgType.voiceRoomRocketRewardUser,
|
||||||
|
'userId': '1002',
|
||||||
|
'account': '334455',
|
||||||
|
'rewardType': 'BADGE',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(msg.user?.userNickname, '334455');
|
||||||
|
expect(msg.user?.id, '1002');
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:yumi/app/config/app_config.dart';
|
import 'package:yumi/app/config/app_config.dart';
|
||||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||||
import 'package:yumi/shared/data_sources/models/message/room_rocket_launch_broadcast_message.dart';
|
import 'package:yumi/shared/data_sources/models/message/room_rocket_launch_broadcast_message.dart';
|
||||||
@ -60,6 +61,19 @@ void main() {
|
|||||||
timer?.cancel();
|
timer?.cancel();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('maps top avatar frame from pag raw canvas using cover fit', () {
|
||||||
|
final rect = debugMapRoomRocketPagRawRectToCoverCanvas(
|
||||||
|
rawRect: const Rect.fromLTWH(291, 164, 168, 168),
|
||||||
|
rawSize: const Size(750, 1624),
|
||||||
|
canvasSize: const Size(392.7272727, 829.090909),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(rect.left, closeTo(152.38, 0.01));
|
||||||
|
expect(rect.top, closeTo(75.23, 0.01));
|
||||||
|
expect(rect.width, closeTo(87.97, 0.01));
|
||||||
|
expect(rect.height, closeTo(87.97, 0.01));
|
||||||
|
});
|
||||||
|
|
||||||
test('extracts launch pag urls from nested resource payloads', () {
|
test('extracts launch pag urls from nested resource payloads', () {
|
||||||
final launch = RoomRocketLaunchBroadcastMessage.fromJson({
|
final launch = RoomRocketLaunchBroadcastMessage.fromJson({
|
||||||
'rocketAnimationResource': {
|
'rocketAnimationResource': {
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.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_rocket_api_res.dart';
|
||||||
|
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_dialog.dart';
|
||||||
|
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_reward_dialog.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_reward_dialog_loader.dart';
|
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_reward_dialog_loader.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@ -27,6 +30,30 @@ void main() {
|
|||||||
expect(records.map((item) => item.rewardAmount), [5000, 5200]);
|
expect(records.map((item) => item.rewardAmount), [5000, 5200]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('room rocket reward dialog', () {
|
||||||
|
testWidgets('shows reward empty state when room winners exist', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: RoomRocketRewardDialog(
|
||||||
|
rewards: <RoomRocketRewardItem>[],
|
||||||
|
winners: <RoomRocketRewardWinner>[
|
||||||
|
RoomRocketRewardWinner(nickname: 'test man'),
|
||||||
|
],
|
||||||
|
loadingText: '',
|
||||||
|
useMockFallback: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.text('No reward'), findsOneWidget);
|
||||||
|
expect(find.text('test man'), findsOneWidget);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
SCRoomRocketRewardRecordRes _rewardRecord({
|
SCRoomRocketRewardRecordRes _rewardRecord({
|
||||||
|
|||||||
@ -122,6 +122,37 @@ void main() {
|
|||||||
expect(levels?.last.progressImageUrl, 'progress-2');
|
expect(levels?.last.progressImageUrl, 'progress-2');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('parses disabled rocket status from go api', () {
|
||||||
|
final status = SCRoomRocketStatusRes.fromJson({
|
||||||
|
'configured': true,
|
||||||
|
'enabled': false,
|
||||||
|
'status': 'DISABLED',
|
||||||
|
'roomId': 'room-1',
|
||||||
|
'levels': [],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(status.configured, isTrue);
|
||||||
|
expect(status.enabled, isFalse);
|
||||||
|
expect(status.isEnabled, isFalse);
|
||||||
|
expect(status.status, 'DISABLED');
|
||||||
|
expect(RoomRocketApiMapper.levelsFromStatus(status), isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('treats unconfigured or disabled-status rocket as unavailable', () {
|
||||||
|
final unconfigured = SCRoomRocketStatusRes.fromJson({
|
||||||
|
'configured': false,
|
||||||
|
'enabled': true,
|
||||||
|
'status': 'ENABLED',
|
||||||
|
});
|
||||||
|
final disabledStatus = SCRoomRocketStatusRes.fromJson({
|
||||||
|
'configured': true,
|
||||||
|
'status': 'DISABLED',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(unconfigured.isEnabled, isFalse);
|
||||||
|
expect(disabledStatus.isEnabled, isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'uses top-level reward preview for the response level instead of current level',
|
'uses top-level reward preview for the response level instead of current level',
|
||||||
() {
|
() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user