Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7baa3b48ca | |||
| 8b3e5cce35 | |||
|
|
02b4fafb3e | ||
|
|
76daf8e32c | ||
|
|
247e58e4b8 | ||
|
|
5edc117ed3 | ||
|
|
a44bdd7287 | ||
|
|
bae5ee44af | ||
|
|
3a6010001d | ||
|
|
e2a488cdf8 | ||
|
|
3d942de37b | ||
|
|
46b09eb51b | ||
|
|
085afce461 | ||
|
|
4b72a10cbf | ||
|
|
cd4957e6b5 | ||
|
|
b5bea97c2a | ||
|
|
3872dc03b1 | ||
|
|
2de2aa7891 | ||
|
|
f9b7c53142 | ||
|
|
142dcebaae | ||
|
|
8ac9590a8f | ||
|
|
2e9bb9bddc | ||
|
|
dc6795e00e | ||
|
|
e9ee3e9c02 | ||
|
|
7bce08a3b4 | ||
|
|
ef11d0f01b | ||
|
|
c190d95ec1 | ||
|
|
67baeb15f3 | ||
|
|
732e2bfcfb | ||
|
|
122bc5668d | ||
|
|
fddebaff69 | ||
|
|
fcb131f66c | ||
|
|
5501345a7e | ||
|
|
7e10b77d10 | ||
|
|
cedfc912cf | ||
|
|
0c4acb28e5 | ||
|
|
15eb93c810 | ||
|
|
8b05bb367a | ||
|
|
10d5e821f7 | ||
|
|
fac8f9fbab | ||
|
|
27c74abf7e | ||
|
|
07624ac105 | ||
|
|
a73e2ff14b | ||
|
|
5242ef0870 | ||
|
|
13d0d1ef84 | ||
|
|
85925d3607 | ||
|
|
352ca150a0 | ||
|
|
5f6bd6f5f8 | ||
|
|
de4a06be77 | ||
|
|
06b21e0156 | ||
|
|
22d688f6f1 | ||
|
|
bb9b92230b | ||
|
|
ad0fd72180 | ||
|
|
2e1a3037b6 | ||
|
|
9a11487059 | ||
|
|
c3ad3be817 | ||
|
|
9aa22dcdd8 | ||
|
|
4d86236227 | ||
|
|
b4c45e6400 | ||
|
|
3be68c17f8 | ||
|
|
8096ca6acc | ||
|
|
b045b3b784 | ||
|
|
3b777540a7 | ||
|
|
f025c18211 | ||
|
|
6eb7cb91df | ||
|
|
f8e13f31ba | ||
|
|
8f95b72fd0 | ||
|
|
bc3cbabfd1 | ||
|
|
1e7261927e | ||
|
|
e842c41736 | ||
|
|
44e10028b9 | ||
|
|
27f4e75cb8 | ||
|
|
6da7e28fbc | ||
|
|
71993f438b | ||
|
|
cbb811a18a | ||
|
|
d8ef488a28 | ||
|
|
0e2f4cf4bf | ||
|
|
2999de23d1 | ||
|
|
79e78cd897 | ||
|
|
2476e6d94c | ||
|
|
0a3ace948a |
@ -241,10 +241,11 @@
|
|||||||
"useCoupontips": "هل أنت متأكد أنك تريد استخدام القسيمة؟",
|
"useCoupontips": "هل أنت متأكد أنك تريد استخدام القسيمة؟",
|
||||||
"searchUserId": "ابحث عن معرف المستخدم",
|
"searchUserId": "ابحث عن معرف المستخدم",
|
||||||
"receiveSucc": "تم المطالبة بنجاح",
|
"receiveSucc": "تم المطالبة بنجاح",
|
||||||
"reject": "رفض",
|
"reject": "رفض",
|
||||||
"sendUser": "إرسال المستخدم",
|
"sendUser": "إرسال المستخدم",
|
||||||
"bio": "معلومات شخصية",
|
"bio": "معلومات شخصية",
|
||||||
"delete": "حذف",
|
"introduction": "مقدمة",
|
||||||
|
"delete": "حذف",
|
||||||
"sendCoupontips": "هل أنت متأكد أنك تريد إرسال هذه القسيمة لهذا المستخدم؟",
|
"sendCoupontips": "هل أنت متأكد أنك تريد إرسال هذه القسيمة لهذا المستخدم؟",
|
||||||
"recallThisMessage": "هل تريد استرجاع هذه الرسالة؟",
|
"recallThisMessage": "هل تريد استرجاع هذه الرسالة؟",
|
||||||
"copy": "نسخ",
|
"copy": "نسخ",
|
||||||
@ -255,9 +256,24 @@
|
|||||||
"signedin": "تم تسجيل الدخول",
|
"signedin": "تم تسجيل الدخول",
|
||||||
"following": "يتابع",
|
"following": "يتابع",
|
||||||
"inviteYouToBecomeBD": "ندعوك للانضمام كـ BD.",
|
"inviteYouToBecomeBD": "ندعوك للانضمام كـ BD.",
|
||||||
"confirmAcceptTheInvitation": "هل تريد تأكيد قبول الدعوة؟",
|
"confirmAcceptTheInvitation": "هل تريد تأكيد قبول الدعوة؟",
|
||||||
"confirmDeclineTheInvitation": "تأكيد رفض الدعوة؟",
|
"confirmDeclineTheInvitation": "تأكيد رفض الدعوة؟",
|
||||||
"friends": "أصدقاء",
|
"cpInviteRejectedByOther": "رفض الطرف الآخر أن يصبح {1}.",
|
||||||
|
"closeFriendInvitationTitle": "Invitation from a Close Friend",
|
||||||
|
"closeFriendInvitationSubtitle": "Click to view the invitation",
|
||||||
|
"closeFriendInvitationExpired": "The invitation to add a close friend has expired. This action cannot be performed.",
|
||||||
|
"closeFriendRequestDeclined": "Friend request declined",
|
||||||
|
"closeFriendsHaveBecome": "Have become close friends",
|
||||||
|
"otherAcceptedCloseFriendInvite": "The other party accepted your close friend invitation.",
|
||||||
|
"otherDeclinedCloseFriendInvite": "The other party declined your close friend invitation.",
|
||||||
|
"youAcceptedCloseFriendInvite": "You accepted the other party's close friend invitation.",
|
||||||
|
"youDeclinedCloseFriendInvite": "You declined the other party's close friend invitation.",
|
||||||
|
"closeFriendInviteTimedOut": "Close friend invitation timed out.",
|
||||||
|
"cpDismissConfirmMessage": "Removing this relationship will cost {1} coins. Confirm removal?",
|
||||||
|
"cpRelationCp": "CP",
|
||||||
|
"cpRelationBrother": "Brother",
|
||||||
|
"cpRelationSister": "Sister",
|
||||||
|
"friends": "أصدقاء",
|
||||||
"language": "اللغة",
|
"language": "اللغة",
|
||||||
"feedback": "ملاحظات",
|
"feedback": "ملاحظات",
|
||||||
"coupon": "قسيمة",
|
"coupon": "قسيمة",
|
||||||
@ -553,8 +569,9 @@
|
|||||||
"days": "أيام",
|
"days": "أيام",
|
||||||
"permanent": "دائم",
|
"permanent": "دائم",
|
||||||
"unFollow": "إلغاء المتابعة",
|
"unFollow": "إلغاء المتابعة",
|
||||||
"searchInputHint": "أدخل رقم الحساب / الغرفة",
|
"searchInputHint": "أدخل رقم الحساب / الغرفة",
|
||||||
"kickRoomTips": "لقد تم طردك من الغرفة",
|
"kickRoomTips": "لقد تم طردك من الغرفة",
|
||||||
|
"roomKickedEntryBlockedTips": "لقد أخرجك أحد المشرفين من الغرفة، ولا يمكنك الدخول مؤقتًا.",
|
||||||
"joinRoomTips": "انضم إلى الغرفة!",
|
"joinRoomTips": "انضم إلى الغرفة!",
|
||||||
"roomSetting": "إعدادات الغرفة",
|
"roomSetting": "إعدادات الغرفة",
|
||||||
"roomDetails": "تفاصيل الغرفة",
|
"roomDetails": "تفاصيل الغرفة",
|
||||||
|
|||||||
@ -247,6 +247,7 @@
|
|||||||
"delete": "মুছে ফেলুন",
|
"delete": "মুছে ফেলুন",
|
||||||
"copy": "কপি করুন",
|
"copy": "কপি করুন",
|
||||||
"bio": "জীবনী",
|
"bio": "জীবনী",
|
||||||
|
"introduction": "পরিচিতি",
|
||||||
"useCoupontips": "আপনি কি কুপন ব্যবহার করতে চান?",
|
"useCoupontips": "আপনি কি কুপন ব্যবহার করতে চান?",
|
||||||
"searchUserId": "ব্যবহারকারী ID সার্চ করুন",
|
"searchUserId": "ব্যবহারকারী ID সার্চ করুন",
|
||||||
"sendUser": "ব্যবহারকারীকে পাঠান",
|
"sendUser": "ব্যবহারকারীকে পাঠান",
|
||||||
@ -324,6 +325,21 @@
|
|||||||
"adminInviteRechargeAgent": "আপনাকে রিচার্জ এজেন্ট হতে আমন্ত্রণ জানাচ্ছি।",
|
"adminInviteRechargeAgent": "আপনাকে রিচার্জ এজেন্ট হতে আমন্ত্রণ জানাচ্ছি।",
|
||||||
"confirmAcceptTheInvitation": "আপনি কি আমন্ত্রণ গ্রহণ করতে নিশ্চিত করছেন?",
|
"confirmAcceptTheInvitation": "আপনি কি আমন্ত্রণ গ্রহণ করতে নিশ্চিত করছেন?",
|
||||||
"confirmDeclineTheInvitation": "আপনি কি আমন্ত্রণ প্রত্যাখ্যান করতে নিশ্চিত করছেন?",
|
"confirmDeclineTheInvitation": "আপনি কি আমন্ত্রণ প্রত্যাখ্যান করতে নিশ্চিত করছেন?",
|
||||||
|
"cpInviteRejectedByOther": "অন্য পক্ষ {1} হতে অস্বীকার করেছে।",
|
||||||
|
"closeFriendInvitationTitle": "Invitation from a Close Friend",
|
||||||
|
"closeFriendInvitationSubtitle": "Click to view the invitation",
|
||||||
|
"closeFriendInvitationExpired": "The invitation to add a close friend has expired. This action cannot be performed.",
|
||||||
|
"closeFriendRequestDeclined": "Friend request declined",
|
||||||
|
"closeFriendsHaveBecome": "Have become close friends",
|
||||||
|
"otherAcceptedCloseFriendInvite": "The other party accepted your close friend invitation.",
|
||||||
|
"otherDeclinedCloseFriendInvite": "The other party declined your close friend invitation.",
|
||||||
|
"youAcceptedCloseFriendInvite": "You accepted the other party's close friend invitation.",
|
||||||
|
"youDeclinedCloseFriendInvite": "You declined the other party's close friend invitation.",
|
||||||
|
"closeFriendInviteTimedOut": "Close friend invitation timed out.",
|
||||||
|
"cpDismissConfirmMessage": "Removing this relationship will cost {1} coins. Confirm removal?",
|
||||||
|
"cpRelationCp": "CP",
|
||||||
|
"cpRelationBrother": "Brother",
|
||||||
|
"cpRelationSister": "Sister",
|
||||||
"host": "হোস্ট",
|
"host": "হোস্ট",
|
||||||
"following": "ফলো করা হচ্ছে",
|
"following": "ফলো করা হচ্ছে",
|
||||||
"agent": "এজেন্ট",
|
"agent": "এজেন্ট",
|
||||||
@ -485,6 +501,7 @@
|
|||||||
"customized": "কাস্টমাইজড",
|
"customized": "কাস্টমাইজড",
|
||||||
"searchInputHint": "অ্যাকাউন্ট/রুম নম্বর লিখুন",
|
"searchInputHint": "অ্যাকাউন্ট/রুম নম্বর লিখুন",
|
||||||
"kickRoomTips": "আপনাকে রুম থেকে বের করা হয়েছে।",
|
"kickRoomTips": "আপনাকে রুম থেকে বের করা হয়েছে।",
|
||||||
|
"roomKickedEntryBlockedTips": "একজন অ্যাডমিন আপনাকে রুম থেকে সরিয়ে দিয়েছেন, আপাতত আপনি প্রবেশ করতে পারবেন না।",
|
||||||
"joinRoomTips": "রুমে যোগদান করেছেন !",
|
"joinRoomTips": "রুমে যোগদান করেছেন !",
|
||||||
"roomSetting": "রুম সেটিংস",
|
"roomSetting": "রুম সেটিংস",
|
||||||
"roomDetails": "রুম বিবরণ",
|
"roomDetails": "রুম বিবরণ",
|
||||||
|
|||||||
@ -217,6 +217,7 @@
|
|||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"copy": "Copy",
|
"copy": "Copy",
|
||||||
"bio": "Bio",
|
"bio": "Bio",
|
||||||
|
"introduction": "Introduction",
|
||||||
"useCoupontips": "Are you sure you want to use the coupon?",
|
"useCoupontips": "Are you sure you want to use the coupon?",
|
||||||
"searchUserId": "Search user's ID",
|
"searchUserId": "Search user's ID",
|
||||||
"sendUser": "Send User",
|
"sendUser": "Send User",
|
||||||
@ -294,6 +295,21 @@
|
|||||||
"adminInviteRechargeAgent": "Invite you to become a Recharge agent.",
|
"adminInviteRechargeAgent": "Invite you to become a Recharge agent.",
|
||||||
"confirmAcceptTheInvitation": "Confirm to accept the invitation?",
|
"confirmAcceptTheInvitation": "Confirm to accept the invitation?",
|
||||||
"confirmDeclineTheInvitation": "Confirm declining the invitation?",
|
"confirmDeclineTheInvitation": "Confirm declining the invitation?",
|
||||||
|
"cpInviteRejectedByOther": "The other party declined to become {1}.",
|
||||||
|
"closeFriendInvitationTitle": "Invitation from a Close Friend",
|
||||||
|
"closeFriendInvitationSubtitle": "Click to view the invitation",
|
||||||
|
"closeFriendInvitationExpired": "The invitation to add a close friend has expired. This action cannot be performed.",
|
||||||
|
"closeFriendRequestDeclined": "Friend request declined",
|
||||||
|
"closeFriendsHaveBecome": "Have become close friends",
|
||||||
|
"otherAcceptedCloseFriendInvite": "The other party accepted your close friend invitation.",
|
||||||
|
"otherDeclinedCloseFriendInvite": "The other party declined your close friend invitation.",
|
||||||
|
"youAcceptedCloseFriendInvite": "You accepted the other party's close friend invitation.",
|
||||||
|
"youDeclinedCloseFriendInvite": "You declined the other party's close friend invitation.",
|
||||||
|
"closeFriendInviteTimedOut": "Close friend invitation timed out.",
|
||||||
|
"cpDismissConfirmMessage": "Removing this relationship will cost {1} coins. Confirm removal?",
|
||||||
|
"cpRelationCp": "CP",
|
||||||
|
"cpRelationBrother": "Brother",
|
||||||
|
"cpRelationSister": "Sister",
|
||||||
"host": "Host",
|
"host": "Host",
|
||||||
"following": "Following",
|
"following": "Following",
|
||||||
"agent": "Agency",
|
"agent": "Agency",
|
||||||
@ -455,6 +471,7 @@
|
|||||||
"customized": "Customized",
|
"customized": "Customized",
|
||||||
"searchInputHint": "Enter account/room number",
|
"searchInputHint": "Enter account/room number",
|
||||||
"kickRoomTips": "You have been kicked out of the room.",
|
"kickRoomTips": "You have been kicked out of the room.",
|
||||||
|
"roomKickedEntryBlockedTips": "You have been removed from the room by an admin and cannot enter for now.",
|
||||||
"joinRoomTips": "Joined the room!",
|
"joinRoomTips": "Joined the room!",
|
||||||
"roomSetting": "Room Setting",
|
"roomSetting": "Room Setting",
|
||||||
"roomDetails": "Room Details",
|
"roomDetails": "Room Details",
|
||||||
@ -558,7 +575,7 @@
|
|||||||
"openTheMic": "Open the mic",
|
"openTheMic": "Open the mic",
|
||||||
"muteTheMic": "Mute the mic",
|
"muteTheMic": "Mute the mic",
|
||||||
"unlockTheMic": "Unlock the mic",
|
"unlockTheMic": "Unlock the mic",
|
||||||
"leavelTheMic": "Leave the mic",
|
"leavelTheMic": "Leave mic",
|
||||||
"lockTheMic": "Lock the mic",
|
"lockTheMic": "Lock the mic",
|
||||||
"removeTheMic": "Remove the mic",
|
"removeTheMic": "Remove the mic",
|
||||||
"inviteToTheMicrophone": "Invite to the microphone",
|
"inviteToTheMicrophone": "Invite to the microphone",
|
||||||
|
|||||||
@ -217,6 +217,7 @@
|
|||||||
"delete": "Supprimer",
|
"delete": "Supprimer",
|
||||||
"copy": "Copier",
|
"copy": "Copier",
|
||||||
"bio": "Biographie",
|
"bio": "Biographie",
|
||||||
|
"introduction": "Présentation",
|
||||||
"useCoupontips": "Êtes-vous sûr de vouloir utiliser le coupon ?",
|
"useCoupontips": "Êtes-vous sûr de vouloir utiliser le coupon ?",
|
||||||
"searchUserId": "Rechercher l'ID de l'utilisateur",
|
"searchUserId": "Rechercher l'ID de l'utilisateur",
|
||||||
"sendUser": "Envoyer l'utilisateur",
|
"sendUser": "Envoyer l'utilisateur",
|
||||||
@ -294,6 +295,21 @@
|
|||||||
"adminInviteRechargeAgent": "Vous invite à devenir agent Recharge.",
|
"adminInviteRechargeAgent": "Vous invite à devenir agent Recharge.",
|
||||||
"confirmAcceptTheInvitation": "Confirmer pour accepter l'invitation ?",
|
"confirmAcceptTheInvitation": "Confirmer pour accepter l'invitation ?",
|
||||||
"confirmDeclineTheInvitation": "Confirmer le refus de l'invitation ?",
|
"confirmDeclineTheInvitation": "Confirmer le refus de l'invitation ?",
|
||||||
|
"cpInviteRejectedByOther": "L'autre personne a refusé de devenir {1}.",
|
||||||
|
"closeFriendInvitationTitle": "Invitation from a Close Friend",
|
||||||
|
"closeFriendInvitationSubtitle": "Click to view the invitation",
|
||||||
|
"closeFriendInvitationExpired": "The invitation to add a close friend has expired. This action cannot be performed.",
|
||||||
|
"closeFriendRequestDeclined": "Friend request declined",
|
||||||
|
"closeFriendsHaveBecome": "Have become close friends",
|
||||||
|
"otherAcceptedCloseFriendInvite": "The other party accepted your close friend invitation.",
|
||||||
|
"otherDeclinedCloseFriendInvite": "The other party declined your close friend invitation.",
|
||||||
|
"youAcceptedCloseFriendInvite": "You accepted the other party's close friend invitation.",
|
||||||
|
"youDeclinedCloseFriendInvite": "You declined the other party's close friend invitation.",
|
||||||
|
"closeFriendInviteTimedOut": "Close friend invitation timed out.",
|
||||||
|
"cpDismissConfirmMessage": "Removing this relationship will cost {1} coins. Confirm removal?",
|
||||||
|
"cpRelationCp": "CP",
|
||||||
|
"cpRelationBrother": "Brother",
|
||||||
|
"cpRelationSister": "Sister",
|
||||||
"host": "Hôte",
|
"host": "Hôte",
|
||||||
"following": "Suite",
|
"following": "Suite",
|
||||||
"agent": "Agence",
|
"agent": "Agence",
|
||||||
@ -455,6 +471,7 @@
|
|||||||
"customized": "Personnalisé",
|
"customized": "Personnalisé",
|
||||||
"searchInputHint": "Entrez le numéro de compte/de chambre",
|
"searchInputHint": "Entrez le numéro de compte/de chambre",
|
||||||
"kickRoomTips": "Vous avez été expulsé de la pièce.",
|
"kickRoomTips": "Vous avez été expulsé de la pièce.",
|
||||||
|
"roomKickedEntryBlockedTips": "Un administrateur vous a retiré de la salle, vous ne pouvez pas y entrer pour le moment.",
|
||||||
"joinRoomTips": "J'ai rejoint la salle !",
|
"joinRoomTips": "J'ai rejoint la salle !",
|
||||||
"roomSetting": "Cadre de la pièce",
|
"roomSetting": "Cadre de la pièce",
|
||||||
"roomDetails": "Détails de la chambre",
|
"roomDetails": "Détails de la chambre",
|
||||||
|
|||||||
@ -217,6 +217,7 @@
|
|||||||
"delete": "Elimina",
|
"delete": "Elimina",
|
||||||
"copy": "Copia",
|
"copy": "Copia",
|
||||||
"bio": "Bio",
|
"bio": "Bio",
|
||||||
|
"introduction": "Introduzione",
|
||||||
"useCoupontips": "Sei sicuro di voler utilizzare il coupon?",
|
"useCoupontips": "Sei sicuro di voler utilizzare il coupon?",
|
||||||
"searchUserId": "Cerca l'ID dell'utente",
|
"searchUserId": "Cerca l'ID dell'utente",
|
||||||
"sendUser": "Invia utente",
|
"sendUser": "Invia utente",
|
||||||
@ -294,6 +295,21 @@
|
|||||||
"adminInviteRechargeAgent": "Invitati a diventare un agente Recharge.",
|
"adminInviteRechargeAgent": "Invitati a diventare un agente Recharge.",
|
||||||
"confirmAcceptTheInvitation": "Confermare per accettare l'invito?",
|
"confirmAcceptTheInvitation": "Confermare per accettare l'invito?",
|
||||||
"confirmDeclineTheInvitation": "Confermi di rifiutare l'invito?",
|
"confirmDeclineTheInvitation": "Confermi di rifiutare l'invito?",
|
||||||
|
"cpInviteRejectedByOther": "L'altra persona ha rifiutato di diventare {1}.",
|
||||||
|
"closeFriendInvitationTitle": "Invitation from a Close Friend",
|
||||||
|
"closeFriendInvitationSubtitle": "Click to view the invitation",
|
||||||
|
"closeFriendInvitationExpired": "The invitation to add a close friend has expired. This action cannot be performed.",
|
||||||
|
"closeFriendRequestDeclined": "Friend request declined",
|
||||||
|
"closeFriendsHaveBecome": "Have become close friends",
|
||||||
|
"otherAcceptedCloseFriendInvite": "The other party accepted your close friend invitation.",
|
||||||
|
"otherDeclinedCloseFriendInvite": "The other party declined your close friend invitation.",
|
||||||
|
"youAcceptedCloseFriendInvite": "You accepted the other party's close friend invitation.",
|
||||||
|
"youDeclinedCloseFriendInvite": "You declined the other party's close friend invitation.",
|
||||||
|
"closeFriendInviteTimedOut": "Close friend invitation timed out.",
|
||||||
|
"cpDismissConfirmMessage": "Removing this relationship will cost {1} coins. Confirm removal?",
|
||||||
|
"cpRelationCp": "CP",
|
||||||
|
"cpRelationBrother": "Brother",
|
||||||
|
"cpRelationSister": "Sister",
|
||||||
"host": "Ospite",
|
"host": "Ospite",
|
||||||
"following": "Segue",
|
"following": "Segue",
|
||||||
"agent": "Agenzia",
|
"agent": "Agenzia",
|
||||||
@ -455,6 +471,7 @@
|
|||||||
"customized": "Personalizzato",
|
"customized": "Personalizzato",
|
||||||
"searchInputHint": "Inserisci il numero di conto/stanza",
|
"searchInputHint": "Inserisci il numero di conto/stanza",
|
||||||
"kickRoomTips": "Sei stato cacciato dalla stanza.",
|
"kickRoomTips": "Sei stato cacciato dalla stanza.",
|
||||||
|
"roomKickedEntryBlockedTips": "Sei stato rimosso dalla stanza da un amministratore e al momento non puoi entrare.",
|
||||||
"joinRoomTips": "Entra nella stanza!",
|
"joinRoomTips": "Entra nella stanza!",
|
||||||
"roomSetting": "Impostazione della stanza",
|
"roomSetting": "Impostazione della stanza",
|
||||||
"roomDetails": "Dettagli della camera",
|
"roomDetails": "Dettagli della camera",
|
||||||
|
|||||||
@ -217,6 +217,7 @@
|
|||||||
"delete": "Excluir",
|
"delete": "Excluir",
|
||||||
"copy": "Copiar",
|
"copy": "Copiar",
|
||||||
"bio": "Biografia",
|
"bio": "Biografia",
|
||||||
|
"introduction": "Introdução",
|
||||||
"useCoupontips": "Tem certeza que deseja usar o cupom?",
|
"useCoupontips": "Tem certeza que deseja usar o cupom?",
|
||||||
"searchUserId": "Pesquisar ID do usuário",
|
"searchUserId": "Pesquisar ID do usuário",
|
||||||
"sendUser": "Enviar usuário",
|
"sendUser": "Enviar usuário",
|
||||||
@ -294,6 +295,21 @@
|
|||||||
"adminInviteRechargeAgent": "Convido você a se tornar um agente Recharge.",
|
"adminInviteRechargeAgent": "Convido você a se tornar um agente Recharge.",
|
||||||
"confirmAcceptTheInvitation": "Confirmar para aceitar o convite?",
|
"confirmAcceptTheInvitation": "Confirmar para aceitar o convite?",
|
||||||
"confirmDeclineTheInvitation": "Confirmar a recusa do convite?",
|
"confirmDeclineTheInvitation": "Confirmar a recusa do convite?",
|
||||||
|
"cpInviteRejectedByOther": "A outra pessoa recusou se tornar {1}.",
|
||||||
|
"closeFriendInvitationTitle": "Invitation from a Close Friend",
|
||||||
|
"closeFriendInvitationSubtitle": "Click to view the invitation",
|
||||||
|
"closeFriendInvitationExpired": "The invitation to add a close friend has expired. This action cannot be performed.",
|
||||||
|
"closeFriendRequestDeclined": "Friend request declined",
|
||||||
|
"closeFriendsHaveBecome": "Have become close friends",
|
||||||
|
"otherAcceptedCloseFriendInvite": "The other party accepted your close friend invitation.",
|
||||||
|
"otherDeclinedCloseFriendInvite": "The other party declined your close friend invitation.",
|
||||||
|
"youAcceptedCloseFriendInvite": "You accepted the other party's close friend invitation.",
|
||||||
|
"youDeclinedCloseFriendInvite": "You declined the other party's close friend invitation.",
|
||||||
|
"closeFriendInviteTimedOut": "Close friend invitation timed out.",
|
||||||
|
"cpDismissConfirmMessage": "Removing this relationship will cost {1} coins. Confirm removal?",
|
||||||
|
"cpRelationCp": "CP",
|
||||||
|
"cpRelationBrother": "Brother",
|
||||||
|
"cpRelationSister": "Sister",
|
||||||
"host": "Anfitrião",
|
"host": "Anfitrião",
|
||||||
"following": "Seguindo",
|
"following": "Seguindo",
|
||||||
"agent": "Agência",
|
"agent": "Agência",
|
||||||
@ -455,6 +471,7 @@
|
|||||||
"customized": "Personalizado",
|
"customized": "Personalizado",
|
||||||
"searchInputHint": "Insira o número da conta/quarto",
|
"searchInputHint": "Insira o número da conta/quarto",
|
||||||
"kickRoomTips": "Você foi expulso da sala.",
|
"kickRoomTips": "Você foi expulso da sala.",
|
||||||
|
"roomKickedEntryBlockedTips": "Você foi removido da sala por um administrador e não pode entrar por enquanto.",
|
||||||
"joinRoomTips": "Entrei na sala!",
|
"joinRoomTips": "Entrei na sala!",
|
||||||
"roomSetting": "Configuração da sala",
|
"roomSetting": "Configuração da sala",
|
||||||
"roomDetails": "Detalhes do quarto",
|
"roomDetails": "Detalhes do quarto",
|
||||||
|
|||||||
@ -203,10 +203,11 @@
|
|||||||
"settings": "Ayarlar",
|
"settings": "Ayarlar",
|
||||||
"account": "Hesap",
|
"account": "Hesap",
|
||||||
"common": "Genel",
|
"common": "Genel",
|
||||||
"delete": "Sil",
|
"delete": "Sil",
|
||||||
"copy": "Kopyala",
|
"copy": "Kopyala",
|
||||||
"bio": "Biyografi",
|
"bio": "Biyografi",
|
||||||
"useCoupontips": "Kuponu kullanmak istediğinizden emin misiniz?",
|
"introduction": "Tanıtım",
|
||||||
|
"useCoupontips": "Kuponu kullanmak istediğinizden emin misiniz?",
|
||||||
"searchUserId": "Kullanıcı ID'sini Ara",
|
"searchUserId": "Kullanıcı ID'sini Ara",
|
||||||
"sendUser": "Kullanıcıya Gönder",
|
"sendUser": "Kullanıcıya Gönder",
|
||||||
"hobby": "Hobi",
|
"hobby": "Hobi",
|
||||||
@ -291,9 +292,24 @@
|
|||||||
"noMatchedCP": "Eşleşen CP yok",
|
"noMatchedCP": "Eşleşen CP yok",
|
||||||
"inviteYouToBecomeBD": "Sizi BD yapmaya davet ediyoruz.",
|
"inviteYouToBecomeBD": "Sizi BD yapmaya davet ediyoruz.",
|
||||||
"adminInviteRechargeAgent": "Sizi Yükleme temsilcisi yapmaya davet ediyoruz.",
|
"adminInviteRechargeAgent": "Sizi Yükleme temsilcisi yapmaya davet ediyoruz.",
|
||||||
"confirmAcceptTheInvitation": "Daveti kabul etmek için onaylıyor musunuz?",
|
"confirmAcceptTheInvitation": "Daveti kabul etmek için onaylıyor musunuz?",
|
||||||
"confirmDeclineTheInvitation": "Daveti reddetmek için onaylıyor musunuz?",
|
"confirmDeclineTheInvitation": "Daveti reddetmek için onaylıyor musunuz?",
|
||||||
"host": "Sunucu",
|
"cpInviteRejectedByOther": "Karşı taraf {1} olmayı reddetti.",
|
||||||
|
"closeFriendInvitationTitle": "Invitation from a Close Friend",
|
||||||
|
"closeFriendInvitationSubtitle": "Click to view the invitation",
|
||||||
|
"closeFriendInvitationExpired": "The invitation to add a close friend has expired. This action cannot be performed.",
|
||||||
|
"closeFriendRequestDeclined": "Friend request declined",
|
||||||
|
"closeFriendsHaveBecome": "Have become close friends",
|
||||||
|
"otherAcceptedCloseFriendInvite": "The other party accepted your close friend invitation.",
|
||||||
|
"otherDeclinedCloseFriendInvite": "The other party declined your close friend invitation.",
|
||||||
|
"youAcceptedCloseFriendInvite": "You accepted the other party's close friend invitation.",
|
||||||
|
"youDeclinedCloseFriendInvite": "You declined the other party's close friend invitation.",
|
||||||
|
"closeFriendInviteTimedOut": "Close friend invitation timed out.",
|
||||||
|
"cpDismissConfirmMessage": "Removing this relationship will cost {1} coins. Confirm removal?",
|
||||||
|
"cpRelationCp": "CP",
|
||||||
|
"cpRelationBrother": "Brother",
|
||||||
|
"cpRelationSister": "Sister",
|
||||||
|
"host": "Sunucu",
|
||||||
"following": "Takip Edilenler",
|
"following": "Takip Edilenler",
|
||||||
"agent": "Temsilcilik",
|
"agent": "Temsilcilik",
|
||||||
"approved": "Onaylandı",
|
"approved": "Onaylandı",
|
||||||
@ -452,9 +468,10 @@
|
|||||||
"message": "Mesaj",
|
"message": "Mesaj",
|
||||||
"clearCache": "Önbelleği Temizle",
|
"clearCache": "Önbelleği Temizle",
|
||||||
"customized": "Özel",
|
"customized": "Özel",
|
||||||
"searchInputHint": "Hesap/oda numarasını girin",
|
"searchInputHint": "Hesap/oda numarasını girin",
|
||||||
"kickRoomTips": "Odadan atıldınız.",
|
"kickRoomTips": "Odadan atıldınız.",
|
||||||
"joinRoomTips": "odaya katıldı !",
|
"roomKickedEntryBlockedTips": "Bir yönetici tarafından odadan çıkarıldınız, şu anda giremezsiniz.",
|
||||||
|
"joinRoomTips": "odaya katıldı !",
|
||||||
"roomSetting": "Oda Ayarları",
|
"roomSetting": "Oda Ayarları",
|
||||||
"roomDetails": "Oda Detayları",
|
"roomDetails": "Oda Detayları",
|
||||||
"systemRoomTips": "Nezaketli ve saygılı olun. yumi'de pornografik veya uygunsuz içerik kesinlikle yasaktır. Tespit edilmesi halinde hesap kalıcı olarak engellenecektir. Lütfen yumi platform kurallarına bilinçli şekilde uyun.",
|
"systemRoomTips": "Nezaketli ve saygılı olun. yumi'de pornografik veya uygunsuz içerik kesinlikle yasaktır. Tespit edilmesi halinde hesap kalıcı olarak engellenecektir. Lütfen yumi platform kurallarına bilinçli şekilde uyun.",
|
||||||
|
|||||||
22
ios/ExportOptions.plist
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>destination</key>
|
||||||
|
<string>export</string>
|
||||||
|
<key>distributionBundleIdentifier</key>
|
||||||
|
<string>com.org.yumiparty</string>
|
||||||
|
<key>manageAppVersionAndBuildNumber</key>
|
||||||
|
<false/>
|
||||||
|
<key>method</key>
|
||||||
|
<string>app-store-connect</string>
|
||||||
|
<key>signingStyle</key>
|
||||||
|
<string>automatic</string>
|
||||||
|
<key>stripSwiftSymbols</key>
|
||||||
|
<true/>
|
||||||
|
<key>teamID</key>
|
||||||
|
<string>S9YG87C297</string>
|
||||||
|
<key>uploadSymbols</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -1,5 +1,5 @@
|
|||||||
# Uncomment this line to define a global platform for your project
|
# Uncomment this line to define a global platform for your project
|
||||||
# platform :ios, '13.0'
|
platform :ios, '13.0'
|
||||||
|
|
||||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
@ -64,7 +64,7 @@ post_install do |installer|
|
|||||||
## 6. 禁用 Siri/助手 (NSSiriUsageDescription)
|
## 6. 禁用 Siri/助手 (NSSiriUsageDescription)
|
||||||
'PERMISSION_ASSISTANT=0',
|
'PERMISSION_ASSISTANT=0',
|
||||||
|
|
||||||
## 7. 禁用 媒体库 (NSAppleMusicUsageDescription)
|
## 7. 启用 媒体库 (NSAppleMusicUsageDescription)
|
||||||
'PERMISSION_MEDIA_LIBRARY=1',
|
'PERMISSION_MEDIA_LIBRARY=1',
|
||||||
|
|
||||||
## 8. 禁用 蓝牙 (如果你没用到蓝牙,建议也关掉)
|
## 8. 禁用 蓝牙 (如果你没用到蓝牙,建议也关掉)
|
||||||
|
|||||||
@ -11,11 +11,47 @@ PODS:
|
|||||||
- GoogleUtilities/Environment (~> 8.0)
|
- GoogleUtilities/Environment (~> 8.0)
|
||||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||||
- PromisesObjC (~> 2.4)
|
- PromisesObjC (~> 2.4)
|
||||||
|
- audio_session (0.0.1):
|
||||||
|
- Flutter
|
||||||
- audioplayers_darwin (0.0.1):
|
- audioplayers_darwin (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- device_info_plus (0.0.1):
|
- device_info_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- DKImagePickerController/Core (4.3.9):
|
||||||
|
- DKImagePickerController/ImageDataManager
|
||||||
|
- DKImagePickerController/Resource
|
||||||
|
- DKImagePickerController/ImageDataManager (4.3.9)
|
||||||
|
- DKImagePickerController/PhotoGallery (4.3.9):
|
||||||
|
- DKImagePickerController/Core
|
||||||
|
- DKPhotoGallery
|
||||||
|
- DKImagePickerController/Resource (4.3.9)
|
||||||
|
- DKPhotoGallery (0.0.19):
|
||||||
|
- DKPhotoGallery/Core (= 0.0.19)
|
||||||
|
- DKPhotoGallery/Model (= 0.0.19)
|
||||||
|
- DKPhotoGallery/Preview (= 0.0.19)
|
||||||
|
- DKPhotoGallery/Resource (= 0.0.19)
|
||||||
|
- SDWebImage
|
||||||
|
- SwiftyGif
|
||||||
|
- DKPhotoGallery/Core (0.0.19):
|
||||||
|
- DKPhotoGallery/Model
|
||||||
|
- DKPhotoGallery/Preview
|
||||||
|
- SDWebImage
|
||||||
|
- SwiftyGif
|
||||||
|
- DKPhotoGallery/Model (0.0.19):
|
||||||
|
- SDWebImage
|
||||||
|
- SwiftyGif
|
||||||
|
- DKPhotoGallery/Preview (0.0.19):
|
||||||
|
- DKPhotoGallery/Model
|
||||||
|
- DKPhotoGallery/Resource
|
||||||
|
- SDWebImage
|
||||||
|
- SwiftyGif
|
||||||
|
- DKPhotoGallery/Resource (0.0.19):
|
||||||
|
- SDWebImage
|
||||||
|
- SwiftyGif
|
||||||
|
- file_picker (0.0.1):
|
||||||
|
- DKImagePickerController/PhotoGallery
|
||||||
|
- Flutter
|
||||||
- Firebase/Auth (11.15.0):
|
- Firebase/Auth (11.15.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseAuth (~> 11.15.0)
|
- FirebaseAuth (~> 11.15.0)
|
||||||
@ -141,6 +177,9 @@ PODS:
|
|||||||
- in_app_purchase_storekit (0.0.1):
|
- in_app_purchase_storekit (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- just_audio (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- libpag (4.5.52)
|
- libpag (4.5.52)
|
||||||
- libwebp (1.5.0):
|
- libwebp (1.5.0):
|
||||||
- libwebp/demux (= 1.5.0)
|
- libwebp/demux (= 1.5.0)
|
||||||
@ -170,6 +209,9 @@ PODS:
|
|||||||
- pag (0.0.1):
|
- pag (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- libpag
|
- libpag
|
||||||
|
- path_provider_foundation (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- permission_handler_apple (9.3.0):
|
- permission_handler_apple (9.3.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- PromisesObjC (2.4.0)
|
- PromisesObjC (2.4.0)
|
||||||
@ -191,6 +233,7 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- SwiftyBeaver (1.9.5)
|
- SwiftyBeaver (1.9.5)
|
||||||
|
- SwiftyGif (5.4.5)
|
||||||
- tancent_vap (0.0.1):
|
- tancent_vap (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- tencent_cloud_chat_sdk (8.0.0):
|
- tencent_cloud_chat_sdk (8.0.0):
|
||||||
@ -223,8 +266,10 @@ PODS:
|
|||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- app_links (from `.symlinks/plugins/app_links/ios`)
|
- app_links (from `.symlinks/plugins/app_links/ios`)
|
||||||
|
- audio_session (from `.symlinks/plugins/audio_session/ios`)
|
||||||
- audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/darwin`)
|
- audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/darwin`)
|
||||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
|
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||||
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
|
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
|
||||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||||
- firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`)
|
- firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`)
|
||||||
@ -235,9 +280,11 @@ DEPENDENCIES:
|
|||||||
- image_cropper (from `.symlinks/plugins/image_cropper/ios`)
|
- image_cropper (from `.symlinks/plugins/image_cropper/ios`)
|
||||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
- in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/darwin`)
|
- in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/darwin`)
|
||||||
|
- just_audio (from `.symlinks/plugins/just_audio/darwin`)
|
||||||
- on_audio_query_ios (from `.symlinks/plugins/on_audio_query_ios/ios`)
|
- on_audio_query_ios (from `.symlinks/plugins/on_audio_query_ios/ios`)
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- pag (from `.symlinks/plugins/pag/ios`)
|
- pag (from `.symlinks/plugins/pag/ios`)
|
||||||
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`)
|
- sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`)
|
||||||
@ -255,6 +302,8 @@ SPEC REPOS:
|
|||||||
trunk:
|
trunk:
|
||||||
- AppAuth
|
- AppAuth
|
||||||
- AppCheckCore
|
- AppCheckCore
|
||||||
|
- DKImagePickerController
|
||||||
|
- DKPhotoGallery
|
||||||
- Firebase
|
- Firebase
|
||||||
- FirebaseAppCheckInterop
|
- FirebaseAppCheckInterop
|
||||||
- FirebaseAuth
|
- FirebaseAuth
|
||||||
@ -282,6 +331,7 @@ SPEC REPOS:
|
|||||||
- SDWebImage
|
- SDWebImage
|
||||||
- SDWebImageWebPCoder
|
- SDWebImageWebPCoder
|
||||||
- SwiftyBeaver
|
- SwiftyBeaver
|
||||||
|
- SwiftyGif
|
||||||
- TOCropViewController
|
- TOCropViewController
|
||||||
- TXCustomBeautyProcesserPlugin
|
- TXCustomBeautyProcesserPlugin
|
||||||
- TXIMSDK_Plus_iOS_XCFramework
|
- TXIMSDK_Plus_iOS_XCFramework
|
||||||
@ -290,10 +340,14 @@ SPEC REPOS:
|
|||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
app_links:
|
app_links:
|
||||||
:path: ".symlinks/plugins/app_links/ios"
|
:path: ".symlinks/plugins/app_links/ios"
|
||||||
|
audio_session:
|
||||||
|
:path: ".symlinks/plugins/audio_session/ios"
|
||||||
audioplayers_darwin:
|
audioplayers_darwin:
|
||||||
:path: ".symlinks/plugins/audioplayers_darwin/darwin"
|
:path: ".symlinks/plugins/audioplayers_darwin/darwin"
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||||
|
file_picker:
|
||||||
|
:path: ".symlinks/plugins/file_picker/ios"
|
||||||
firebase_auth:
|
firebase_auth:
|
||||||
:path: ".symlinks/plugins/firebase_auth/ios"
|
:path: ".symlinks/plugins/firebase_auth/ios"
|
||||||
firebase_core:
|
firebase_core:
|
||||||
@ -314,12 +368,16 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||||
in_app_purchase_storekit:
|
in_app_purchase_storekit:
|
||||||
:path: ".symlinks/plugins/in_app_purchase_storekit/darwin"
|
:path: ".symlinks/plugins/in_app_purchase_storekit/darwin"
|
||||||
|
just_audio:
|
||||||
|
:path: ".symlinks/plugins/just_audio/darwin"
|
||||||
on_audio_query_ios:
|
on_audio_query_ios:
|
||||||
:path: ".symlinks/plugins/on_audio_query_ios/ios"
|
:path: ".symlinks/plugins/on_audio_query_ios/ios"
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
pag:
|
pag:
|
||||||
:path: ".symlinks/plugins/pag/ios"
|
:path: ".symlinks/plugins/pag/ios"
|
||||||
|
path_provider_foundation:
|
||||||
|
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||||
permission_handler_apple:
|
permission_handler_apple:
|
||||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
@ -349,8 +407,12 @@ SPEC CHECKSUMS:
|
|||||||
app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a
|
app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a
|
||||||
AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73
|
AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73
|
||||||
AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
|
AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
|
||||||
|
audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
|
||||||
audioplayers_darwin: 835ced6edd4c9fc8ebb0a7cc9e294a91d99917d5
|
audioplayers_darwin: 835ced6edd4c9fc8ebb0a7cc9e294a91d99917d5
|
||||||
device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896
|
device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896
|
||||||
|
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||||
|
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||||
|
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||||
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
|
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
|
||||||
firebase_auth: 50af8366c87bb88c80ebeae62eb60189c7246b9b
|
firebase_auth: 50af8366c87bb88c80ebeae62eb60189c7246b9b
|
||||||
firebase_core: 995454a784ff288be5689b796deb9e9fa3601818
|
firebase_core: 995454a784ff288be5689b796deb9e9fa3601818
|
||||||
@ -378,6 +440,7 @@ SPEC CHECKSUMS:
|
|||||||
image_cropper: 655b3ba703c9e15e3111e79151624d6154288774
|
image_cropper: 655b3ba703c9e15e3111e79151624d6154288774
|
||||||
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
|
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
|
||||||
in_app_purchase_storekit: 22cca7d08eebca9babdf4d07d0baccb73325d3c8
|
in_app_purchase_storekit: 22cca7d08eebca9babdf4d07d0baccb73325d3c8
|
||||||
|
just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed
|
||||||
libpag: 98742fad4b3ac2a2ee31e383d2483495fb9a5fb4
|
libpag: 98742fad4b3ac2a2ee31e383d2483495fb9a5fb4
|
||||||
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
|
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
|
||||||
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
|
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
|
||||||
@ -385,6 +448,7 @@ SPEC CHECKSUMS:
|
|||||||
on_audio_query_ios: 28a780e2d0d85d92d500ba6e12c6c8167022b2fa
|
on_audio_query_ios: 28a780e2d0d85d92d500ba6e12c6c8167022b2fa
|
||||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||||
pag: f01aa9017ab0e04a83ba4a20d6070a50f0ac9da8
|
pag: f01aa9017ab0e04a83ba4a20d6070a50f0ac9da8
|
||||||
|
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||||
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
|
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
|
||||||
@ -395,6 +459,7 @@ SPEC CHECKSUMS:
|
|||||||
sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418
|
sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
SwiftyBeaver: 84069991dd5dca07d7069100985badaca7f0ce82
|
SwiftyBeaver: 84069991dd5dca07d7069100985badaca7f0ce82
|
||||||
|
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||||
tancent_vap: fa8ad93814a9f950514a7074c662d2c937084c68
|
tancent_vap: fa8ad93814a9f950514a7074c662d2c937084c68
|
||||||
tencent_cloud_chat_sdk: 55e5fffe20f6b7937a26a674ccccb639563a9790
|
tencent_cloud_chat_sdk: 55e5fffe20f6b7937a26a674ccccb639563a9790
|
||||||
tencent_rtc_sdk: f77558b6b436a149d378557c2a837f73c09061bc
|
tencent_rtc_sdk: f77558b6b436a149d378557c2a837f73c09061bc
|
||||||
@ -403,11 +468,11 @@ SPEC CHECKSUMS:
|
|||||||
TXIMSDK_Plus_iOS_XCFramework: 5d1933192fb3b7ef2fe933f1623de4a0486a7fe2
|
TXIMSDK_Plus_iOS_XCFramework: 5d1933192fb3b7ef2fe933f1623de4a0486a7fe2
|
||||||
TXLiteAVSDK_Professional: e792e6084a208ecef6196c5f28237ad8078b4d7c
|
TXLiteAVSDK_Professional: e792e6084a208ecef6196c5f28237ad8078b4d7c
|
||||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||||
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
|
video_player_avfoundation: 3453f792138786248960ca029747fcd9f318ef52
|
||||||
video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140
|
video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140
|
||||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||||
webview_flutter_wkwebview: 8ebf4fded22593026f7dbff1fbff31ea98573c8d
|
webview_flutter_wkwebview: 8ebf4fded22593026f7dbff1fbff31ea98573c8d
|
||||||
|
|
||||||
PODFILE CHECKSUM: a6f49a93e5f85201a2efdadcd7cf184b0e310894
|
PODFILE CHECKSUM: b724feb0f01aec7228c34cc4763485e911aa9322
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
B7A1D58E2EE51A0000000001 /* GiftMp4VideoPlatformView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A1D58F2EE51A0000000002 /* GiftMp4VideoPlatformView.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -70,6 +71,7 @@
|
|||||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
B7A1D58F2EE51A0000000002 /* GiftMp4VideoPlatformView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GiftMp4VideoPlatformView.swift; sourceTree = "<group>"; };
|
||||||
D8CD6B2D6477A38E56A72C61 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
D8CD6B2D6477A38E56A72C61 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
@ -170,6 +172,7 @@
|
|||||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
|
B7A1D58F2EE51A0000000002 /* GiftMp4VideoPlatformView.swift */,
|
||||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
);
|
);
|
||||||
path = Runner;
|
path = Runner;
|
||||||
@ -210,6 +213,7 @@
|
|||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
F1AFD9E9E8DBA4F1609CE22F /* [CP] Embed Pods Frameworks */,
|
F1AFD9E9E8DBA4F1609CE22F /* [CP] Embed Pods Frameworks */,
|
||||||
5DF67264ADC5268AC3021CED /* [CP] Copy Pods Resources */,
|
5DF67264ADC5268AC3021CED /* [CP] Copy Pods Resources */,
|
||||||
|
B7A1D5902EE51A0000000003 /* Upload Crashlytics Symbols */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@ -352,6 +356,22 @@
|
|||||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
showEnvVarsInLog = 0;
|
showEnvVarsInLog = 0;
|
||||||
};
|
};
|
||||||
|
B7A1D5902EE51A0000000003 /* Upload Crashlytics Symbols */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}",
|
||||||
|
"${PROJECT_DIR}/Runner/GoogleService-Info.plist",
|
||||||
|
);
|
||||||
|
name = "Upload Crashlytics Symbols";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "if [ \"$CONFIGURATION\" = \"Release\" ] || [ \"$CONFIGURATION\" = \"Profile\" ]; then\n if [ -x \"${PODS_ROOT}/FirebaseCrashlytics/upload-symbols\" ]; then\n \"${PODS_ROOT}/FirebaseCrashlytics/upload-symbols\" -gsp \"${PROJECT_DIR}/Runner/GoogleService-Info.plist\" -p ios \"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}\"\n else\n echo \"warning: FirebaseCrashlytics upload-symbols not found at ${PODS_ROOT}/FirebaseCrashlytics/upload-symbols\"\n fi\nfi\n";
|
||||||
|
};
|
||||||
CFD1FCCAE92AFBF15E8B607E /* [CP] Check Pods Manifest.lock */ = {
|
CFD1FCCAE92AFBF15E8B607E /* [CP] Check Pods Manifest.lock */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@ -407,6 +427,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
|
B7A1D58E2EE51A0000000001 /* GiftMp4VideoPlatformView.swift in Sources */,
|
||||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@ -489,7 +510,7 @@
|
|||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
};
|
};
|
||||||
name = Profile;
|
name = Profile;
|
||||||
@ -498,12 +519,13 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPat_icon_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 140;
|
CURRENT_PROJECT_VERSION = 102;
|
||||||
DEVELOPMENT_TEAM = S9X2AJ2US9;
|
DEVELOPMENT_TEAM = S9YG87C297;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
@ -511,7 +533,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.4.0;
|
MARKETING_VERSION = 1.0.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@ -625,7 +647,7 @@
|
|||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
@ -679,7 +701,7 @@
|
|||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
@ -688,13 +710,13 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPat_icon_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 140;
|
CURRENT_PROJECT_VERSION = 102;
|
||||||
DEVELOPMENT_TEAM = F33K8VUZ62;
|
DEVELOPMENT_TEAM = S9YG87C297;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
@ -702,7 +724,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.4.0;
|
MARKETING_VERSION = 1.0.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@ -717,13 +739,13 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPat_icon_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 140;
|
CURRENT_PROJECT_VERSION = 102;
|
||||||
DEVELOPMENT_TEAM = F33K8VUZ62;
|
DEVELOPMENT_TEAM = S9YG87C297;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
@ -731,7 +753,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.4.0;
|
MARKETING_VERSION = 1.0.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
|||||||
@ -9,11 +9,23 @@ import UIKit
|
|||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
registerGiftMp4VideoView()
|
||||||
registerDurableAuthStorageChannel()
|
registerDurableAuthStorageChannel()
|
||||||
registerMobileContextChannel()
|
registerMobileContextChannel()
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func registerGiftMp4VideoView() {
|
||||||
|
guard let registrar = self.registrar(forPlugin: "GiftMp4VideoViewPlugin") else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
GiftMp4VideoPlatformView.installControlChannel(messenger: registrar.messenger())
|
||||||
|
registrar.register(
|
||||||
|
GiftMp4VideoViewFactory(messenger: registrar.messenger()),
|
||||||
|
withId: GiftMp4VideoPlatformView.viewType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private func registerMobileContextChannel() {
|
private func registerMobileContextChannel() {
|
||||||
guard let controller = window?.rootViewController as? FlutterViewController else {
|
guard let controller = window?.rootViewController as? FlutterViewController else {
|
||||||
return
|
return
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 58 KiB |
23
ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "LaunchBackground.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "LaunchBackground@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "LaunchBackground@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
ios/Runner/Assets.xcassets/LaunchBackground.imageset/LaunchBackground.png
vendored
Normal file
|
After Width: | Height: | Size: 187 KiB |
BIN
ios/Runner/Assets.xcassets/LaunchBackground.imageset/LaunchBackground@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 811 KiB |
BIN
ios/Runner/Assets.xcassets/LaunchBackground.imageset/LaunchBackground@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 371 KiB |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 187 KiB |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 811 KiB |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 371 KiB |
23
ios/Runner/Assets.xcassets/LaunchLogo.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "LaunchLogo.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "LaunchLogo@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "LaunchLogo@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
ios/Runner/Assets.xcassets/LaunchLogo.imageset/LaunchLogo.png
vendored
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
ios/Runner/Assets.xcassets/LaunchLogo.imageset/LaunchLogo@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
ios/Runner/Assets.xcassets/LaunchLogo.imageset/LaunchLogo@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 108 KiB |
@ -15,15 +15,25 @@
|
|||||||
</layoutGuides>
|
</layoutGuides>
|
||||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleAspectFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="BGD-k0-Ey4">
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleAspectFit" image="LaunchLogo" translatesAutoresizingMaskIntoConstraints="NO" id="LOG-k0-Ey4">
|
||||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<constraints>
|
||||||
<constraints>
|
<constraint firstAttribute="width" constant="107" id="LOG-width-107"/>
|
||||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
<constraint firstAttribute="height" constant="97" id="LOG-height-97"/>
|
||||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
</constraints>
|
||||||
</constraints>
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" red="0" green="0" blue="0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="BGD-k0-Ey4" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="BGD-top"/>
|
||||||
|
<constraint firstItem="BGD-k0-Ey4" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="BGD-leading"/>
|
||||||
|
<constraint firstItem="BGD-k0-Ey4" firstAttribute="trailing" secondItem="Ze5-6b-2t3" secondAttribute="trailing" id="BGD-trailing"/>
|
||||||
|
<constraint firstItem="BGD-k0-Ey4" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="BGD-bottom"/>
|
||||||
|
<constraint firstItem="LOG-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="LOG-centerX"/>
|
||||||
|
<constraint firstItem="LOG-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="LOG-centerY"/>
|
||||||
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
</viewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
@ -32,6 +42,7 @@
|
|||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="LaunchImage" width="168" height="185"/>
|
<image name="LaunchBackground" width="375" height="812"/>
|
||||||
|
<image name="LaunchLogo" width="107" height="97"/>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
1184
ios/Runner/GiftMp4VideoPlatformView.swift
Normal file
@ -35,23 +35,39 @@
|
|||||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleTypeRole</key>
|
<key>CFBundleURLName</key>
|
||||||
<string>Editor</string>
|
<string>com.googleusercontent.apps.52796773508-dk441c9e7tdmqv47mbdt2vpbn2clrii8</string>
|
||||||
</dict>
|
<key>CFBundleURLSchemes</key>
|
||||||
</array>
|
<array>
|
||||||
<key>CFBundleVersion</key>
|
<string>com.googleusercontent.apps.52796773508-dk441c9e7tdmqv47mbdt2vpbn2clrii8</string>
|
||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
</array>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>com.org.yumiparty</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>yumi</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>We need access to your camera so you can take a photo and make it a profile picture, or take and share a picture in real time in a voice chat room.</string>
|
<string>We need access to your camera so you can take a photo and make it a profile picture, or take and share a picture in real time in a voice chat room.</string>
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
<string>We need access to your microphone so that you can make live calls, record, and send voice messages with other users in the voice dating room.</string>
|
<string>We need access to your microphone so that you can make live calls, record, and send voice messages with other users in the voice dating room.</string>
|
||||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
<key>NSAppleMusicUsageDescription</key>
|
||||||
<string>We need your permission to save beautiful images you take or edit in the app to your phone album.</string>
|
<string>We need access to your media library so you can select local music to play in voice rooms.</string>
|
||||||
|
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||||
|
<string>We need your permission to save beautiful images you take or edit in the app to your phone album.</string>
|
||||||
<key>NSPhotoLibraryUsageDescription</key>
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
<string>We need access to your photo album so you can choose photos from it to update your profile picture or share moments from your life in chat.</string>
|
<string>We need access to your photo album so you can choose photos from it to update your profile picture or share moments from your life in chat.</string>
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
@ -77,9 +93,5 @@
|
|||||||
<array>
|
<array>
|
||||||
<string>audio</string>
|
<string>audio</string>
|
||||||
</array>
|
</array>
|
||||||
<key>com.apple.developer.applesignin</key>
|
</dict>
|
||||||
<array>
|
</plist>
|
||||||
<string>Default</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
|
|||||||
@ -2,9 +2,13 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.developer.applesignin</key>
|
<key>com.apple.developer.applesignin</key>
|
||||||
<array>
|
<array>
|
||||||
<string>Default</string>
|
<string>Default</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
<key>com.apple.developer.associated-domains</key>
|
||||||
</plist>
|
<array>
|
||||||
|
<string>applinks:h5.yumi.com</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|||||||
@ -2,9 +2,13 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.developer.applesignin</key>
|
<key>com.apple.developer.applesignin</key>
|
||||||
<array>
|
<array>
|
||||||
<string>Default</string>
|
<string>Default</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
<key>com.apple.developer.associated-domains</key>
|
||||||
</plist>
|
<array>
|
||||||
|
<string>applinks:h5.yumi.com</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|||||||
@ -16,8 +16,8 @@ class SCVariant1Config implements AppConfig {
|
|||||||
@override
|
@override
|
||||||
String get apiHost => const String.fromEnvironment(
|
String get apiHost => const String.fromEnvironment(
|
||||||
'API_HOST',
|
'API_HOST',
|
||||||
defaultValue: 'http://192.168.110.66:1100/',
|
defaultValue: 'http://192.168.110.64:1100/',
|
||||||
); // 默认连线上环境,本地调试可通过 --dart-define=API_HOST 覆盖 本地“http://192.168.110.66:1100/”,线上“https://jvapi.haiyihy.com/”
|
); // 默认连本地接口,本地调试可通过 --dart-define=API_HOST 覆盖,线上“https://jvapi.haiyihy.com/”
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get imgHost => 'https://img.atuchat.com/'; // 测试图片服务器,上架前需替换为正式域名
|
String get imgHost => 'https://img.atuchat.com/'; // 测试图片服务器,上架前需替换为正式域名
|
||||||
|
|||||||
@ -168,8 +168,7 @@ class SCGlobalConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static String clampFloatingBroadcastScope(String value) {
|
static String clampFloatingBroadcastScope(String value) {
|
||||||
final scope = normalizeFloatingBroadcastScope(value);
|
return normalizeFloatingBroadcastScope(value);
|
||||||
return _isLowPerformanceDevice ? floatingBroadcastScopeOff : scope;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static String floatingBroadcastScopeStorageKey(String? account) {
|
static String floatingBroadcastScopeStorageKey(String? account) {
|
||||||
@ -188,8 +187,7 @@ class SCGlobalConfig {
|
|||||||
|
|
||||||
static bool get isLowPerformanceDevice => _isLowPerformanceDevice;
|
static bool get isLowPerformanceDevice => _isLowPerformanceDevice;
|
||||||
|
|
||||||
static bool get allowsHighCostAnimations =>
|
static bool get allowsHighCostAnimations => true;
|
||||||
!_isLowPerformanceDevice && sdkInt > maxSdkNoAnim;
|
|
||||||
|
|
||||||
static int get recommendedImageCacheBytes =>
|
static int get recommendedImageCacheBytes =>
|
||||||
_isLowPerformanceDevice ? 40 * 1024 * 1024 : 100 * 1024 * 1024;
|
_isLowPerformanceDevice ? 40 * 1024 * 1024 : 100 * 1024 * 1024;
|
||||||
@ -211,19 +209,17 @@ class SCGlobalConfig {
|
|||||||
isLowRamDevice ||
|
isLowRamDevice ||
|
||||||
processorCount <= 6 ||
|
processorCount <= 6 ||
|
||||||
matchesKnownLowPerformanceModel;
|
matchesKnownLowPerformanceModel;
|
||||||
resetVisualEffectSwitchesToRecommendedDefaults();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool clampVisualEffectPreference(bool enabled) {
|
static bool clampVisualEffectPreference(bool enabled) {
|
||||||
return !_isLowPerformanceDevice && enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void resetVisualEffectSwitchesToRecommendedDefaults() {
|
static void resetVisualEffectSwitchesToRecommendedDefaults() {
|
||||||
final enabled = !_isLowPerformanceDevice;
|
isEntryVehicleAnimation = true;
|
||||||
isEntryVehicleAnimation = enabled;
|
isGiftSpecialEffects = true;
|
||||||
isGiftSpecialEffects = enabled;
|
isFloatingAnimationInGlobal = true;
|
||||||
isFloatingAnimationInGlobal = enabled;
|
isLuckGiftSpecialEffects = true;
|
||||||
isLuckGiftSpecialEffects = enabled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取当前业务逻辑策略
|
/// 获取当前业务逻辑策略
|
||||||
|
|||||||
@ -3,6 +3,7 @@ class SCRoomMsgType {
|
|||||||
static const String image = "IMAGE";
|
static const String image = "IMAGE";
|
||||||
static const String joinRoom = "JOIN_ROOM";
|
static const String joinRoom = "JOIN_ROOM";
|
||||||
static const String systemTips = "SYSTEM_TIPS";
|
static const String systemTips = "SYSTEM_TIPS";
|
||||||
|
static const String cpSystemNotice = "CP_SYSTEM_NOTICE";
|
||||||
|
|
||||||
///表情包
|
///表情包
|
||||||
static const String emoticons = "EMOTICONS";
|
static const String emoticons = "EMOTICONS";
|
||||||
|
|||||||
@ -890,6 +890,8 @@ class SCAppLocalizations {
|
|||||||
|
|
||||||
String get bio => translate('bio');
|
String get bio => translate('bio');
|
||||||
|
|
||||||
|
String get introduction => translate('introduction');
|
||||||
|
|
||||||
String get hobby => translate('hobby');
|
String get hobby => translate('hobby');
|
||||||
|
|
||||||
String get search => translate('search');
|
String get search => translate('search');
|
||||||
@ -995,6 +997,47 @@ class SCAppLocalizations {
|
|||||||
String get confirmDeclineTheInvitation =>
|
String get confirmDeclineTheInvitation =>
|
||||||
translate('confirmDeclineTheInvitation');
|
translate('confirmDeclineTheInvitation');
|
||||||
|
|
||||||
|
String cpInviteRejectedByOther(String relation) =>
|
||||||
|
translate('cpInviteRejectedByOther').replaceAll('{1}', relation);
|
||||||
|
|
||||||
|
String get closeFriendInvitationTitle =>
|
||||||
|
translate('closeFriendInvitationTitle');
|
||||||
|
|
||||||
|
String get closeFriendInvitationSubtitle =>
|
||||||
|
translate('closeFriendInvitationSubtitle');
|
||||||
|
|
||||||
|
String get closeFriendInvitationExpired =>
|
||||||
|
translate('closeFriendInvitationExpired');
|
||||||
|
|
||||||
|
String get closeFriendRequestDeclined =>
|
||||||
|
translate('closeFriendRequestDeclined');
|
||||||
|
|
||||||
|
String get closeFriendsHaveBecome => translate('closeFriendsHaveBecome');
|
||||||
|
|
||||||
|
String get otherAcceptedCloseFriendInvite =>
|
||||||
|
translate('otherAcceptedCloseFriendInvite');
|
||||||
|
|
||||||
|
String get otherDeclinedCloseFriendInvite =>
|
||||||
|
translate('otherDeclinedCloseFriendInvite');
|
||||||
|
|
||||||
|
String get youAcceptedCloseFriendInvite =>
|
||||||
|
translate('youAcceptedCloseFriendInvite');
|
||||||
|
|
||||||
|
String get youDeclinedCloseFriendInvite =>
|
||||||
|
translate('youDeclinedCloseFriendInvite');
|
||||||
|
|
||||||
|
String get closeFriendInviteTimedOut =>
|
||||||
|
translate('closeFriendInviteTimedOut');
|
||||||
|
|
||||||
|
String cpDismissConfirmMessage(String coins) =>
|
||||||
|
translate('cpDismissConfirmMessage').replaceAll('{1}', coins);
|
||||||
|
|
||||||
|
String get cpRelationCp => translate('cpRelationCp');
|
||||||
|
|
||||||
|
String get cpRelationBrother => translate('cpRelationBrother');
|
||||||
|
|
||||||
|
String get cpRelationSister => translate('cpRelationSister');
|
||||||
|
|
||||||
String get propMessagePrompt => translate('propMessagePrompt');
|
String get propMessagePrompt => translate('propMessagePrompt');
|
||||||
|
|
||||||
String get chats => translate('chats');
|
String get chats => translate('chats');
|
||||||
@ -1042,14 +1085,14 @@ class SCAppLocalizations {
|
|||||||
|
|
||||||
String get backTheRoom => translate('backTheRoom');
|
String get backTheRoom => translate('backTheRoom');
|
||||||
|
|
||||||
String get spendCoinsToGainExperiencePoints =>
|
String get spendCoinsToGainExperiencePoints =>
|
||||||
translate('spendCoinsToGainExperiencePoints');
|
translate('spendCoinsToGainExperiencePoints');
|
||||||
|
|
||||||
String get richLevelUpgradeAnswer => translate('richLevelUpgradeAnswer');
|
String get richLevelUpgradeAnswer => translate('richLevelUpgradeAnswer');
|
||||||
|
|
||||||
String get charmLevelUpgradeAnswer => translate('charmLevelUpgradeAnswer');
|
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');
|
||||||
|
|
||||||
@ -1552,6 +1595,9 @@ class SCAppLocalizations {
|
|||||||
|
|
||||||
String get kickRoomTips => translate('kickRoomTips');
|
String get kickRoomTips => translate('kickRoomTips');
|
||||||
|
|
||||||
|
String get roomKickedEntryBlockedTips =>
|
||||||
|
translate('roomKickedEntryBlockedTips');
|
||||||
|
|
||||||
String get joinRoomTips => translate('joinRoomTips');
|
String get joinRoomTips => translate('joinRoomTips');
|
||||||
|
|
||||||
String get youAreCurrentlyCPRelationshipPleaseDissolve =>
|
String get youAreCurrentlyCPRelationshipPleaseDissolve =>
|
||||||
|
|||||||
75
lib/debug/first_recharge_dialog_preview.dart
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:yumi/ui_kit/widgets/first_recharge/first_recharge_dialog.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
|
||||||
|
runApp(const FirstRechargeDialogPreviewApp());
|
||||||
|
}
|
||||||
|
|
||||||
|
class FirstRechargeDialogPreviewApp extends StatelessWidget {
|
||||||
|
const FirstRechargeDialogPreviewApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ScreenUtilInit(
|
||||||
|
designSize: const Size(375, 812),
|
||||||
|
minTextAdapt: true,
|
||||||
|
splitScreenMode: true,
|
||||||
|
builder:
|
||||||
|
(_, child) => MaterialApp(
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
builder: FlutterSmartDialog.init(),
|
||||||
|
home: const _FirstRechargeDialogPreviewPage(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FirstRechargeDialogPreviewPage extends StatefulWidget {
|
||||||
|
const _FirstRechargeDialogPreviewPage();
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_FirstRechargeDialogPreviewPage> createState() =>
|
||||||
|
_FirstRechargeDialogPreviewPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FirstRechargeDialogPreviewPageState
|
||||||
|
extends State<_FirstRechargeDialogPreviewPage> {
|
||||||
|
bool _shown = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
if (_shown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_shown = true;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FirstRechargeDialog.show(
|
||||||
|
context,
|
||||||
|
data: FirstRechargeDialogData.mock(),
|
||||||
|
onBuyNow: (_) {},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
body: Image.asset(
|
||||||
|
'sc_images/first_recharge/preview_background.png',
|
||||||
|
width: double.infinity,
|
||||||
|
height: double.infinity,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
173
lib/debug/gift_page_preview.dart
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:yumi/app/config/app_config.dart';
|
||||||
|
import 'package:yumi/app_localizations.dart';
|
||||||
|
import 'package:yumi/main.dart';
|
||||||
|
import 'package:yumi/modules/gift/gift_page.dart';
|
||||||
|
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||||
|
import 'package:yumi/services/audio/rtm_manager.dart';
|
||||||
|
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||||
|
import 'package:yumi/services/general/sc_app_general_manager.dart';
|
||||||
|
import 'package:yumi/shared/business_logic/models/res/gift_res.dart';
|
||||||
|
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
|
AppConfig.initialize();
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
await DataPersistence.initialize();
|
||||||
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
|
||||||
|
runApp(const GiftPagePreviewApp());
|
||||||
|
}
|
||||||
|
|
||||||
|
class GiftPagePreviewApp extends StatelessWidget {
|
||||||
|
const GiftPagePreviewApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MultiProvider(
|
||||||
|
providers: [
|
||||||
|
ChangeNotifierProvider<SCAppGeneralManager>(
|
||||||
|
create: (_) => _PreviewAppGeneralManager(),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider<RtcProvider>(create: (_) => RtcProvider()),
|
||||||
|
ChangeNotifierProvider<RtmProvider>(create: (_) => RtmProvider()),
|
||||||
|
ChangeNotifierProvider<SocialChatUserProfileManager>(
|
||||||
|
create: (_) => _PreviewUserProfileManager(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: ScreenUtilInit(
|
||||||
|
designSize: const Size(375, 812),
|
||||||
|
minTextAdapt: true,
|
||||||
|
splitScreenMode: true,
|
||||||
|
builder:
|
||||||
|
(_, child) => MaterialApp(
|
||||||
|
navigatorKey: navigatorKey,
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
locale: const Locale('en'),
|
||||||
|
supportedLocales: SCAppLocalizations.supportedLocales,
|
||||||
|
localizationsDelegates: const [
|
||||||
|
SCAppLocalizations.delegate,
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
],
|
||||||
|
builder: FlutterSmartDialog.init(),
|
||||||
|
home: const _GiftPagePreviewHome(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GiftPagePreviewHome extends StatefulWidget {
|
||||||
|
const _GiftPagePreviewHome();
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_GiftPagePreviewHome> createState() => _GiftPagePreviewHomeState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GiftPagePreviewHomeState extends State<_GiftPagePreviewHome> {
|
||||||
|
bool _shown = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
if (_shown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_shown = true;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SmartDialog.show(
|
||||||
|
tag: "showGiftControl",
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
maskColor: Colors.transparent,
|
||||||
|
animationType: SmartAnimationType.fade,
|
||||||
|
clickMaskDismiss: true,
|
||||||
|
builder: (_) => const GiftPage(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
body: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
'sc_images/room/sc_icon_room_defaut_bg.png',
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
errorBuilder:
|
||||||
|
(_, __, ___) => const ColoredBox(color: Color(0xFF062320)),
|
||||||
|
),
|
||||||
|
const Align(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(top: 88),
|
||||||
|
child: Text(
|
||||||
|
'Gift panel preview',
|
||||||
|
style: TextStyle(color: Colors.white70, fontSize: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PreviewAppGeneralManager extends SCAppGeneralManager {
|
||||||
|
_PreviewAppGeneralManager() {
|
||||||
|
final gifts = List<SocialChatGiftRes>.generate(
|
||||||
|
8,
|
||||||
|
(index) => SocialChatGiftRes(
|
||||||
|
id: 'preview_$index',
|
||||||
|
giftName: 'Gift ${index + 1}',
|
||||||
|
giftCandy: (index + 1) * 10,
|
||||||
|
giftTab: 'ALL',
|
||||||
|
giftPhoto: '',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
giftResList = gifts;
|
||||||
|
giftByTab = {'ALL': gifts};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> fetchCountryList() async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> giftList({
|
||||||
|
bool includeCustomized = true,
|
||||||
|
bool forceRefresh = false,
|
||||||
|
}) async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void giftActivityList() {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> giftBackpack({bool forceRefresh = false}) async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> warmGiftPageCovers(
|
||||||
|
String type, {
|
||||||
|
required int pageIndex,
|
||||||
|
int pageCount = 1,
|
||||||
|
int pageSize = 8,
|
||||||
|
}) async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isGiftTabLoading(String type) => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PreviewUserProfileManager extends SocialChatUserProfileManager {
|
||||||
|
@override
|
||||||
|
void balance() {}
|
||||||
|
}
|
||||||
164
lib/debug/room_animation_switch_dialog_preview.dart
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||||
|
import 'package:yumi/app_localizations.dart';
|
||||||
|
import 'package:yumi/ui_kit/widgets/room/room_animation_switch_dialog.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
SCGlobalConfig.isGiftSpecialEffects = true;
|
||||||
|
SCGlobalConfig.isLuckGiftSpecialEffects = true;
|
||||||
|
SCGlobalConfig.isEntryVehicleAnimation = false;
|
||||||
|
runApp(const RoomAnimationSwitchDialogPreviewApp());
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoomAnimationSwitchDialogPreviewApp extends StatelessWidget {
|
||||||
|
const RoomAnimationSwitchDialogPreviewApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ScreenUtilInit(
|
||||||
|
designSize: const Size(375, 812),
|
||||||
|
minTextAdapt: true,
|
||||||
|
splitScreenMode: true,
|
||||||
|
builder:
|
||||||
|
(_, child) => MaterialApp(
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
locale: const Locale('en'),
|
||||||
|
supportedLocales: SCAppLocalizations.supportedLocales,
|
||||||
|
localizationsDelegates: const [
|
||||||
|
SCAppLocalizations.delegate,
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
],
|
||||||
|
builder: FlutterSmartDialog.init(),
|
||||||
|
home: const _RoomAnimationSwitchDialogPreviewPage(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RoomAnimationSwitchDialogPreviewPage extends StatelessWidget {
|
||||||
|
const _RoomAnimationSwitchDialogPreviewPage();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
body: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: const [
|
||||||
|
_PreviewRoomBackground(),
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: RoomAnimationSwitchDialog(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PreviewRoomBackground extends StatelessWidget {
|
||||||
|
const _PreviewRoomBackground();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: [Color(0xFF172F35), Color(0xFF071B17), Color(0xFF1B1308)],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned(
|
||||||
|
left: 24.w,
|
||||||
|
top: 70.w,
|
||||||
|
child: _PreviewLine(width: 128.w, opacity: 0.55),
|
||||||
|
),
|
||||||
|
for (var index = 0; index < 10; index++)
|
||||||
|
Positioned(
|
||||||
|
left: (38 + (index % 5) * 66).w,
|
||||||
|
top: (145 + (index ~/ 5) * 80).w,
|
||||||
|
child: const _PreviewSeat(),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 26.w,
|
||||||
|
right: 26.w,
|
||||||
|
top: 355.w,
|
||||||
|
child: _PreviewPanel(height: 150.w),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 24.w,
|
||||||
|
right: 24.w,
|
||||||
|
bottom: 36.w,
|
||||||
|
child: _PreviewPanel(height: 52.w),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PreviewSeat extends StatelessWidget {
|
||||||
|
const _PreviewSeat();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
color: const Color(0xFFFFCE7C).withValues(alpha: 0.36),
|
||||||
|
width: 2.w,
|
||||||
|
),
|
||||||
|
color: Colors.black.withValues(alpha: 0.18),
|
||||||
|
),
|
||||||
|
child: SizedBox(width: 44.w, height: 44.w),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PreviewLine extends StatelessWidget {
|
||||||
|
const _PreviewLine({required this.width, required this.opacity});
|
||||||
|
|
||||||
|
final double width;
|
||||||
|
final double opacity;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: width,
|
||||||
|
height: 14.w,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withValues(alpha: opacity),
|
||||||
|
borderRadius: BorderRadius.circular(7.w),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PreviewPanel extends StatelessWidget {
|
||||||
|
const _PreviewPanel({required this.height});
|
||||||
|
|
||||||
|
final double height;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
height: height,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black.withValues(alpha: 0.24),
|
||||||
|
borderRadius: BorderRadius.circular(16.w),
|
||||||
|
border: Border.all(color: Colors.white.withValues(alpha: 0.08)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -48,7 +48,7 @@ class _RoomCpProgressDialogPreviewPage extends StatelessWidget {
|
|||||||
const RoomCpProgressDialog(
|
const RoomCpProgressDialog(
|
||||||
daysValue: '999',
|
daysValue: '999',
|
||||||
daysLabel: 'Days',
|
daysLabel: 'Days',
|
||||||
levelText: 'Lv.1 Simple Love',
|
levelText: 'Lv.0',
|
||||||
currentValue: 1,
|
currentValue: 1,
|
||||||
targetValue: 1,
|
targetValue: 1,
|
||||||
expAwayText: '1,000',
|
expAwayText: '1,000',
|
||||||
|
|||||||
113
lib/debug/room_cp_relation_formed_effect_preview.dart
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:yumi/app/constants/sc_screen.dart';
|
||||||
|
import 'package:yumi/ui_kit/widgets/room/cp/room_cp_relation_formed_effect.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
runApp(const RoomCpRelationFormedEffectPreviewApp());
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoomCpRelationFormedEffectPreviewApp extends StatelessWidget {
|
||||||
|
const RoomCpRelationFormedEffectPreviewApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ScreenUtilInit(
|
||||||
|
designSize: Size(SCScreen.designWidth, SCScreen.designHeight),
|
||||||
|
splitScreenMode: false,
|
||||||
|
minTextAdapt: true,
|
||||||
|
builder: (context, child) {
|
||||||
|
return MaterialApp(
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
builder: FlutterSmartDialog.init(),
|
||||||
|
home: const RoomCpRelationFormedEffectPreviewPage(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoomCpRelationFormedEffectPreviewPage extends StatefulWidget {
|
||||||
|
const RoomCpRelationFormedEffectPreviewPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<RoomCpRelationFormedEffectPreviewPage> createState() =>
|
||||||
|
_RoomCpRelationFormedEffectPreviewPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RoomCpRelationFormedEffectPreviewPageState
|
||||||
|
extends State<RoomCpRelationFormedEffectPreviewPage> {
|
||||||
|
Timer? _loopTimer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => _showEffect());
|
||||||
|
_loopTimer = Timer.periodic(
|
||||||
|
const Duration(seconds: 7),
|
||||||
|
(_) => _showEffect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_loopTimer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showEffect() {
|
||||||
|
RoomCpRelationFormedEffect.show(
|
||||||
|
leftName: "Avery",
|
||||||
|
rightName: "Jordan",
|
||||||
|
leftAvatar: "https://i.pravatar.cc/180?img=32",
|
||||||
|
rightAvatar: "https://i.pravatar.cc/180?img=47",
|
||||||
|
relationType: "CP",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFF170D24),
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned.fill(
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: [Color(0xFF2B163A), Color(0xFF07040D)],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
bottom: 34,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
FilledButton(
|
||||||
|
onPressed: _showEffect,
|
||||||
|
child: const Text("Replay"),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
FilledButton.tonal(
|
||||||
|
onPressed:
|
||||||
|
() => SmartDialog.dismiss(
|
||||||
|
tag: RoomCpRelationFormedEffect.dialogTag,
|
||||||
|
),
|
||||||
|
child: const Text("Dismiss"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
103
lib/debug/room_recharge_bottom_sheet_preview.dart
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:yumi/app_localizations.dart';
|
||||||
|
import 'package:yumi/main.dart';
|
||||||
|
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||||
|
import 'package:yumi/services/payment/google_payment_manager.dart';
|
||||||
|
import 'package:yumi/ui_kit/widgets/room/room_recharge_bottom_sheet.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
|
||||||
|
runApp(const RoomRechargeBottomSheetPreviewApp());
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoomRechargeBottomSheetPreviewApp extends StatelessWidget {
|
||||||
|
const RoomRechargeBottomSheetPreviewApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MultiProvider(
|
||||||
|
providers: [
|
||||||
|
ChangeNotifierProvider<SocialChatUserProfileManager>(
|
||||||
|
create: (_) => _PreviewUserProfileManager(),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider<AndroidPaymentProcessor>(
|
||||||
|
create: (_) => AndroidPaymentProcessor(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: ScreenUtilInit(
|
||||||
|
designSize: const Size(375, 812),
|
||||||
|
minTextAdapt: true,
|
||||||
|
splitScreenMode: true,
|
||||||
|
builder:
|
||||||
|
(_, child) => MaterialApp(
|
||||||
|
navigatorKey: navigatorKey,
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
locale: const Locale('en'),
|
||||||
|
supportedLocales: SCAppLocalizations.supportedLocales,
|
||||||
|
localizationsDelegates: const [
|
||||||
|
SCAppLocalizations.delegate,
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
],
|
||||||
|
builder: FlutterSmartDialog.init(),
|
||||||
|
home: const _RoomRechargeBottomSheetPreviewPage(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RoomRechargeBottomSheetPreviewPage extends StatefulWidget {
|
||||||
|
const _RoomRechargeBottomSheetPreviewPage();
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_RoomRechargeBottomSheetPreviewPage> createState() =>
|
||||||
|
_RoomRechargeBottomSheetPreviewPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RoomRechargeBottomSheetPreviewPageState
|
||||||
|
extends State<_RoomRechargeBottomSheetPreviewPage> {
|
||||||
|
bool _shown = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
if (_shown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_shown = true;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RoomRechargeBottomSheet.show(context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
body: Image.asset(
|
||||||
|
'sc_images/room/sc_icon_room_defaut_bg.png',
|
||||||
|
width: double.infinity,
|
||||||
|
height: double.infinity,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PreviewUserProfileManager extends SocialChatUserProfileManager {
|
||||||
|
@override
|
||||||
|
void balance() {
|
||||||
|
updateBalance(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@ import 'package:firebase_core/firebase_core.dart';
|
|||||||
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
||||||
import 'package:fluro/fluro.dart' as fluro;
|
import 'package:fluro/fluro.dart' as fluro;
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@ -44,8 +45,6 @@ import 'services/shop/shop_manager.dart';
|
|||||||
import 'services/theme/theme_manager.dart';
|
import 'services/theme/theme_manager.dart';
|
||||||
import 'services/auth/user_profile_manager.dart';
|
import 'services/auth/user_profile_manager.dart';
|
||||||
import 'ui_kit/theme/socialchat_theme.dart';
|
import 'ui_kit/theme/socialchat_theme.dart';
|
||||||
import 'ui_kit/widgets/room/anim/room_gift_seat_flight_overlay.dart';
|
|
||||||
import 'ui_kit/widgets/room/effect/vapp_svga_layer_widget.dart';
|
|
||||||
|
|
||||||
bool _isCrashlyticsReady = false;
|
bool _isCrashlyticsReady = false;
|
||||||
|
|
||||||
@ -278,6 +277,8 @@ class RootAppWithProviders extends StatelessWidget {
|
|||||||
|
|
||||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
|
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
|
||||||
|
final RouteObserver<ModalRoute<dynamic>> modalRouteObserver =
|
||||||
|
RouteObserver<ModalRoute<dynamic>>();
|
||||||
|
|
||||||
class YumiApplication extends StatefulWidget {
|
class YumiApplication extends StatefulWidget {
|
||||||
const YumiApplication({super.key});
|
const YumiApplication({super.key});
|
||||||
@ -299,6 +300,7 @@ class _YumiApplicationState extends State<YumiApplication>
|
|||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
_initRouter();
|
_initRouter();
|
||||||
unawaited(_initLink());
|
unawaited(_initLink());
|
||||||
|
unawaited(_reconcilePendingGooglePurchases(force: true));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,6 +327,24 @@ class _YumiApplicationState extends State<YumiApplication>
|
|||||||
context.read<RtcProvider>().releaseRtcEngineForAppTermination(),
|
context.read<RtcProvider>().releaseRtcEngineForAppTermination(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (state == AppLifecycleState.resumed) {
|
||||||
|
unawaited(_reconcilePendingGooglePurchases());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _reconcilePendingGooglePurchases({bool force = false}) async {
|
||||||
|
if (!Platform.isAndroid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await context
|
||||||
|
.read<AndroidPaymentProcessor>()
|
||||||
|
.reconcilePendingGooglePurchases(context: context, force: force);
|
||||||
|
} catch (error) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint('reconcile pending google purchases failed: $error');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -427,46 +447,11 @@ class _YumiApplicationState extends State<YumiApplication>
|
|||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return Stack(
|
return Stack(
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [child ?? const SizedBox.shrink()],
|
||||||
child ?? const SizedBox.shrink(),
|
|
||||||
if (SCGlobalConfig.allowsHighCostAnimations)
|
|
||||||
Consumer<RtcProvider>(
|
|
||||||
builder: (context, rtcProvider, _) {
|
|
||||||
if (!rtcProvider
|
|
||||||
.shouldShowRoomVisualEffects) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
return const Positioned.fill(
|
|
||||||
child: VapPlusSvgaPlayer(
|
|
||||||
tag: "room_gift",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Consumer<RtcProvider>(
|
|
||||||
builder: (context, rtcProvider, _) {
|
|
||||||
if (!rtcProvider
|
|
||||||
.shouldShowRoomVisualEffects) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
return Positioned.fill(
|
|
||||||
child: RoomGiftSeatFlightOverlay(
|
|
||||||
controller:
|
|
||||||
RoomGiftSeatFlightController(),
|
|
||||||
resolveTargetKey:
|
|
||||||
(userId) => Provider.of<RtcProvider>(
|
|
||||||
context,
|
|
||||||
listen: false,
|
|
||||||
).getSeatGlobalKeyByIndex(userId),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
navigatorObservers: [routeObserver],
|
navigatorObservers: [routeObserver, modalRouteObserver],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -106,7 +106,7 @@ class _SCEditProfilePageState extends State<SCEditProfilePage> {
|
|||||||
if (success) {
|
if (success) {
|
||||||
userProvider?.updateUserAvatar(url);
|
userProvider?.updateUserAvatar(url);
|
||||||
}
|
}
|
||||||
});
|
}, allowGif: true);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SizedBox(height: 15.w),
|
SizedBox(height: 15.w),
|
||||||
@ -559,24 +559,6 @@ class _SCEditProfilePageState extends State<SCEditProfilePage> {
|
|||||||
return primary ?? fallback;
|
return primary ?? fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isBrokenLocalMediaUrl(String? url) {
|
|
||||||
return (url ?? "").contains("/external/oss/local/");
|
|
||||||
}
|
|
||||||
|
|
||||||
String? _preferUsableAvatar(String? primary, String? fallback) {
|
|
||||||
if (primary != null &&
|
|
||||||
primary.isNotEmpty &&
|
|
||||||
!_isBrokenLocalMediaUrl(primary)) {
|
|
||||||
return primary;
|
|
||||||
}
|
|
||||||
if (fallback != null &&
|
|
||||||
fallback.isNotEmpty &&
|
|
||||||
!_isBrokenLocalMediaUrl(fallback)) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
return _preferNonEmpty(primary, fallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
num? _preferNonZero(num? primary, num? fallback) {
|
num? _preferNonZero(num? primary, num? fallback) {
|
||||||
if (primary != null && primary != 0) {
|
if (primary != null && primary != 0) {
|
||||||
return primary;
|
return primary;
|
||||||
@ -661,10 +643,6 @@ class _SCEditProfilePageState extends State<SCEditProfilePage> {
|
|||||||
if (submittedProfile != null) {
|
if (submittedProfile != null) {
|
||||||
final mergedProfile = (user.userProfile ?? SocialChatUserProfile())
|
final mergedProfile = (user.userProfile ?? SocialChatUserProfile())
|
||||||
.copyWith(
|
.copyWith(
|
||||||
userAvatar: _preferUsableAvatar(
|
|
||||||
user.userProfile?.userAvatar,
|
|
||||||
submittedProfile.userAvatar,
|
|
||||||
),
|
|
||||||
userNickname: _preferNonEmpty(
|
userNickname: _preferNonEmpty(
|
||||||
user.userProfile?.userNickname,
|
user.userProfile?.userNickname,
|
||||||
submittedProfile.userNickname,
|
submittedProfile.userNickname,
|
||||||
|
|||||||
@ -4,37 +4,38 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|||||||
import 'package:yumi/app_localizations.dart';
|
import 'package:yumi/app_localizations.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/text/sc_text.dart';
|
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||||
import 'package:yumi/services/audio/rtm_manager.dart';
|
import 'package:yumi/services/audio/rtm_manager.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart';
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart';
|
||||||
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
|
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
|
||||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||||
import 'package:yumi/app/config/business_logic_strategy.dart';
|
import 'package:yumi/app/config/business_logic_strategy.dart';
|
||||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
|
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||||
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/msg/message_conversation_list_page.dart';
|
import 'package:yumi/ui_kit/widgets/msg/message_conversation_list_page.dart';
|
||||||
|
|
||||||
import '../../index/main_route.dart';
|
|
||||||
import '../chat_route.dart';
|
import '../chat_route.dart';
|
||||||
|
|
||||||
///消息
|
///消息
|
||||||
class SCMessagePage extends StatefulWidget {
|
class SCMessagePage extends StatefulWidget {
|
||||||
bool isFromRoom = false;
|
final bool isFromRoom;
|
||||||
|
|
||||||
SCMessagePage({this.isFromRoom = false});
|
const SCMessagePage({super.key, this.isFromRoom = false});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_MessagePageState createState() => _MessagePageState(isFromRoom);
|
State<SCMessagePage> createState() => _MessagePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MessagePageState extends State<SCMessagePage> {
|
class _MessagePageState extends State<SCMessagePage> {
|
||||||
BusinessLogicStrategy get _strategy => SCGlobalConfig.businessLogicStrategy;
|
BusinessLogicStrategy get _strategy => SCGlobalConfig.businessLogicStrategy;
|
||||||
bool isFromRoom = false;
|
|
||||||
bool showSystemAnnouncementTips = true;
|
bool showSystemAnnouncementTips = true;
|
||||||
|
final TextEditingController _messageSearchController =
|
||||||
_MessagePageState(this.isFromRoom);
|
TextEditingController();
|
||||||
|
final FocusNode _messageSearchFocusNode = FocusNode();
|
||||||
|
bool _isSearchingMessages = false;
|
||||||
|
String _messageSearchText = "";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -84,6 +85,8 @@ class _MessagePageState extends State<SCMessagePage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_messageSearchController.dispose();
|
||||||
|
_messageSearchFocusNode.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,46 +108,59 @@ class _MessagePageState extends State<SCMessagePage> {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
leading: null,
|
leading: null,
|
||||||
title: TabBar(
|
title:
|
||||||
tabs:
|
_isSearchingMessages
|
||||||
[SCAppLocalizations.of(context)!.message]
|
? _messageSearchField()
|
||||||
.map(
|
: TabBar(
|
||||||
(e) => Padding(
|
tabs:
|
||||||
padding: EdgeInsets.only(bottom: 3.w, top: 4.w),
|
[SCAppLocalizations.of(context)!.message]
|
||||||
child: Text(e),
|
.map(
|
||||||
),
|
(e) => Padding(
|
||||||
)
|
padding: EdgeInsets.only(
|
||||||
.toList(),
|
bottom: 3.w,
|
||||||
dividerColor: Colors.transparent,
|
top: 4.w,
|
||||||
labelStyle: TextStyle(
|
),
|
||||||
fontSize: 16.sp,
|
child: Text(e),
|
||||||
fontWeight: FontWeight.bold,
|
),
|
||||||
fontStyle: FontStyle.italic,
|
)
|
||||||
),
|
.toList(),
|
||||||
unselectedLabelStyle: TextStyle(
|
dividerColor: Colors.transparent,
|
||||||
fontSize: 15.sp,
|
labelStyle: TextStyle(
|
||||||
color: Colors.white,
|
fontSize: 16.sp,
|
||||||
fontWeight: FontWeight.normal,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
fontStyle: FontStyle.italic,
|
||||||
indicator: BoxDecoration(),
|
),
|
||||||
//TabBarIndicator(),
|
unselectedLabelStyle: TextStyle(
|
||||||
indicatorSize: TabBarIndicatorSize.label,
|
fontSize: 15.sp,
|
||||||
isScrollable: true,
|
color: Colors.white,
|
||||||
tabAlignment: TabAlignment.start,
|
fontWeight: FontWeight.normal,
|
||||||
labelColor: SocialChatTheme.primaryLight,
|
),
|
||||||
),
|
indicator: BoxDecoration(),
|
||||||
|
//TabBarIndicator(),
|
||||||
|
indicatorSize: TabBarIndicatorSize.label,
|
||||||
|
isScrollable: true,
|
||||||
|
tabAlignment: TabAlignment.start,
|
||||||
|
labelColor: SocialChatTheme.primaryLight,
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
SCDebounceWidget(
|
SCDebounceWidget(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsetsDirectional.only(end: 10.w),
|
padding: EdgeInsetsDirectional.only(start: 10.w, end: 10.w),
|
||||||
child: Image.asset(
|
child:
|
||||||
"sc_images/index/sc_icon_serach.png",
|
_isSearchingMessages
|
||||||
width: 20.w,
|
? Icon(Icons.close, size: 22.w, color: Colors.white)
|
||||||
height: 20.w,
|
: Image.asset(
|
||||||
),
|
"sc_images/index/sc_icon_serach.png",
|
||||||
|
width: 20.w,
|
||||||
|
height: 20.w,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
SCNavigatorUtils.push(context, SCMainRoute.mainSearch);
|
if (_isSearchingMessages) {
|
||||||
|
_stopMessageSearch();
|
||||||
|
} else {
|
||||||
|
_startMessageSearch();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -165,7 +181,7 @@ class _MessagePageState extends State<SCMessagePage> {
|
|||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_headMenu(provider),
|
if (!_isSearchingMessages) _headMenu(provider),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: EdgeInsets.only(
|
margin: EdgeInsets.only(
|
||||||
@ -173,7 +189,10 @@ class _MessagePageState extends State<SCMessagePage> {
|
|||||||
left: 8.w,
|
left: 8.w,
|
||||||
right: 8.w,
|
right: 8.w,
|
||||||
),
|
),
|
||||||
child: MessageConversationListPage(false),
|
child: MessageConversationListPage(
|
||||||
|
false,
|
||||||
|
searchQuery: _messageSearchText,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -189,9 +208,76 @@ class _MessagePageState extends State<SCMessagePage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _messageSearchField() {
|
||||||
|
return Container(
|
||||||
|
height: 36.w,
|
||||||
|
alignment: AlignmentDirectional.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xff18F2B1).withValues(alpha: 0.1),
|
||||||
|
borderRadius: BorderRadius.circular(20.w),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(width: 10.w),
|
||||||
|
Image.asset(
|
||||||
|
"sc_images/index/sc_icon_serach2.png",
|
||||||
|
width: 18.w,
|
||||||
|
height: 18.w,
|
||||||
|
color: const Color(0xff18F2B1),
|
||||||
|
),
|
||||||
|
SizedBox(width: 6.w),
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: _messageSearchController,
|
||||||
|
focusNode: _messageSearchFocusNode,
|
||||||
|
autofocus: true,
|
||||||
|
cursorColor: SocialChatTheme.primaryLight,
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 14.sp),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText:
|
||||||
|
"${SCAppLocalizations.of(context)!.search} ID/${SCAppLocalizations.of(context)!.userName}",
|
||||||
|
hintStyle: TextStyle(color: Colors.white54, fontSize: 13.sp),
|
||||||
|
border: InputBorder.none,
|
||||||
|
isDense: true,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_messageSearchText = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startMessageSearch() {
|
||||||
|
setState(() {
|
||||||
|
_isSearchingMessages = true;
|
||||||
|
});
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (mounted) {
|
||||||
|
_messageSearchFocusNode.requestFocus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _stopMessageSearch() {
|
||||||
|
_messageSearchController.clear();
|
||||||
|
_messageSearchFocusNode.unfocus();
|
||||||
|
setState(() {
|
||||||
|
_isSearchingMessages = false;
|
||||||
|
_messageSearchText = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Widget _headMenu(RtmProvider provider) {
|
Widget _headMenu(RtmProvider provider) {
|
||||||
return Container(
|
return Container(
|
||||||
margin: EdgeInsetsDirectional.symmetric(horizontal: 25.w,).copyWith(top: 10.w),
|
margin: EdgeInsetsDirectional.symmetric(
|
||||||
|
horizontal: 20.w,
|
||||||
|
).copyWith(top: 10.w),
|
||||||
child: Column(
|
child: Column(
|
||||||
spacing: 15.w,
|
spacing: 15.w,
|
||||||
children: [
|
children: [
|
||||||
@ -265,70 +351,49 @@ class _MessagePageState extends State<SCMessagePage> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
SCDebounceWidget(
|
SCDebounceWidget(
|
||||||
child: Stack(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
SizedBox(height: 3.w),
|
||||||
children: [
|
Image.asset(
|
||||||
SizedBox(height: 3.w),
|
_strategy.getMessagePageSystemMessageIcon(),
|
||||||
Image.asset(
|
width: 45.w,
|
||||||
_strategy.getMessagePageSystemMessageIcon(),
|
height: 45.w,
|
||||||
width: 45.w,
|
|
||||||
height: 45.w,
|
|
||||||
),
|
|
||||||
SizedBox(width: 5.w),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
text(
|
|
||||||
SCAppLocalizations.of(context)!.system,
|
|
||||||
textColor: Colors.white,
|
|
||||||
fontSize: 14.sp,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
SizedBox(height: 3.w),
|
|
||||||
text(
|
|
||||||
"[${SCAppLocalizations.of(context)!.newMessage}]",
|
|
||||||
fontSize: 12.sp,
|
|
||||||
textColor: Colors.white54,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
PositionedDirectional(
|
SizedBox(width: 6.w),
|
||||||
bottom: 0,
|
Expanded(
|
||||||
end: 10.w,
|
child: Column(
|
||||||
child: Visibility(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
visible: provider.systemUnReadCount > 0,
|
children: [
|
||||||
child: Container(
|
text(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 6.w),
|
SCAppLocalizations.of(context)!.system,
|
||||||
alignment: Alignment.center,
|
textColor: Colors.white,
|
||||||
height: 16.w,
|
fontSize: 14.sp,
|
||||||
decoration: BoxDecoration(
|
fontWeight: FontWeight.bold,
|
||||||
//gradient: LinearGradient(colors: [Color(0xffA447FF), Color(0xff623CE9)]),
|
|
||||||
color: Colors.red,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
//borderRadius: BorderRadius.all(Radius.circular(7.w)),
|
|
||||||
),
|
),
|
||||||
child: Text(
|
SizedBox(height: 3.w),
|
||||||
"${provider.systemUnReadCount > 99 ? '99+' : provider.systemUnReadCount}",
|
Row(
|
||||||
style: TextStyle(
|
children: [
|
||||||
fontSize: 10.sp,
|
Expanded(
|
||||||
color: Color(0xffffffff),
|
child: text(
|
||||||
fontWeight: FontWeight.w400,
|
"[${SCAppLocalizations.of(context)!.newMessage}]",
|
||||||
decoration: TextDecoration.none,
|
fontSize: 12.sp,
|
||||||
),
|
textColor: Colors.white54,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 5.w),
|
||||||
|
_unreadBadge(provider.systemUnReadCount),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
var conversation = provider.getPreferredSystemConversation();
|
var conversation = provider.getPreferredSystemConversation();
|
||||||
provider.updateSystemCount(0);
|
provider.updateSystemCount(0);
|
||||||
var bool = await provider.startConversation(conversation);
|
var bool = await provider.startConversation(conversation);
|
||||||
if (!bool) return;
|
if (!bool) return;
|
||||||
var json = jsonEncode(conversation.toJson());
|
var json = jsonEncode(conversation.toJson());
|
||||||
SCNavigatorUtils.push(
|
SCNavigatorUtils.push(
|
||||||
context,
|
context,
|
||||||
@ -337,64 +402,40 @@ class _MessagePageState extends State<SCMessagePage> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
SCDebounceWidget(
|
SCDebounceWidget(
|
||||||
child: Stack(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
SizedBox(height: 3.w),
|
||||||
alignment: Alignment.center,
|
Image.asset(
|
||||||
child: Row(
|
_strategy.getMessagePageNotificationMessageIcon(),
|
||||||
children: [
|
width: 45.w,
|
||||||
SizedBox(height: 3.w),
|
height: 45.w,
|
||||||
Image.asset(
|
|
||||||
_strategy.getMessagePageNotificationMessageIcon(),
|
|
||||||
width: 45.w,
|
|
||||||
height: 45.w,
|
|
||||||
),
|
|
||||||
SizedBox(width: 5.w),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
text(
|
|
||||||
SCAppLocalizations.of(context)!.notifcation,
|
|
||||||
textColor: Colors.white,
|
|
||||||
fontSize: 14.sp,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
SizedBox(height: 3.w),
|
|
||||||
text(
|
|
||||||
"[${SCAppLocalizations.of(context)!.newMessage}]",
|
|
||||||
fontSize: 12.sp,
|
|
||||||
textColor: Colors.white54,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
PositionedDirectional(
|
SizedBox(width: 6.w),
|
||||||
bottom: 0,
|
Expanded(
|
||||||
end: 10.w,
|
child: Column(
|
||||||
child: Visibility(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
visible: provider.notifcationUnReadCount > 0,
|
children: [
|
||||||
child: Container(
|
text(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 6.w),
|
SCAppLocalizations.of(context)!.notifcation,
|
||||||
alignment: Alignment.center,
|
textColor: Colors.white,
|
||||||
height: 16.w,
|
fontSize: 14.sp,
|
||||||
decoration: BoxDecoration(
|
fontWeight: FontWeight.bold,
|
||||||
//gradient: LinearGradient(colors: [Color(0xffA447FF), Color(0xff623CE9)]),
|
|
||||||
color: Colors.red,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
//borderRadius: BorderRadius.all(Radius.circular(7.w)),
|
|
||||||
),
|
),
|
||||||
child: Text(
|
SizedBox(height: 3.w),
|
||||||
"${provider.notifcationUnReadCount > 99 ? '99+' : provider.notifcationUnReadCount}",
|
Row(
|
||||||
style: TextStyle(
|
children: [
|
||||||
fontSize: 10.sp,
|
Expanded(
|
||||||
color: Color(0xffffffff),
|
child: text(
|
||||||
fontWeight: FontWeight.w400,
|
"[${SCAppLocalizations.of(context)!.newMessage}]",
|
||||||
decoration: TextDecoration.none,
|
fontSize: 12.sp,
|
||||||
),
|
textColor: Colors.white54,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 5.w),
|
||||||
|
_unreadBadge(provider.notifcationUnReadCount),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -412,4 +453,25 @@ class _MessagePageState extends State<SCMessagePage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _unreadBadge(int unreadCount) {
|
||||||
|
return Visibility(
|
||||||
|
visible: unreadCount > 0,
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 6.w),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
height: 16.w,
|
||||||
|
decoration: BoxDecoration(color: Colors.red, shape: BoxShape.circle),
|
||||||
|
child: Text(
|
||||||
|
"${unreadCount > 99 ? '99+' : unreadCount}",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10.sp,
|
||||||
|
color: Color(0xffffffff),
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
928
lib/modules/gift/cp_rights/cp_rights_guide_page.dart
Normal file
@ -0,0 +1,928 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||||
|
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||||
|
import 'package:yumi/shared/business_logic/models/res/sc_cp_rights_config_res.dart';
|
||||||
|
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
|
||||||
|
import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart';
|
||||||
|
import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart';
|
||||||
|
import 'package:yumi/shared/tools/sc_room_effect_scheduler.dart';
|
||||||
|
|
||||||
|
enum _CpGuideTab { note, rights }
|
||||||
|
|
||||||
|
enum _CpRightsCategory { cp, brother, sisters }
|
||||||
|
|
||||||
|
class CpRightsGuidePage extends StatefulWidget {
|
||||||
|
const CpRightsGuidePage({super.key, this.cpUserId});
|
||||||
|
|
||||||
|
final String? cpUserId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CpRightsGuidePage> createState() => _CpRightsGuidePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CpRightsGuidePageState extends State<CpRightsGuidePage> {
|
||||||
|
static const String _assetBase = "sc_images/room/cp_rights";
|
||||||
|
static const Color _textColor = Color(0xff543356);
|
||||||
|
static const Color _accentColor = Color(0xffff2a70);
|
||||||
|
static const String _effectSuppressReason = "cp_rights_guide_page";
|
||||||
|
static const String _roomRocketPagLaunchDialogTag =
|
||||||
|
"showRoomRocketPagLaunchDialog";
|
||||||
|
|
||||||
|
_CpGuideTab _tab = _CpGuideTab.note;
|
||||||
|
_CpRightsCategory _rightsCategory = _CpRightsCategory.cp;
|
||||||
|
RtcProvider? _suppressedRtcProvider;
|
||||||
|
bool _roomEffectsSuppressed = false;
|
||||||
|
bool _restoreRoomVisualEffects = false;
|
||||||
|
final Map<_CpRightsCategory, List<_CpRightsLevel>> _rightsRowsByCategory = {};
|
||||||
|
final Set<_CpRightsCategory> _loadingRightsCategories = {};
|
||||||
|
final Set<_CpRightsCategory> _loadedRightsCategories = {};
|
||||||
|
final Set<_CpRightsCategory> _failedRightsCategories = {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_suppressRoomEffects();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_releaseRoomEffects();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xffffe7eb),
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned.fill(child: _buildBackground()),
|
||||||
|
SafeArea(
|
||||||
|
top: true,
|
||||||
|
bottom: false,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_buildNavigationBar(),
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const BouncingScrollPhysics(),
|
||||||
|
padding: EdgeInsets.only(bottom: 28.w),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 15.w),
|
||||||
|
_buildTopTabs(),
|
||||||
|
SizedBox(height: 14.w),
|
||||||
|
if (_tab == _CpGuideTab.note)
|
||||||
|
_buildNoteContent()
|
||||||
|
else
|
||||||
|
_buildRightsContent(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBackground() {
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: [Color(0xffffd8e6), Color(0xffffe7eb)],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Opacity(
|
||||||
|
opacity: 0.6,
|
||||||
|
child: Image.asset(
|
||||||
|
"$_assetBase/sc_cp_rights_bg.png",
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildNavigationBar() {
|
||||||
|
return SizedBox(
|
||||||
|
height: 44.w,
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
PositionedDirectional(
|
||||||
|
start: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () => Navigator.of(context).maybePop(),
|
||||||
|
child: SizedBox(
|
||||||
|
width: 44.w,
|
||||||
|
child: Center(
|
||||||
|
child: Transform.flip(
|
||||||
|
flipX: SCGlobalConfig.lang == "ar",
|
||||||
|
child: Icon(
|
||||||
|
Icons.arrow_back_ios_new,
|
||||||
|
color: Colors.black,
|
||||||
|
size: 18.w,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 54.w),
|
||||||
|
child: Text(
|
||||||
|
"Gifts For Close Friends",
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
textScaler: TextScaler.noScaling,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontSize: 18.sp,
|
||||||
|
height: 1,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTopTabs() {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
_buildMainTab(label: "Note", tab: _CpGuideTab.note),
|
||||||
|
SizedBox(width: 44.w),
|
||||||
|
_buildMainTab(label: "Rights", tab: _CpGuideTab.rights),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMainTab({required String label, required _CpGuideTab tab}) {
|
||||||
|
final selected = _tab == tab;
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {
|
||||||
|
if (_tab == tab) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() => _tab = tab);
|
||||||
|
if (tab == _CpGuideTab.rights) {
|
||||||
|
_ensureRightsConfigLoaded(_rightsCategory);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
width: 116.w,
|
||||||
|
height: 36.w,
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
Positioned.fill(
|
||||||
|
child: Image.asset(
|
||||||
|
selected
|
||||||
|
? "$_assetBase/sc_cp_rights_tab_selected.png"
|
||||||
|
: "$_assetBase/sc_cp_rights_tab_unselected.png",
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
textScaler: TextScaler.noScaling,
|
||||||
|
style: TextStyle(
|
||||||
|
color: selected ? Colors.white : _textColor,
|
||||||
|
fontSize: 14.sp,
|
||||||
|
height: 1,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildNoteContent() {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
_CpOrnatePanel(
|
||||||
|
assetBase: _assetBase,
|
||||||
|
title: "What is a close friendship?",
|
||||||
|
height: 223.w,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(20.w, 67.w, 20.w, 18.w),
|
||||||
|
child: Text(
|
||||||
|
"The \"Best Friends\" feature is a brand-new interactive way to celebrate close relationships between users. By sending gifts to someone and requesting to become best friends, you can form a best friend connection once they accept.\n"
|
||||||
|
"'CP' connections can only be formed between a man and a woman, while \"brother\" and \"sister\" connections have no gender restrictions.",
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
textScaler: TextScaler.noScaling,
|
||||||
|
style: _bodyStyle(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 12.w),
|
||||||
|
_CpOrnatePanel(
|
||||||
|
assetBase: _assetBase,
|
||||||
|
title: "How to Form Relationships?",
|
||||||
|
height: 940.w,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(20.w, 67.w, 20.w, 16.w),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_bodyText(
|
||||||
|
"Request for a Gift Panel in a Group Chat Room",
|
||||||
|
color: _accentColor,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
SizedBox(height: 5.w),
|
||||||
|
_bodyText(
|
||||||
|
"1. In the room, tap [Gift Panel] > select [Best Friends];",
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.w),
|
||||||
|
_noteImage("sc_cp_rights_note_gift_panel.png", 122),
|
||||||
|
SizedBox(height: 13.w),
|
||||||
|
_bodyText(
|
||||||
|
"2. Choose a gift tailored to a specific type of close friend; once you send it, a friend request will be automatically sent;",
|
||||||
|
),
|
||||||
|
SizedBox(height: 10.w),
|
||||||
|
_noteImage("sc_cp_rights_note_relation_types.png", 42),
|
||||||
|
SizedBox(height: 13.w),
|
||||||
|
_bodyText(
|
||||||
|
"3. The other party must accept the match within 24 hours;",
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.w),
|
||||||
|
_noteImage("sc_cp_rights_note_invitation.png", 408),
|
||||||
|
SizedBox(height: 9.w),
|
||||||
|
_bodyText(
|
||||||
|
"*If the other party does not respond within 24 hours of the request being sent, the connection will be automatically declined.",
|
||||||
|
color: _accentColor,
|
||||||
|
fontSize: 10,
|
||||||
|
lineHeight: 1.4,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 12.w),
|
||||||
|
_CpOrnatePanel(
|
||||||
|
assetBase: _assetBase,
|
||||||
|
title: "Intimacy Level",
|
||||||
|
height: 174.w,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(20.w, 67.w, 20.w, 16.w),
|
||||||
|
child: _bodyText(
|
||||||
|
"Intimacy Points are a numerical representation of the level of closeness between two players. 1 Gold Coin equals 1 Intimacy Point, and you can increase your Intimacy Points by sending gifts to your best friend every day.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 12.w),
|
||||||
|
_CpOrnatePanel(
|
||||||
|
assetBase: _assetBase,
|
||||||
|
title: "FAQ",
|
||||||
|
height: 486.w,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(20.w, 67.w, 20.w, 18.w),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: const [
|
||||||
|
_FaqItem(
|
||||||
|
question:
|
||||||
|
"Q1: Does giving someone a regular gift increase intimacy?",
|
||||||
|
answer: "No increase.",
|
||||||
|
),
|
||||||
|
_FaqItem(
|
||||||
|
question:
|
||||||
|
"Q2: Is there a limit to the number of relationships?",
|
||||||
|
answer:
|
||||||
|
"You can only have one CP, and a maximum of three brothers or sisters. More slots will be added in the future.",
|
||||||
|
),
|
||||||
|
_FaqItem(
|
||||||
|
question: "Q3: Who can I invite to be a CP?",
|
||||||
|
answer:
|
||||||
|
"As long as neither party has a CP, you can send a CP gift to extend a relationship invitation.",
|
||||||
|
),
|
||||||
|
_FaqItem(
|
||||||
|
question:
|
||||||
|
"Q4: If my application is denied, will the Best Friend Gift be refunded?",
|
||||||
|
answer:
|
||||||
|
"Experience points are awarded the moment the gift is sent. If the request expires or is rejected, the gift will not be returned.",
|
||||||
|
),
|
||||||
|
_FaqItem(
|
||||||
|
question:
|
||||||
|
"Q5: Can the termination of the relationship be reversed?",
|
||||||
|
answer:
|
||||||
|
"Once the relationship is dissolved, all rights and interests will be reclaimed by the platform. The action cannot be undone; the relationship can only be reestablished.",
|
||||||
|
isLast: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _noteImage(String name, double height) {
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8.w),
|
||||||
|
child: Image.asset(
|
||||||
|
"$_assetBase/$name",
|
||||||
|
width: 310.w,
|
||||||
|
height: height.w,
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextStyle _bodyStyle({
|
||||||
|
Color color = _textColor,
|
||||||
|
FontWeight fontWeight = FontWeight.w400,
|
||||||
|
double fontSize = 12,
|
||||||
|
double lineHeight = 1.333,
|
||||||
|
}) {
|
||||||
|
return TextStyle(
|
||||||
|
color: color,
|
||||||
|
fontSize: fontSize.sp,
|
||||||
|
height: lineHeight,
|
||||||
|
fontWeight: fontWeight,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _bodyText(
|
||||||
|
String value, {
|
||||||
|
Color color = _textColor,
|
||||||
|
FontWeight fontWeight = FontWeight.w400,
|
||||||
|
double fontSize = 12,
|
||||||
|
double lineHeight = 1.333,
|
||||||
|
}) {
|
||||||
|
return Text(
|
||||||
|
value,
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
textScaler: TextScaler.noScaling,
|
||||||
|
style: _bodyStyle(
|
||||||
|
color: color,
|
||||||
|
fontWeight: fontWeight,
|
||||||
|
fontSize: fontSize,
|
||||||
|
lineHeight: lineHeight,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRightsContent() {
|
||||||
|
final rows = _currentRightsRowsFor(_rightsCategory);
|
||||||
|
final isInitialLoading =
|
||||||
|
_loadingRightsCategories.contains(_rightsCategory) &&
|
||||||
|
!_rightsRowsByCategory.containsKey(_rightsCategory);
|
||||||
|
final hasLoadError =
|
||||||
|
_failedRightsCategories.contains(_rightsCategory) &&
|
||||||
|
!_rightsRowsByCategory.containsKey(_rightsCategory);
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 52.w),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
_buildRightsCategoryTab("CP", _CpRightsCategory.cp),
|
||||||
|
_buildRightsCategoryTab("Brother", _CpRightsCategory.brother),
|
||||||
|
_buildRightsCategoryTab("Sisters", _CpRightsCategory.sisters),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 13.w),
|
||||||
|
if (hasLoadError)
|
||||||
|
_buildRightsLoadError(_rightsCategory)
|
||||||
|
else
|
||||||
|
Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
_buildRightsTable(rows),
|
||||||
|
if (isInitialLoading)
|
||||||
|
SizedBox(
|
||||||
|
width: 22.w,
|
||||||
|
height: 22.w,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2.w,
|
||||||
|
color: _accentColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRightsCategoryTab(String label, _CpRightsCategory category) {
|
||||||
|
final selected = _rightsCategory == category;
|
||||||
|
final textStyle = TextStyle(
|
||||||
|
color: selected ? _accentColor : _textColor.withValues(alpha: 0.5),
|
||||||
|
fontSize: 16.sp,
|
||||||
|
height: 1,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
);
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {
|
||||||
|
if (_rightsCategory == category) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() => _rightsCategory = category);
|
||||||
|
_ensureRightsConfigLoaded(category);
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
height: 26.w,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(label, textScaler: TextScaler.noScaling, style: textStyle),
|
||||||
|
SizedBox(height: 6.w),
|
||||||
|
if (selected)
|
||||||
|
Container(
|
||||||
|
width: _rightsIndicatorWidth(label, textStyle),
|
||||||
|
height: 3.w,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _accentColor,
|
||||||
|
borderRadius: BorderRadius.circular(31.5.w),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRightsLoadError(_CpRightsCategory category) {
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () => _ensureRightsConfigLoaded(category, force: true),
|
||||||
|
child: Container(
|
||||||
|
width: 351.w,
|
||||||
|
height: 156.w,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withValues(alpha: 0.3),
|
||||||
|
borderRadius: BorderRadius.circular(12.w),
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 24.w),
|
||||||
|
child: Text(
|
||||||
|
"Failed to load rights. Tap to retry.",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
textScaler: TextScaler.noScaling,
|
||||||
|
style: _bodyStyle(color: _accentColor, lineHeight: 1.3),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<_CpRightsLevel> _rightsRowsFor(_CpRightsCategory category) {
|
||||||
|
switch (category) {
|
||||||
|
case _CpRightsCategory.cp:
|
||||||
|
return const [
|
||||||
|
_CpRightsLevel(level: "Lv.1", intimacy: "--"),
|
||||||
|
_CpRightsLevel(level: "Lv.2", intimacy: "--"),
|
||||||
|
_CpRightsLevel(level: "Lv.3", intimacy: "--"),
|
||||||
|
_CpRightsLevel(level: "Lv.4", intimacy: "--"),
|
||||||
|
_CpRightsLevel(level: "Lv.5", intimacy: "--"),
|
||||||
|
];
|
||||||
|
case _CpRightsCategory.brother:
|
||||||
|
return const [
|
||||||
|
_CpRightsLevel(level: "Lv.1", intimacy: "--"),
|
||||||
|
_CpRightsLevel(level: "Lv.2", intimacy: "--"),
|
||||||
|
_CpRightsLevel(level: "Lv.3", intimacy: "--"),
|
||||||
|
_CpRightsLevel(level: "Lv.4", intimacy: "--"),
|
||||||
|
_CpRightsLevel(level: "Lv.5", intimacy: "--"),
|
||||||
|
];
|
||||||
|
case _CpRightsCategory.sisters:
|
||||||
|
return const [
|
||||||
|
_CpRightsLevel(level: "Lv.1", intimacy: "--"),
|
||||||
|
_CpRightsLevel(level: "Lv.2", intimacy: "--"),
|
||||||
|
_CpRightsLevel(level: "Lv.3", intimacy: "--"),
|
||||||
|
_CpRightsLevel(level: "Lv.4", intimacy: "--"),
|
||||||
|
_CpRightsLevel(level: "Lv.5", intimacy: "--"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<_CpRightsLevel> _currentRightsRowsFor(_CpRightsCategory category) {
|
||||||
|
return _rightsRowsByCategory[category] ?? _rightsRowsFor(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _ensureRightsConfigLoaded(
|
||||||
|
_CpRightsCategory category, {
|
||||||
|
bool force = false,
|
||||||
|
}) async {
|
||||||
|
if (!force &&
|
||||||
|
(_loadedRightsCategories.contains(category) ||
|
||||||
|
_loadingRightsCategories.contains(category))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final relationType = _relationTypeForRightsCategory(category);
|
||||||
|
setState(() {
|
||||||
|
_loadingRightsCategories.add(category);
|
||||||
|
_failedRightsCategories.remove(category);
|
||||||
|
});
|
||||||
|
_rightsDebugLog(
|
||||||
|
'load start relationType=$relationType cpUserId=${widget.cpUserId}',
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
final config = await SCAccountRepository().cpRightsConfig(
|
||||||
|
relationType: relationType,
|
||||||
|
cpUserId: widget.cpUserId,
|
||||||
|
);
|
||||||
|
final rows = _rightsRowsFromConfig(config, category);
|
||||||
|
if (rows.isEmpty) {
|
||||||
|
throw StateError('empty CP rights levels');
|
||||||
|
}
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_rightsRowsByCategory[category] = rows;
|
||||||
|
_loadedRightsCategories.add(category);
|
||||||
|
_failedRightsCategories.remove(category);
|
||||||
|
_loadingRightsCategories.remove(category);
|
||||||
|
});
|
||||||
|
_rightsDebugLog(
|
||||||
|
'load success relationType=$relationType levels=${rows.length}',
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_loadingRightsCategories.remove(category);
|
||||||
|
_failedRightsCategories.add(category);
|
||||||
|
});
|
||||||
|
_rightsDebugLog('load failed relationType=$relationType error=$error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<_CpRightsLevel> _rightsRowsFromConfig(
|
||||||
|
SCCpRightsConfigRes config,
|
||||||
|
_CpRightsCategory category,
|
||||||
|
) {
|
||||||
|
final rows = <_CpRightsLevel>[];
|
||||||
|
for (final level in config.levels) {
|
||||||
|
final levelNumber = level.level;
|
||||||
|
final levelName = level.levelName?.trim();
|
||||||
|
final requiredValue = level.requiredIntimacyValue ?? 0;
|
||||||
|
rows.add(
|
||||||
|
_CpRightsLevel(
|
||||||
|
level:
|
||||||
|
levelName?.isNotEmpty == true
|
||||||
|
? levelName!
|
||||||
|
: (levelNumber != null
|
||||||
|
? "Lv.$levelNumber"
|
||||||
|
: "Lv.${rows.length + 1}"),
|
||||||
|
intimacy: _formatRightsIntimacyValue(requiredValue),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _relationTypeForRightsCategory(_CpRightsCategory category) {
|
||||||
|
switch (category) {
|
||||||
|
case _CpRightsCategory.cp:
|
||||||
|
return "CP";
|
||||||
|
case _CpRightsCategory.brother:
|
||||||
|
return "BROTHER";
|
||||||
|
case _CpRightsCategory.sisters:
|
||||||
|
return "SISTERS";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatRightsIntimacyValue(int value) {
|
||||||
|
final text = value.abs().toString();
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
for (var index = 0; index < text.length; index++) {
|
||||||
|
if (index > 0 && (text.length - index) % 3 == 0) {
|
||||||
|
buffer.write(",");
|
||||||
|
}
|
||||||
|
buffer.write(text[index]);
|
||||||
|
}
|
||||||
|
return value < 0 ? "-$buffer" : buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _rightsDebugLog(String message) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint("[CP][Rights] $message");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRightsTable(List<_CpRightsLevel> rows) {
|
||||||
|
const tableWidth = 351.0;
|
||||||
|
const columnWidth = tableWidth / 2;
|
||||||
|
const headerHeight = 39.0;
|
||||||
|
const rowHeight = 77.0;
|
||||||
|
final tableHeight = headerHeight + rows.length * rowHeight;
|
||||||
|
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(12.w),
|
||||||
|
child: Container(
|
||||||
|
width: tableWidth.w,
|
||||||
|
height: tableHeight.w,
|
||||||
|
color: Colors.white.withValues(alpha: 0.3),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned(
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: columnWidth.w,
|
||||||
|
width: columnWidth.w,
|
||||||
|
child: ColoredBox(color: Colors.white.withValues(alpha: 0.24)),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: headerHeight.w,
|
||||||
|
child: _tableLine(width: tableWidth),
|
||||||
|
),
|
||||||
|
Positioned.fill(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: headerHeight.w,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
_tableHeader("Level", columnWidth),
|
||||||
|
_tableHeader("Intimacy Level", columnWidth),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
for (final row in rows) _buildRightsLevelRow(row),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _tableHeader(String value, double width) {
|
||||||
|
return SizedBox(
|
||||||
|
width: width.w,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
value,
|
||||||
|
textScaler: TextScaler.noScaling,
|
||||||
|
style: _bodyStyle(fontSize: 12, lineHeight: 1.2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRightsLevelRow(_CpRightsLevel row) {
|
||||||
|
const tableWidth = 351.0;
|
||||||
|
const columnWidth = tableWidth / 2;
|
||||||
|
const rowHeight = 77.0;
|
||||||
|
return SizedBox(
|
||||||
|
height: rowHeight.w,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned.fill(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
_spanCell(row.level, width: columnWidth, height: rowHeight),
|
||||||
|
_spanCell(row.intimacy, width: columnWidth, height: rowHeight),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: _tableLine(width: tableWidth),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _spanCell(
|
||||||
|
String value, {
|
||||||
|
required double width,
|
||||||
|
required double height,
|
||||||
|
}) {
|
||||||
|
return SizedBox(
|
||||||
|
width: width.w,
|
||||||
|
height: height.w,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
value,
|
||||||
|
textScaler: TextScaler.noScaling,
|
||||||
|
style: _bodyStyle(fontSize: 12, lineHeight: 1.2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _tableLine({required double width}) {
|
||||||
|
return Container(width: width.w, height: 1.w, color: Colors.white);
|
||||||
|
}
|
||||||
|
|
||||||
|
double _rightsIndicatorWidth(String label, TextStyle style) {
|
||||||
|
final painter = TextPainter(
|
||||||
|
text: TextSpan(text: label, style: style),
|
||||||
|
textDirection: Directionality.of(context),
|
||||||
|
textScaler: TextScaler.noScaling,
|
||||||
|
maxLines: 1,
|
||||||
|
)..layout();
|
||||||
|
return painter.width.clamp(16.w, 72.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _suppressRoomEffects() {
|
||||||
|
if (_roomEffectsSuppressed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_roomEffectsSuppressed = true;
|
||||||
|
try {
|
||||||
|
final rtcProvider = context.read<RtcProvider>();
|
||||||
|
_suppressedRtcProvider = rtcProvider;
|
||||||
|
_restoreRoomVisualEffects = rtcProvider.roomVisualEffectsEnabled;
|
||||||
|
rtcProvider.setRoomVisualEffectsEnabled(false);
|
||||||
|
} catch (_) {}
|
||||||
|
OverlayManager().beginSuppressFloatingScreens(
|
||||||
|
reason: _effectSuppressReason,
|
||||||
|
);
|
||||||
|
SCRoomEffectScheduler().clearDeferredTasks(reason: _effectSuppressReason);
|
||||||
|
SmartDialog.dismiss(tag: _roomRocketPagLaunchDialogTag);
|
||||||
|
SCGiftVapSvgaManager().stopPlayback();
|
||||||
|
SCGiftVapSvgaManager().pauseAnim();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _releaseRoomEffects() {
|
||||||
|
if (!_roomEffectsSuppressed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_roomEffectsSuppressed = false;
|
||||||
|
SCGiftVapSvgaManager().stopPlayback();
|
||||||
|
OverlayManager().endSuppressFloatingScreens(reason: _effectSuppressReason);
|
||||||
|
final rtcProvider = _suppressedRtcProvider;
|
||||||
|
if (rtcProvider != null && _restoreRoomVisualEffects) {
|
||||||
|
rtcProvider.setRoomVisualEffectsEnabled(true);
|
||||||
|
}
|
||||||
|
SCGiftVapSvgaManager().resumeAnim();
|
||||||
|
_suppressedRtcProvider = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CpOrnatePanel extends StatelessWidget {
|
||||||
|
const _CpOrnatePanel({
|
||||||
|
required this.assetBase,
|
||||||
|
required this.title,
|
||||||
|
required this.height,
|
||||||
|
required this.child,
|
||||||
|
});
|
||||||
|
|
||||||
|
static const Color _textColor = Color(0xff543356);
|
||||||
|
|
||||||
|
final String assetBase;
|
||||||
|
final String title;
|
||||||
|
final double height;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final middleHeight = (height - 65.w - 73.w).clamp(1.0, double.infinity);
|
||||||
|
return SizedBox(
|
||||||
|
width: 350.w,
|
||||||
|
height: height,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned.fill(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
"$assetBase/sc_cp_rights_panel_top.png",
|
||||||
|
width: 350.w,
|
||||||
|
height: 65.w,
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
Image.asset(
|
||||||
|
"$assetBase/sc_cp_rights_panel_mid.png",
|
||||||
|
width: 350.w,
|
||||||
|
height: middleHeight,
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
Image.asset(
|
||||||
|
"$assetBase/sc_cp_rights_panel_bottom.png",
|
||||||
|
width: 350.w,
|
||||||
|
height: 73.w,
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 24.w,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
textScaler: TextScaler.noScaling,
|
||||||
|
style: TextStyle(
|
||||||
|
color: _textColor,
|
||||||
|
fontSize: 16.sp,
|
||||||
|
height: 1,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 30.w,
|
||||||
|
right: 30.w,
|
||||||
|
top: 44.w,
|
||||||
|
child: Image.asset(
|
||||||
|
"$assetBase/sc_cp_rights_panel_divider.png",
|
||||||
|
height: 20.w,
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned.fill(child: child),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FaqItem extends StatelessWidget {
|
||||||
|
const _FaqItem({
|
||||||
|
required this.question,
|
||||||
|
required this.answer,
|
||||||
|
this.isLast = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
static const Color _textColor = Color(0xff543356);
|
||||||
|
static const Color _accentColor = Color(0xffff2a70);
|
||||||
|
|
||||||
|
final String question;
|
||||||
|
final String answer;
|
||||||
|
final bool isLast;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: isLast ? 0 : 8.w),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
question,
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
textScaler: TextScaler.noScaling,
|
||||||
|
style: TextStyle(
|
||||||
|
color: _accentColor,
|
||||||
|
fontSize: 12.sp,
|
||||||
|
height: 1.333,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 2.w),
|
||||||
|
Text(
|
||||||
|
answer,
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
textScaler: TextScaler.noScaling,
|
||||||
|
style: TextStyle(
|
||||||
|
color: _textColor,
|
||||||
|
fontSize: 12.sp,
|
||||||
|
height: 1.333,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CpRightsLevel {
|
||||||
|
const _CpRightsLevel({required this.level, required this.intimacy});
|
||||||
|
|
||||||
|
final String level;
|
||||||
|
final String intimacy;
|
||||||
|
}
|
||||||
@ -20,11 +20,11 @@ import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
|||||||
import 'package:yumi/shared/data_sources/sources/remote/net/api.dart';
|
import 'package:yumi/shared/data_sources/sources/remote/net/api.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart';
|
import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
||||||
|
import 'package:yumi/shared/tools/sc_cp_gift_relation_utils.dart';
|
||||||
import 'package:yumi/main.dart';
|
import 'package:yumi/main.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:yumi/app/constants/sc_room_msg_type.dart';
|
import 'package:yumi/app/constants/sc_room_msg_type.dart';
|
||||||
import 'package:yumi/app/constants/sc_screen.dart';
|
import 'package:yumi/app/constants/sc_screen.dart';
|
||||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
|
||||||
import 'package:yumi/shared/tools/sc_dialog_utils.dart';
|
import 'package:yumi/shared/tools/sc_dialog_utils.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart';
|
import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/gift_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/gift_res.dart';
|
||||||
@ -33,12 +33,12 @@ import 'package:yumi/services/general/sc_app_general_manager.dart';
|
|||||||
import 'package:yumi/services/audio/rtc_manager.dart';
|
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||||
import 'package:yumi/services/audio/rtm_manager.dart';
|
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/ui_kit/widgets/countdown_timer.dart';
|
|
||||||
import 'package:yumi/ui_kit/widgets/gift/sc_gift_combo_send_button.dart';
|
import 'package:yumi/ui_kit/widgets/gift/sc_gift_combo_send_button.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
||||||
import 'package:yumi/modules/wallet/wallet_route.dart';
|
|
||||||
import 'package:yumi/modules/gift/gift_tab_page.dart';
|
import 'package:yumi/modules/gift/gift_tab_page.dart';
|
||||||
|
import 'package:yumi/modules/gift/cp_rights/cp_rights_guide_page.dart';
|
||||||
import 'package:yumi/services/gift/room_gift_combo_send_controller.dart';
|
import 'package:yumi/services/gift/room_gift_combo_send_controller.dart';
|
||||||
|
import 'package:yumi/ui_kit/widgets/room/room_recharge_bottom_sheet.dart';
|
||||||
import '../../shared/data_sources/models/enum/sc_gift_type.dart';
|
import '../../shared/data_sources/models/enum/sc_gift_type.dart';
|
||||||
import '../../shared/data_sources/models/message/sc_floating_message.dart';
|
import '../../shared/data_sources/models/message/sc_floating_message.dart';
|
||||||
import '../../shared/business_logic/usecases/sc_fixed_width_tabIndicator.dart';
|
import '../../shared/business_logic/usecases/sc_fixed_width_tabIndicator.dart';
|
||||||
@ -52,14 +52,18 @@ class _GiftPageTabItem {
|
|||||||
|
|
||||||
class GiftPage extends StatefulWidget {
|
class GiftPage extends StatefulWidget {
|
||||||
final SocialChatUserProfile? toUser;
|
final SocialChatUserProfile? toUser;
|
||||||
|
final String? initialTabType;
|
||||||
|
|
||||||
const GiftPage({super.key, this.toUser});
|
const GiftPage({super.key, this.toUser, this.initialTabType});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<GiftPage> createState() => _GiftPageState();
|
State<GiftPage> createState() => _GiftPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||||
|
static const String _cpProfileAssetBase = "sc_images/room/cp_profile";
|
||||||
|
static const String _firstRechargeGiftEntryCard =
|
||||||
|
"sc_images/first_recharge/room_gift_entry_card.png";
|
||||||
static const String _backpackGiftTab = SCAppGeneralManager.backpackGiftTab;
|
static const String _backpackGiftTab = SCAppGeneralManager.backpackGiftTab;
|
||||||
static const Duration _comboSendBatchWindow = Duration(milliseconds: 200);
|
static const Duration _comboSendBatchWindow = Duration(milliseconds: 200);
|
||||||
|
|
||||||
@ -75,6 +79,7 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
|
|
||||||
TabController? _tabController;
|
TabController? _tabController;
|
||||||
List<String> _tabTypes = <String>[];
|
List<String> _tabTypes = <String>[];
|
||||||
|
bool _didApplyInitialTab = false;
|
||||||
|
|
||||||
/// 业务逻辑策略访问器
|
/// 业务逻辑策略访问器
|
||||||
BusinessLogicStrategy get _strategy => SCGlobalConfig.businessLogicStrategy;
|
BusinessLogicStrategy get _strategy => SCGlobalConfig.businessLogicStrategy;
|
||||||
@ -203,6 +208,9 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
number = 1;
|
number = 1;
|
||||||
noShowNumber = false;
|
noShowNumber = false;
|
||||||
giftType = _giftTypeFromTab(gift?.giftTab);
|
giftType = _giftTypeFromTab(gift?.giftTab);
|
||||||
|
if (_isCpGift(gift)) {
|
||||||
|
_normalizeCpRecipientSelection();
|
||||||
|
}
|
||||||
if (notify) {
|
if (notify) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
@ -220,7 +228,7 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
Provider.of<SCAppGeneralManager>(
|
Provider.of<SCAppGeneralManager>(
|
||||||
context,
|
context,
|
||||||
listen: false,
|
listen: false,
|
||||||
).giftList(includeCustomized: false);
|
).giftList(includeCustomized: true);
|
||||||
Provider.of<SCAppGeneralManager>(context, listen: false).giftActivityList();
|
Provider.of<SCAppGeneralManager>(context, listen: false).giftActivityList();
|
||||||
unawaited(
|
unawaited(
|
||||||
Provider.of<SCAppGeneralManager>(
|
Provider.of<SCAppGeneralManager>(
|
||||||
@ -260,6 +268,53 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
}
|
}
|
||||||
final ref = Provider.of<SCAppGeneralManager>(context, listen: false);
|
final ref = Provider.of<SCAppGeneralManager>(context, listen: false);
|
||||||
_syncSelectedGiftForTab(ref, _tabTypes[controller.index]);
|
_syncSelectedGiftForTab(ref, _tabTypes[controller.index]);
|
||||||
|
_normalizeCpRecipientSelection(notify: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get _isCurrentCpGiftTab {
|
||||||
|
final controller = _tabController;
|
||||||
|
if (controller == null || controller.index >= _tabTypes.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return _isCpGiftTab(_tabTypes[controller.index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isCpGiftTab(String tabType) => scIsCpGiftTab(tabType);
|
||||||
|
|
||||||
|
bool _isCpGift(SocialChatGiftRes? gift) => scIsCpGift(gift);
|
||||||
|
|
||||||
|
bool get _isCpRecipientSingleMode =>
|
||||||
|
_isCurrentCpGiftTab || _isCpGift(checkedGift);
|
||||||
|
|
||||||
|
void _normalizeCpRecipientSelection({bool notify = false}) {
|
||||||
|
if (widget.toUser != null ||
|
||||||
|
(!_isCurrentCpGiftTab && !_isCpGift(checkedGift))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var changed = false;
|
||||||
|
var keptSelected = false;
|
||||||
|
for (final mai in listMai) {
|
||||||
|
if (!mai.isSelect) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!keptSelected) {
|
||||||
|
keptSelected = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mai.isSelect = false;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (isAll) {
|
||||||
|
isAll = false;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (giveType == 2) {
|
||||||
|
giveType = 1;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (notify && changed && mounted) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<_GiftPageTabItem> _buildGiftTabs(
|
List<_GiftPageTabItem> _buildGiftTabs(
|
||||||
@ -270,7 +325,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
final availableTypes =
|
final availableTypes =
|
||||||
ref.giftByTab.entries
|
ref.giftByTab.entries
|
||||||
.where((entry) => entry.value.isNotEmpty)
|
.where((entry) => entry.value.isNotEmpty)
|
||||||
.where((entry) => entry.key != "CUSTOMIZED")
|
|
||||||
.where((entry) => entry.key != _backpackGiftTab)
|
.where((entry) => entry.key != _backpackGiftTab)
|
||||||
.map((entry) => entry.key)
|
.map((entry) => entry.key)
|
||||||
.toList();
|
.toList();
|
||||||
@ -312,6 +366,7 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
) {
|
) {
|
||||||
final nextTypes = tabs.map((tab) => tab.type).toList();
|
final nextTypes = tabs.map((tab) => tab.type).toList();
|
||||||
if (_tabController != null && listEquals(_tabTypes, nextTypes)) {
|
if (_tabController != null && listEquals(_tabTypes, nextTypes)) {
|
||||||
|
_applyInitialTabIfNeeded(nextTypes);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,7 +376,20 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
_tabController!.index < _tabTypes.length)
|
_tabController!.index < _tabTypes.length)
|
||||||
? _tabTypes[_tabController!.index]
|
? _tabTypes[_tabController!.index]
|
||||||
: null;
|
: null;
|
||||||
final nextIndex = currentType != null ? nextTypes.indexOf(currentType) : 0;
|
final requestedType = widget.initialTabType?.trim();
|
||||||
|
final requestedIndex =
|
||||||
|
!_didApplyInitialTab && (requestedType?.isNotEmpty ?? false)
|
||||||
|
? nextTypes.indexOf(requestedType!)
|
||||||
|
: -1;
|
||||||
|
final nextIndex =
|
||||||
|
requestedIndex >= 0
|
||||||
|
? requestedIndex
|
||||||
|
: currentType != null
|
||||||
|
? nextTypes.indexOf(currentType)
|
||||||
|
: 0;
|
||||||
|
if (requestedIndex >= 0) {
|
||||||
|
_didApplyInitialTab = true;
|
||||||
|
}
|
||||||
_tabController?.removeListener(_handleTabChanged);
|
_tabController?.removeListener(_handleTabChanged);
|
||||||
_tabController?.dispose();
|
_tabController?.dispose();
|
||||||
_tabTypes = nextTypes;
|
_tabTypes = nextTypes;
|
||||||
@ -337,6 +405,27 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _applyInitialTabIfNeeded(List<String> tabTypes) {
|
||||||
|
final requestedType = widget.initialTabType?.trim();
|
||||||
|
if (_didApplyInitialTab || requestedType == null || requestedType.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final requestedIndex = tabTypes.indexOf(requestedType);
|
||||||
|
final controller = _tabController;
|
||||||
|
if (requestedIndex < 0 || controller == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_didApplyInitialTab = true;
|
||||||
|
if (controller.index != requestedIndex) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!mounted || _tabController != controller) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
controller.animateTo(requestedIndex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _syncSelectedGiftForTab(
|
void _syncSelectedGiftForTab(
|
||||||
SCAppGeneralManager ref,
|
SCAppGeneralManager ref,
|
||||||
String tabType, {
|
String tabType, {
|
||||||
@ -351,11 +440,17 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
void _ensureCurrentTabSelection(SCAppGeneralManager ref, String tabType) {
|
void _ensureCurrentTabSelection(SCAppGeneralManager ref, String tabType) {
|
||||||
final gifts = ref.giftByTab[tabType] ?? const <SocialChatGiftRes>[];
|
final gifts = ref.giftByTab[tabType] ?? const <SocialChatGiftRes>[];
|
||||||
if (_matchesCurrentGift(tabType, gifts)) {
|
if (_matchesCurrentGift(tabType, gifts)) {
|
||||||
|
if (_isCpGiftTab(tabType)) {
|
||||||
|
_normalizeCpRecipientSelection();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final gift = _resolveSelectedGift(tabType, gifts);
|
final gift = _resolveSelectedGift(tabType, gifts);
|
||||||
_selectedGiftByTab[tabType] = gift;
|
_selectedGiftByTab[tabType] = gift;
|
||||||
_applyGiftSelection(gift, notify: false);
|
_applyGiftSelection(gift, notify: false);
|
||||||
|
if (_isCpGiftTab(tabType)) {
|
||||||
|
_normalizeCpRecipientSelection();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SocialChatGiftRes? _resolveSelectedGift(
|
SocialChatGiftRes? _resolveSelectedGift(
|
||||||
@ -542,6 +637,19 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
return errorText.contains('INSUFFICIENT_BALANCE');
|
return errorText.contains('INSUFFICIENT_BALANCE');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isInsufficientMoneyError(Object error, String resolvedMessage) {
|
||||||
|
final errorText =
|
||||||
|
'${_readBackendErrorText(error)} $resolvedMessage'.toLowerCase();
|
||||||
|
return errorText.contains('no enough money') ||
|
||||||
|
errorText.contains('not enough money') ||
|
||||||
|
errorText.contains('insufficient money') ||
|
||||||
|
errorText.contains('insufficient balance') ||
|
||||||
|
errorText.contains('insufficient_balance') ||
|
||||||
|
errorText.contains('balance not enough') ||
|
||||||
|
errorText.contains('not enough gold') ||
|
||||||
|
errorText.contains('insufficient gold');
|
||||||
|
}
|
||||||
|
|
||||||
String _readBackendErrorText(Object error) {
|
String _readBackendErrorText(Object error) {
|
||||||
final values = <String>[];
|
final values = <String>[];
|
||||||
void addValue(dynamic value) {
|
void addValue(dynamic value) {
|
||||||
@ -584,89 +692,34 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
if (tabController == null) {
|
if (tabController == null) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
final currentTabType = giftTabs[tabController.index].type;
|
||||||
|
final isCpTab = currentTabType == "CP";
|
||||||
|
final showFirstRechargeEntry =
|
||||||
|
!SCGlobalConfig.isReview &&
|
||||||
|
!isCpTab &&
|
||||||
|
ref.shouldShowFirstRechargeReward;
|
||||||
_ensureCurrentTabSelection(ref, giftTabs[tabController.index].type);
|
_ensureCurrentTabSelection(ref, giftTabs[tabController.index].type);
|
||||||
|
final isCpRecipientSingleMode = isCpTab || _isCpGift(checkedGift);
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
SCGlobalConfig.isReview
|
|
||||||
? Container()
|
|
||||||
: ((AccountStorage()
|
|
||||||
.getCurrentUser()
|
|
||||||
?.userProfile
|
|
||||||
?.firstRecharge ??
|
|
||||||
false)
|
|
||||||
? GestureDetector(
|
|
||||||
child: Stack(
|
|
||||||
alignment: AlignmentDirectional.bottomEnd,
|
|
||||||
children: [
|
|
||||||
Transform.flip(
|
|
||||||
flipX:
|
|
||||||
SCGlobalConfig.lang == "ar"
|
|
||||||
? true
|
|
||||||
: false, // 水平翻转
|
|
||||||
flipY: false, // 垂直翻转设为 false
|
|
||||||
child: Image.asset(
|
|
||||||
_strategy
|
|
||||||
.getGiftPageFirstRechargeRoomTagIcon(),
|
|
||||||
height: 75.w,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SCGlobalConfig.lang == "ar"
|
|
||||||
? PositionedDirectional(
|
|
||||||
end: 22.w,
|
|
||||||
bottom: 38.w,
|
|
||||||
child: Image.asset(
|
|
||||||
_strategy
|
|
||||||
.getGiftPageFirstRechargeTextIcon(
|
|
||||||
'ar',
|
|
||||||
),
|
|
||||||
height: 13.w,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: PositionedDirectional(
|
|
||||||
end: 16.w,
|
|
||||||
bottom: 38.w,
|
|
||||||
child: Image.asset(
|
|
||||||
_strategy
|
|
||||||
.getGiftPageFirstRechargeTextIcon(
|
|
||||||
'en',
|
|
||||||
),
|
|
||||||
height: 13.w,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
PositionedDirectional(
|
|
||||||
end: 34.w,
|
|
||||||
bottom: 13.w,
|
|
||||||
child: CountdownTimer(
|
|
||||||
expiryDate:
|
|
||||||
DateTime.fromMillisecondsSinceEpoch(
|
|
||||||
AccountStorage()
|
|
||||||
.getCurrentUser()
|
|
||||||
?.userProfile
|
|
||||||
?.firstRechargeEndTime ??
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 12.w,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
SCDialogUtils.showFirstRechargeDialog(
|
|
||||||
navigatorKey.currentState!.context,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: Container()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
_buildGiftHead(),
|
_buildGiftHead(),
|
||||||
|
if (showFirstRechargeEntry) ...[
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 10.w),
|
||||||
|
child: _buildFirstRechargeGiftEntry(),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.w),
|
||||||
|
],
|
||||||
|
if (isCpTab) ...[
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 10.w),
|
||||||
|
child: _buildCpGiftCloseFriendEntry(),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.w),
|
||||||
|
],
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topLeft: Radius.circular(12.0),
|
topLeft: Radius.circular(12.0),
|
||||||
@ -687,22 +740,29 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
widget.toUser == null
|
widget.toUser == null
|
||||||
? Builder(
|
? Builder(
|
||||||
builder: (ct) {
|
builder: (ct) {
|
||||||
return GestureDetector(
|
return Opacity(
|
||||||
child: Image.asset(
|
opacity:
|
||||||
isAll
|
isCpRecipientSingleMode ? 0.45 : 1,
|
||||||
? "sc_images/room/sc_icon_gift_all_en.png"
|
child: GestureDetector(
|
||||||
: "sc_images/room/sc_icon_gift_all_no.png",
|
onTap:
|
||||||
width: 28.w,
|
isCpRecipientSingleMode
|
||||||
height: 28.w,
|
? null
|
||||||
|
: () {
|
||||||
|
isAll = !isAll;
|
||||||
|
for (var mai in listMai) {
|
||||||
|
mai.isSelect = isAll;
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
|
// showGiveTypeDialog(ct);
|
||||||
|
},
|
||||||
|
child: Image.asset(
|
||||||
|
!isCpRecipientSingleMode && isAll
|
||||||
|
? "sc_images/room/sc_icon_gift_all_en.png"
|
||||||
|
: "sc_images/room/sc_icon_gift_all_no.png",
|
||||||
|
width: 28.w,
|
||||||
|
height: 28.w,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
|
||||||
isAll = !isAll;
|
|
||||||
for (var mai in listMai) {
|
|
||||||
mai.isSelect = isAll;
|
|
||||||
}
|
|
||||||
setState(() {});
|
|
||||||
// showGiveTypeDialog(ct);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -807,14 +867,7 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
children: [
|
children: [
|
||||||
SizedBox(width: 15.w),
|
SizedBox(width: 15.w),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: _openRoomRechargeBottomSheet,
|
||||||
SmartDialog.dismiss(tag: "showGiftControl");
|
|
||||||
SCNavigatorUtils.push(
|
|
||||||
navigatorKey.currentState!.context,
|
|
||||||
WalletRoute.recharge,
|
|
||||||
replace: false,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
vertical: 8.w,
|
vertical: 8.w,
|
||||||
@ -1042,6 +1095,17 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
// 是否选中
|
// 是否选中
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
if (_isCpRecipientSingleMode) {
|
||||||
|
final shouldSelect = !shead.isSelect;
|
||||||
|
for (final mai in listMai) {
|
||||||
|
mai.isSelect = false;
|
||||||
|
}
|
||||||
|
shead.isSelect = shouldSelect;
|
||||||
|
isAll = false;
|
||||||
|
giveType = 1;
|
||||||
|
setState(() {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
isAll = true;
|
isAll = true;
|
||||||
for (var mai in listMai) {
|
for (var mai in listMai) {
|
||||||
if (shead.mic?.user?.id == mai.mic?.user?.id) {
|
if (shead.mic?.user?.id == mai.mic?.user?.id) {
|
||||||
@ -1143,18 +1207,40 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
|
|
||||||
///选中所有在座位上的用户
|
///选中所有在座位上的用户
|
||||||
void selecteAllMicUsers() {
|
void selecteAllMicUsers() {
|
||||||
|
if (_isCpRecipientSingleMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (var mai in listMai) {
|
for (var mai in listMai) {
|
||||||
mai.isSelect = true;
|
mai.isSelect = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_GiftSendRequest? _buildGiftSendRequest() {
|
_GiftSendRequest? _buildGiftSendRequest() {
|
||||||
|
final selectedGift = checkedGift;
|
||||||
|
if (selectedGift == null) {
|
||||||
|
_giftFxLog(
|
||||||
|
'tap send aborted reason=no_selected_gift '
|
||||||
|
'giveType=$giveType',
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final isCpGiftRequest = _isCpGift(selectedGift);
|
||||||
List<String> acceptUserIds = [];
|
List<String> acceptUserIds = [];
|
||||||
List<MicRes> acceptUsers = [];
|
List<MicRes> acceptUsers = [];
|
||||||
|
|
||||||
if (widget.toUser != null) {
|
if (widget.toUser != null) {
|
||||||
acceptUserIds.add(widget.toUser?.id ?? "");
|
acceptUserIds.add(widget.toUser?.id ?? "");
|
||||||
acceptUsers.add(MicRes(user: widget.toUser));
|
acceptUsers.add(MicRes(user: widget.toUser));
|
||||||
|
} else if (isCpGiftRequest) {
|
||||||
|
isAll = false;
|
||||||
|
giveType = 1;
|
||||||
|
for (final mu in listMai) {
|
||||||
|
if (mu.isSelect) {
|
||||||
|
acceptUsers.add(mu.mic!);
|
||||||
|
acceptUserIds.add(mu.mic!.user!.id ?? "");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
///所有在线
|
///所有在线
|
||||||
if (giveType == 2) {
|
if (giveType == 2) {
|
||||||
@ -1183,15 +1269,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
SCTts.show(SCAppLocalizations.of(context)!.pleaseSelectTheRecipient);
|
SCTts.show(SCAppLocalizations.of(context)!.pleaseSelectTheRecipient);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final selectedGift = checkedGift;
|
|
||||||
if (selectedGift == null) {
|
|
||||||
_giftFxLog(
|
|
||||||
'tap send aborted reason=no_selected_gift '
|
|
||||||
'acceptUserIds=${acceptUserIds.join(",")} '
|
|
||||||
'giveType=$giveType',
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final selectedNumber = number;
|
final selectedNumber = number;
|
||||||
final roomId = rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "";
|
final roomId = rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "";
|
||||||
final roomAccount =
|
final roomAccount =
|
||||||
@ -1472,6 +1549,7 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
'roomId=${request.roomId} '
|
'roomId=${request.roomId} '
|
||||||
'elapsedMs=${stopwatch.elapsedMilliseconds}',
|
'elapsedMs=${stopwatch.elapsedMilliseconds}',
|
||||||
);
|
);
|
||||||
|
_showLocalCpInviteWaitingAfterGift(request, sentUsers);
|
||||||
if (sentUsers.isNotEmpty) {
|
if (sentUsers.isNotEmpty) {
|
||||||
final giftBatchId = _createGiftBatchId(request);
|
final giftBatchId = _createGiftBatchId(request);
|
||||||
sendGiftMsg(
|
sendGiftMsg(
|
||||||
@ -1483,6 +1561,7 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
await _refreshGiftBackpack();
|
await _refreshGiftBackpack();
|
||||||
|
_refreshCpStateAfterGift(request, profileManager);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1494,6 +1573,7 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
request.quantity,
|
request.quantity,
|
||||||
false,
|
false,
|
||||||
roomId: request.roomId,
|
roomId: request.roomId,
|
||||||
|
relationType: request.cpRelationType,
|
||||||
)
|
)
|
||||||
: await repository.giveGift(
|
: await repository.giveGift(
|
||||||
request.acceptUserIds,
|
request.acceptUserIds,
|
||||||
@ -1501,6 +1581,7 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
request.quantity,
|
request.quantity,
|
||||||
false,
|
false,
|
||||||
roomId: request.roomId,
|
roomId: request.roomId,
|
||||||
|
relationType: request.cpRelationType,
|
||||||
);
|
);
|
||||||
_giftFxLog(
|
_giftFxLog(
|
||||||
'$requestName success trigger=$trigger '
|
'$requestName success trigger=$trigger '
|
||||||
@ -1517,6 +1598,7 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
'balance=$result '
|
'balance=$result '
|
||||||
'elapsedMs=${stopwatch.elapsedMilliseconds}',
|
'elapsedMs=${stopwatch.elapsedMilliseconds}',
|
||||||
);
|
);
|
||||||
|
_showLocalCpInviteWaitingAfterGift(request, request.acceptUsers);
|
||||||
if (request.isLuckyGiftRequest) {
|
if (request.isLuckyGiftRequest) {
|
||||||
final giftBatchId = _createGiftBatchId(request);
|
final giftBatchId = _createGiftBatchId(request);
|
||||||
_showLocalLuckyGiftFeedback(
|
_showLocalLuckyGiftFeedback(
|
||||||
@ -1544,6 +1626,7 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
profileManager?.updateBalance(result);
|
profileManager?.updateBalance(result);
|
||||||
|
_refreshCpStateAfterGift(request, profileManager);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
final errorMessage =
|
final errorMessage =
|
||||||
request.isBackpackGiftRequest && _isBackpackQuantityError(e)
|
request.isBackpackGiftRequest && _isBackpackQuantityError(e)
|
||||||
@ -1564,10 +1647,83 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
if (request.isBackpackGiftRequest) {
|
if (request.isBackpackGiftRequest) {
|
||||||
unawaited(_refreshGiftBackpack());
|
unawaited(_refreshGiftBackpack());
|
||||||
}
|
}
|
||||||
|
if (!request.isBackpackGiftRequest &&
|
||||||
|
_isInsufficientMoneyError(e, errorMessage)) {
|
||||||
|
RoomGiftComboSendController().hide();
|
||||||
|
RoomRechargeBottomSheet.show(
|
||||||
|
navigatorKey.currentState?.context ?? context,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
SCTts.show(errorMessage);
|
SCTts.show(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _refreshCpStateAfterGift(
|
||||||
|
_GiftSendRequest request,
|
||||||
|
SocialChatUserProfileManager? profileManager,
|
||||||
|
) {
|
||||||
|
if (!_isCpGift(request.gift)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unawaited(profileManager?.refreshLoadedCpProfiles());
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showLocalCpInviteWaitingAfterGift(
|
||||||
|
_GiftSendRequest request,
|
||||||
|
List<MicRes> sentUsers,
|
||||||
|
) {
|
||||||
|
if (!_isCpGift(request.gift)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final relationType = request.cpRelationType;
|
||||||
|
if (relationType == null || relationType.trim().isEmpty) {
|
||||||
|
_giftFxLog(
|
||||||
|
'local cp waiting skipped reason=no_relation_type '
|
||||||
|
'giftId=${request.gift.id} acceptUserIds=${request.acceptUserIds}',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sentUsers.isEmpty) {
|
||||||
|
_giftFxLog(
|
||||||
|
'local cp waiting skipped reason=no_sent_users '
|
||||||
|
'giftId=${request.gift.id} relationType=$relationType',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final receiverProfile = sentUsers.first.user;
|
||||||
|
if (receiverProfile == null) {
|
||||||
|
_giftFxLog(
|
||||||
|
'local cp waiting skipped reason=no_receiver_profile '
|
||||||
|
'giftId=${request.gift.id} relationType=$relationType',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final context = navigatorKey.currentState?.context;
|
||||||
|
if (context == null) {
|
||||||
|
_giftFxLog(
|
||||||
|
'local cp waiting skipped reason=no_context '
|
||||||
|
'giftId=${request.gift.id} relationType=$relationType',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_giftFxLog(
|
||||||
|
'local cp waiting trigger '
|
||||||
|
'giftId=${request.gift.id} '
|
||||||
|
'relationType=$relationType '
|
||||||
|
'receiverId=${receiverProfile.id}',
|
||||||
|
);
|
||||||
|
unawaited(
|
||||||
|
Provider.of<RtmProvider>(
|
||||||
|
context,
|
||||||
|
listen: false,
|
||||||
|
).showLocalCpGiftInviteWaiting(
|
||||||
|
receiverProfile: receiverProfile,
|
||||||
|
relationType: relationType,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<MicRes>> _giveBackpackGiftToRecipients(
|
Future<List<MicRes>> _giveBackpackGiftToRecipients(
|
||||||
SCChatRoomRepository repository,
|
SCChatRoomRepository repository,
|
||||||
_GiftSendRequest request,
|
_GiftSendRequest request,
|
||||||
@ -1589,6 +1745,7 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
roomId: request.roomId,
|
roomId: request.roomId,
|
||||||
seatIndex:
|
seatIndex:
|
||||||
acceptUser?.micIndex ?? _resolveSeatIndexForUserId(acceptUserId),
|
acceptUser?.micIndex ?? _resolveSeatIndexForUserId(acceptUserId),
|
||||||
|
relationType: request.cpRelationType,
|
||||||
);
|
);
|
||||||
if (acceptUser != null) {
|
if (acceptUser != null) {
|
||||||
sentUsers.add(acceptUser);
|
sentUsers.add(acceptUser);
|
||||||
@ -1699,7 +1856,7 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
num coins = (gift.giftCandy ?? 0) * quantity;
|
num coins = (gift.giftCandy ?? 0) * quantity;
|
||||||
if (coins > 9999) {
|
if (coins > 9999 && !_isCpGift(gift)) {
|
||||||
var fMsg = SCFloatingMessage(
|
var fMsg = SCFloatingMessage(
|
||||||
type: 1,
|
type: 1,
|
||||||
userId: AccountStorage().getCurrentUser()?.userProfile?.id ?? "",
|
userId: AccountStorage().getCurrentUser()?.userProfile?.id ?? "",
|
||||||
@ -1713,6 +1870,7 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
toUserAvatarUrl: u.user?.userAvatar ?? "",
|
toUserAvatarUrl: u.user?.userAvatar ?? "",
|
||||||
giftUrl: gift.giftPhoto ?? "",
|
giftUrl: gift.giftPhoto ?? "",
|
||||||
giftId: gift.id,
|
giftId: gift.id,
|
||||||
|
giftTab: _floatingGiftTabFor(gift),
|
||||||
roomId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
roomId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||||||
coins: coins,
|
coins: coins,
|
||||||
number: quantity,
|
number: quantity,
|
||||||
@ -1741,14 +1899,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
'animationCount=$animationCount',
|
'animationCount=$animationCount',
|
||||||
);
|
);
|
||||||
SCGiftVapSvgaManager().play(gift.giftSourceUrl!);
|
SCGiftVapSvgaManager().play(gift.giftSourceUrl!);
|
||||||
} else if (SCGlobalConfig.isLowPerformanceDevice &&
|
|
||||||
(rtcProvider?.shouldShowRoomVisualEffects ?? false)) {
|
|
||||||
_enqueueLowPerformanceGiftCompensation(
|
|
||||||
gift: gift,
|
|
||||||
receiver: u.user,
|
|
||||||
quantity: quantity,
|
|
||||||
coins: coins,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
_giftFxLog(
|
_giftFxLog(
|
||||||
'skip local play because visual effects disabled '
|
'skip local play because visual effects disabled '
|
||||||
@ -1775,51 +1925,10 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _enqueueLowPerformanceGiftCompensation({
|
|
||||||
required SocialChatGiftRes gift,
|
|
||||||
required SocialChatUserProfile? receiver,
|
|
||||||
required int quantity,
|
|
||||||
required num coins,
|
|
||||||
}) {
|
|
||||||
final sender = AccountStorage().getCurrentUser()?.userProfile;
|
|
||||||
final roomId = rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "";
|
|
||||||
_enqueueGiftFloatingMessage(
|
|
||||||
SCFloatingMessage(
|
|
||||||
type: 1,
|
|
||||||
priority: 900,
|
|
||||||
userId: sender?.id ?? "",
|
|
||||||
toUserId: receiver?.id ?? "",
|
|
||||||
userAvatarUrl: sender?.userAvatar ?? "",
|
|
||||||
toUserAvatarUrl: receiver?.userAvatar ?? "",
|
|
||||||
userName: sender?.userNickname ?? "",
|
|
||||||
toUserName: receiver?.userNickname ?? "",
|
|
||||||
giftUrl: gift.giftPhoto ?? "",
|
|
||||||
giftId: gift.id,
|
|
||||||
number: quantity,
|
|
||||||
coins: coins,
|
|
||||||
roomId: roomId,
|
|
||||||
),
|
|
||||||
dedupKey: _giftFloatingDedupKey(
|
|
||||||
roomId: roomId,
|
|
||||||
senderId: sender?.id,
|
|
||||||
receiverId: receiver?.id,
|
|
||||||
giftId: gift.id,
|
|
||||||
quantity: quantity,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _enqueueGiftFloatingMessage(
|
void _enqueueGiftFloatingMessage(
|
||||||
SCFloatingMessage message, {
|
SCFloatingMessage message, {
|
||||||
String? dedupKey,
|
String? dedupKey,
|
||||||
}) {
|
}) {
|
||||||
if (SCGlobalConfig.isLowPerformanceDevice) {
|
|
||||||
OverlayManager().addLowPerformanceCompensationMessage(
|
|
||||||
message,
|
|
||||||
dedupKey: dedupKey,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
OverlayManager().addMessage(message);
|
OverlayManager().addMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1840,6 +1949,13 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
].join("|");
|
].join("|");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _floatingGiftTabFor(SocialChatGiftRes gift) {
|
||||||
|
if (_isCpGift(gift)) {
|
||||||
|
return scCpRelationTypeForGift(gift);
|
||||||
|
}
|
||||||
|
return gift.giftTab ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
bool _supportsComboFeedback(SocialChatGiftRes gift) {
|
bool _supportsComboFeedback(SocialChatGiftRes gift) {
|
||||||
final giftTab = (gift.giftTab ?? '').trim();
|
final giftTab = (gift.giftTab ?? '').trim();
|
||||||
return giftTab == "LUCK" ||
|
return giftTab == "LUCK" ||
|
||||||
@ -1848,6 +1964,76 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
giftTab == SCGiftType.MAGIC.name;
|
giftTab == SCGiftType.MAGIC.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildCpGiftCloseFriendEntry() {
|
||||||
|
return Semantics(
|
||||||
|
button: true,
|
||||||
|
label: "Gifts for Close Friends",
|
||||||
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: _openCpRightsGuide,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 355.w,
|
||||||
|
height: 46.w,
|
||||||
|
child: Image.asset(
|
||||||
|
"$_cpProfileAssetBase/sc_cp_profile_gift_entry_card.png",
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFirstRechargeGiftEntry() {
|
||||||
|
return Semantics(
|
||||||
|
button: true,
|
||||||
|
label: "First recharge offer",
|
||||||
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: _openFirstRechargeDialog,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 355.w,
|
||||||
|
height: 46.w,
|
||||||
|
child: Image.asset(_firstRechargeGiftEntryCard, fit: BoxFit.fill),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _openFirstRechargeDialog() {
|
||||||
|
SmartDialog.dismiss(tag: "showGiftControl");
|
||||||
|
Future<void>.delayed(const Duration(milliseconds: 80), () {
|
||||||
|
final navigator = navigatorKey.currentState;
|
||||||
|
if (navigator == null || !navigator.mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SCDialogUtils.showFirstRechargeDialog(navigator.context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _openRoomRechargeBottomSheet() {
|
||||||
|
SmartDialog.dismiss(tag: "showGiftControl");
|
||||||
|
Future<void>.delayed(const Duration(milliseconds: 80), () {
|
||||||
|
final navigator = navigatorKey.currentState;
|
||||||
|
if (navigator == null || !navigator.mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RoomRechargeBottomSheet.show(navigator.context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _openCpRightsGuide() {
|
||||||
|
SmartDialog.dismiss(tag: "showGiftControl");
|
||||||
|
Future<void>.delayed(const Duration(milliseconds: 80), () {
|
||||||
|
final navigator = navigatorKey.currentState;
|
||||||
|
if (navigator == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigator.push(
|
||||||
|
MaterialPageRoute<void>(builder: (_) => const CpRightsGuidePage()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildSendButton() {
|
Widget _buildSendButton() {
|
||||||
return SCGiftComboSendButton(
|
return SCGiftComboSendButton(
|
||||||
label: SCAppLocalizations.of(context)!.send,
|
label: SCAppLocalizations.of(context)!.send,
|
||||||
@ -2110,6 +2296,44 @@ class _GiftSendRequest {
|
|||||||
return isLuckyGiftRequest ? 'giveLuckyGift' : 'giveGift';
|
return isLuckyGiftRequest ? 'giveLuckyGift' : 'giveGift';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? get cpRelationType {
|
||||||
|
if (!scIsCpGift(gift)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (isSelfRecipientRequest) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return scCpRelationTypeForGift(gift);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isSelfRecipientRequest {
|
||||||
|
final currentProfile = AccountStorage().getCurrentUser()?.userProfile;
|
||||||
|
final currentKeys = _profileIdentityKeys(currentProfile);
|
||||||
|
if (currentKeys.isEmpty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (final userId in acceptUserIds) {
|
||||||
|
if (currentKeys.contains(userId.trim())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (final acceptUser in acceptUsers) {
|
||||||
|
final acceptKeys = _profileIdentityKeys(acceptUser.user);
|
||||||
|
if (acceptKeys.any(currentKeys.contains)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> _profileIdentityKeys(SocialChatUserProfile? profile) {
|
||||||
|
return <String>{
|
||||||
|
profile?.id?.trim() ?? "",
|
||||||
|
profile?.account?.trim() ?? "",
|
||||||
|
profile?.getID().trim() ?? "",
|
||||||
|
}..remove("");
|
||||||
|
}
|
||||||
|
|
||||||
String get batchKey {
|
String get batchKey {
|
||||||
final sortedAcceptUserIds = List<String>.from(acceptUserIds)..sort();
|
final sortedAcceptUserIds = List<String>.from(acceptUserIds)..sort();
|
||||||
final type =
|
final type =
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:yumi/app_localizations.dart';
|
import 'package:yumi/app_localizations.dart';
|
||||||
import 'package:yumi/app/config/business_logic_strategy.dart';
|
import 'package:yumi/app/config/business_logic_strategy.dart';
|
||||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||||
|
import 'package:yumi/shared/tools/sc_cp_gift_relation_utils.dart';
|
||||||
|
|
||||||
enum _GiftGridPageStatus { idle, loading, ready }
|
enum _GiftGridPageStatus { idle, loading, ready }
|
||||||
|
|
||||||
@ -447,73 +448,84 @@ class _GiftTabPageState extends State<GiftTabPage>
|
|||||||
)
|
)
|
||||||
: GestureDetector(
|
: GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
child: Container(
|
child: Stack(
|
||||||
decoration: BoxDecoration(
|
children: [
|
||||||
borderRadius: BorderRadius.circular(12.w),
|
Container(
|
||||||
border: Border.all(
|
decoration: BoxDecoration(
|
||||||
color:
|
borderRadius: BorderRadius.circular(12.w),
|
||||||
checkedIndex == ref.giftByTab[widget.type]!.indexOf(gift)
|
border: Border.all(
|
||||||
? SocialChatTheme.primaryLight
|
color:
|
||||||
: Colors.transparent,
|
checkedIndex ==
|
||||||
width: 1.w,
|
ref.giftByTab[widget.type]!.indexOf(gift)
|
||||||
),
|
? SocialChatTheme.primaryLight
|
||||||
),
|
: Colors.transparent,
|
||||||
child: Padding(
|
width: 1.w,
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
vertical: _kGiftCardVerticalPadding.w,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
netImage(
|
|
||||||
url: gift.giftPhoto ?? "",
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
width: 48.w,
|
|
||||||
height: 48.w,
|
|
||||||
loadingWidget: _buildGiftCoverLoading(),
|
|
||||||
errorWidget: _buildGiftCoverNoData(),
|
|
||||||
),
|
),
|
||||||
SizedBox(height: _kGiftIconPriceSpacing.w),
|
),
|
||||||
SizedBox(
|
child: Padding(
|
||||||
height: _kGiftPriceRowHeight.w,
|
padding: EdgeInsets.symmetric(
|
||||||
child: Row(
|
vertical: _kGiftCardVerticalPadding.w,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children:
|
|
||||||
_isBackpackTab
|
|
||||||
? [
|
|
||||||
text(
|
|
||||||
"x${_formatBackpackQuantity(gift)}",
|
|
||||||
fontSize: 10,
|
|
||||||
lineHeight: 1,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
textColor:
|
|
||||||
widget.isDark
|
|
||||||
? SocialChatTheme.primaryLight
|
|
||||||
: SocialChatTheme.primaryColor,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
Image.asset(
|
|
||||||
_strategy.getGiftPageGoldCoinIcon(),
|
|
||||||
width: 14.w,
|
|
||||||
height: 14.w,
|
|
||||||
),
|
|
||||||
SizedBox(width: 3.w),
|
|
||||||
text(
|
|
||||||
"${gift.giftCandy}",
|
|
||||||
fontSize: 10,
|
|
||||||
lineHeight: 1,
|
|
||||||
textColor:
|
|
||||||
widget.isDark
|
|
||||||
? Colors.white
|
|
||||||
: Colors.black,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
netImage(
|
||||||
|
url: gift.giftPhoto ?? "",
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
width: 48.w,
|
||||||
|
height: 48.w,
|
||||||
|
loadingWidget: _buildGiftCoverLoading(),
|
||||||
|
errorWidget: _buildGiftCoverNoData(),
|
||||||
|
),
|
||||||
|
SizedBox(height: _kGiftIconPriceSpacing.w),
|
||||||
|
SizedBox(
|
||||||
|
height: _kGiftPriceRowHeight.w,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children:
|
||||||
|
_isBackpackTab
|
||||||
|
? [
|
||||||
|
text(
|
||||||
|
"x${_formatBackpackQuantity(gift)}",
|
||||||
|
fontSize: 10,
|
||||||
|
lineHeight: 1,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
textColor:
|
||||||
|
widget.isDark
|
||||||
|
? SocialChatTheme.primaryLight
|
||||||
|
: SocialChatTheme.primaryColor,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
Image.asset(
|
||||||
|
_strategy.getGiftPageGoldCoinIcon(),
|
||||||
|
width: 14.w,
|
||||||
|
height: 14.w,
|
||||||
|
),
|
||||||
|
SizedBox(width: 3.w),
|
||||||
|
text(
|
||||||
|
"${gift.giftCandy}",
|
||||||
|
fontSize: 10,
|
||||||
|
lineHeight: 1,
|
||||||
|
textColor:
|
||||||
|
widget.isDark
|
||||||
|
? Colors.white
|
||||||
|
: Colors.black,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
if (_shouldShowCpRelationBadge(gift))
|
||||||
|
PositionedDirectional(
|
||||||
|
top: 0,
|
||||||
|
end: 0,
|
||||||
|
child: _buildCpRelationBadge(gift),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -524,6 +536,68 @@ class _GiftTabPageState extends State<GiftTabPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _shouldShowCpRelationBadge(SocialChatGiftRes gift) =>
|
||||||
|
scIsCpGiftTab(widget.type) || scIsCpGift(gift);
|
||||||
|
|
||||||
|
Widget _buildCpRelationBadge(SocialChatGiftRes gift) {
|
||||||
|
final spec = _cpRelationBadgeSpec(scCpRelationTypeForGift(gift));
|
||||||
|
return Container(
|
||||||
|
width: spec.width.w,
|
||||||
|
height: 14.w,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: spec.backgroundColor,
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topRight: Radius.circular(7.w),
|
||||||
|
bottomLeft: Radius.circular(7.w),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
spec.label,
|
||||||
|
maxLines: 1,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
overflow: TextOverflow.visible,
|
||||||
|
style: TextStyle(
|
||||||
|
color: spec.color,
|
||||||
|
fontFamily: spec.fontFamily,
|
||||||
|
fontSize: 10.sp,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
height: 1,
|
||||||
|
letterSpacing: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_CpRelationBadgeSpec _cpRelationBadgeSpec(String relationType) {
|
||||||
|
switch (relationType) {
|
||||||
|
case "BROTHER":
|
||||||
|
return const _CpRelationBadgeSpec(
|
||||||
|
label: "brother",
|
||||||
|
width: 44,
|
||||||
|
color: Color(0xFF024591),
|
||||||
|
backgroundColor: Color(0xFFCEE9FF),
|
||||||
|
fontFamily: "Source Han Sans SC",
|
||||||
|
);
|
||||||
|
case "SISTERS":
|
||||||
|
return const _CpRelationBadgeSpec(
|
||||||
|
label: "sister",
|
||||||
|
width: 39,
|
||||||
|
color: Color(0xFF880291),
|
||||||
|
backgroundColor: Color(0xFFEECEFF),
|
||||||
|
fontFamily: "Source Han Sans SC",
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return const _CpRelationBadgeSpec(
|
||||||
|
label: "cp",
|
||||||
|
width: 26,
|
||||||
|
color: Color(0xFFFF59B7),
|
||||||
|
backgroundColor: Color(0xFFFFCEEA),
|
||||||
|
fontFamily: "Inter",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool get _isBackpackTab => widget.type == SCAppGeneralManager.backpackGiftTab;
|
bool get _isBackpackTab => widget.type == SCAppGeneralManager.backpackGiftTab;
|
||||||
|
|
||||||
String _formatBackpackQuantity(SocialChatGiftRes gift) {
|
String _formatBackpackQuantity(SocialChatGiftRes gift) {
|
||||||
@ -557,3 +631,19 @@ class _GiftTabPageState extends State<GiftTabPage>
|
|||||||
return Row(mainAxisAlignment: MainAxisAlignment.center, children: list);
|
return Row(mainAxisAlignment: MainAxisAlignment.center, children: list);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _CpRelationBadgeSpec {
|
||||||
|
const _CpRelationBadgeSpec({
|
||||||
|
required this.label,
|
||||||
|
required this.width,
|
||||||
|
required this.color,
|
||||||
|
required this.backgroundColor,
|
||||||
|
required this.fontFamily,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String label;
|
||||||
|
final double width;
|
||||||
|
final Color color;
|
||||||
|
final Color backgroundColor;
|
||||||
|
final String fontFamily;
|
||||||
|
}
|
||||||
|
|||||||
@ -15,7 +15,9 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
|
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
|
||||||
|
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
|
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
|
||||||
|
import 'package:yumi/shared/tools/sc_dialog_utils.dart';
|
||||||
import 'package:yumi/services/general/sc_app_general_manager.dart';
|
import 'package:yumi/services/general/sc_app_general_manager.dart';
|
||||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/svga/sc_svga_asset_widget.dart';
|
import 'package:yumi/ui_kit/widgets/svga/sc_svga_asset_widget.dart';
|
||||||
@ -44,11 +46,13 @@ class _SCIndexPageState extends State<SCIndexPage>
|
|||||||
final List<Widget> _pages = [];
|
final List<Widget> _pages = [];
|
||||||
final Set<int> _builtPageIndexes = <int>{0};
|
final Set<int> _builtPageIndexes = <int>{0};
|
||||||
final List<BottomNavigationBarItem> _bottomItems = [];
|
final List<BottomNavigationBarItem> _bottomItems = [];
|
||||||
|
final ValueNotifier<int> _currentIndexNotifier = ValueNotifier<int>(0);
|
||||||
final ValueNotifier<int> _taskClaimableCount = ValueNotifier<int>(0);
|
final ValueNotifier<int> _taskClaimableCount = ValueNotifier<int>(0);
|
||||||
SCAppGeneralManager? generalProvider;
|
SCAppGeneralManager? generalProvider;
|
||||||
Locale? _lastLocale;
|
Locale? _lastLocale;
|
||||||
bool _hasShownEntryDialogs = false;
|
bool _hasShownEntryDialogs = false;
|
||||||
bool _hasShownRegisterRewardDialog = false;
|
bool _hasShownRegisterRewardDialog = false;
|
||||||
|
bool _hasRequestedFirstRechargeDialog = false;
|
||||||
bool _isShowingDailySignInDialog = false;
|
bool _isShowingDailySignInDialog = false;
|
||||||
bool _showRegisterRewardAfterDailySignIn = false;
|
bool _showRegisterRewardAfterDailySignIn = false;
|
||||||
bool _isOpeningFirstRegisterRoomGame = false;
|
bool _isOpeningFirstRegisterRoomGame = false;
|
||||||
@ -93,6 +97,7 @@ class _SCIndexPageState extends State<SCIndexPage>
|
|||||||
routeObserver.unsubscribe(this);
|
routeObserver.unsubscribe(this);
|
||||||
OverlayManager().setHomeRootTabsVisible(false);
|
OverlayManager().setHomeRootTabsVisible(false);
|
||||||
_registerRewardSubscription?.cancel();
|
_registerRewardSubscription?.cancel();
|
||||||
|
_currentIndexNotifier.dispose();
|
||||||
_taskClaimableCount.dispose();
|
_taskClaimableCount.dispose();
|
||||||
WakelockPlus.disable();
|
WakelockPlus.disable();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
@ -270,7 +275,7 @@ class _SCIndexPageState extends State<SCIndexPage>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
_pages.add(SCMessagePage());
|
_pages.add(SCMessagePage());
|
||||||
_pages.add(MePage2());
|
_pages.add(MePage2(currentIndexListenable: _currentIndexNotifier));
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCachedBody() {
|
Widget _buildCachedBody() {
|
||||||
@ -297,6 +302,7 @@ class _SCIndexPageState extends State<SCIndexPage>
|
|||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_currentIndex = index;
|
_currentIndex = index;
|
||||||
|
_currentIndexNotifier.value = index;
|
||||||
_builtPageIndexes.add(index);
|
_builtPageIndexes.add(index);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -435,14 +441,27 @@ class _SCIndexPageState extends State<SCIndexPage>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _showDailySignInDialogIfNeeded();
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await _showEntryPopupIfNeeded();
|
await _showEntryPopupIfNeeded();
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _showFirstRechargeDialogIfNeeded();
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _openFirstRegisterRoomGameIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showDailySignInDialogIfNeeded() async {
|
||||||
final dialogData = await _loadDailySignInDialogData();
|
final dialogData = await _loadDailySignInDialogData();
|
||||||
if (!mounted || dialogData == null) {
|
if (!mounted || dialogData == null) {
|
||||||
await _openFirstRegisterRoomGameIfNeeded();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_isShowingDailySignInDialog = true;
|
_isShowingDailySignInDialog = true;
|
||||||
@ -458,7 +477,6 @@ class _SCIndexPageState extends State<SCIndexPage>
|
|||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await _openFirstRegisterRoomGameIfNeeded();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showEntryPopupIfNeeded() async {
|
Future<void> _showEntryPopupIfNeeded() async {
|
||||||
@ -471,6 +489,18 @@ class _SCIndexPageState extends State<SCIndexPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _showFirstRechargeDialogIfNeeded() async {
|
||||||
|
if (_hasRequestedFirstRechargeDialog ||
|
||||||
|
SCGlobalConfig.isReview ||
|
||||||
|
AccountStorage().getToken().isEmpty ||
|
||||||
|
!mounted ||
|
||||||
|
!(ModalRoute.of(context)?.isCurrent ?? false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_hasRequestedFirstRechargeDialog = true;
|
||||||
|
await SCDialogUtils.showFirstRechargeDialog(context, forceRefresh: true);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _waitForRegisterRewardSocketIfNeeded() async {
|
Future<void> _waitForRegisterRewardSocketIfNeeded() async {
|
||||||
if (!DataPersistence.getAwaitRegisterRewardSocket() ||
|
if (!DataPersistence.getAwaitRegisterRewardSocket() ||
|
||||||
DataPersistence.getPendingRegisterRewardDialog()) {
|
DataPersistence.getPendingRegisterRewardDialog()) {
|
||||||
|
|||||||
@ -29,8 +29,13 @@ import '../voice_room_route.dart';
|
|||||||
///编辑房间信息
|
///编辑房间信息
|
||||||
class RoomEditPage extends StatefulWidget {
|
class RoomEditPage extends StatefulWidget {
|
||||||
final String needRestCurrentRoomInfo;
|
final String needRestCurrentRoomInfo;
|
||||||
|
final RoomEntryPreviewData? setupPreviewData;
|
||||||
|
|
||||||
const RoomEditPage({super.key, this.needRestCurrentRoomInfo = "false"});
|
const RoomEditPage({
|
||||||
|
super.key,
|
||||||
|
this.needRestCurrentRoomInfo = "false",
|
||||||
|
this.setupPreviewData,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<RoomEditPage> createState() => _RoomEditPageState();
|
State<RoomEditPage> createState() => _RoomEditPageState();
|
||||||
@ -45,6 +50,7 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
|||||||
|
|
||||||
RtcProvider? rtcProvider;
|
RtcProvider? rtcProvider;
|
||||||
bool isEdit = false;
|
bool isEdit = false;
|
||||||
|
bool _isBasicInfoEditing = false;
|
||||||
final TextEditingController _roomNameController = TextEditingController();
|
final TextEditingController _roomNameController = TextEditingController();
|
||||||
final TextEditingController _roomAnnouncementController =
|
final TextEditingController _roomAnnouncementController =
|
||||||
TextEditingController();
|
TextEditingController();
|
||||||
@ -53,7 +59,7 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
|||||||
final int _roomNameMaxLength = 24;
|
final int _roomNameMaxLength = 24;
|
||||||
int _roomNameCurrentLength = 0;
|
int _roomNameCurrentLength = 0;
|
||||||
String roomCover = "";
|
String roomCover = "";
|
||||||
final int _roomAnnouncementMaxLength = 24;
|
final int _roomAnnouncementMaxLength = 200;
|
||||||
int _roomAnnouncementCurrentLength = 0;
|
int _roomAnnouncementCurrentLength = 0;
|
||||||
bool _initialRoomLocked = false;
|
bool _initialRoomLocked = false;
|
||||||
bool _isRoomPrivate = false;
|
bool _isRoomPrivate = false;
|
||||||
@ -76,17 +82,25 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
final setupPreviewData = widget.setupPreviewData;
|
||||||
_roomNameController.text =
|
_roomNameController.text =
|
||||||
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomName ?? "";
|
setupPreviewData?.roomName ??
|
||||||
|
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomName ??
|
||||||
|
"";
|
||||||
_roomNameCurrentLength = _getActualCharacterCount(_roomNameController.text);
|
_roomNameCurrentLength = _getActualCharacterCount(_roomNameController.text);
|
||||||
_roomAnnouncementController.text =
|
_roomAnnouncementController.text =
|
||||||
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomDesc ?? "";
|
setupPreviewData?.roomDesc ??
|
||||||
|
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomDesc ??
|
||||||
|
"";
|
||||||
_roomAnnouncementCurrentLength = _getActualCharacterCount(
|
_roomAnnouncementCurrentLength = _getActualCharacterCount(
|
||||||
_roomAnnouncementController.text,
|
_roomAnnouncementController.text,
|
||||||
);
|
);
|
||||||
roomCover =
|
roomCover =
|
||||||
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomCover ?? "";
|
setupPreviewData?.roomCover ??
|
||||||
|
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomCover ??
|
||||||
|
"";
|
||||||
final roomPassword =
|
final roomPassword =
|
||||||
|
setupPreviewData?.roomSetting?.password?.trim() ??
|
||||||
rtcProvider?.currenRoom?.roomProfile?.roomSetting?.password?.trim() ??
|
rtcProvider?.currenRoom?.roomProfile?.roomSetting?.password?.trim() ??
|
||||||
"";
|
"";
|
||||||
_initialRoomLocked = roomPassword.isNotEmpty;
|
_initialRoomLocked = roomPassword.isNotEmpty;
|
||||||
@ -133,21 +147,10 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
|||||||
SizedBox(height: mediaQuery.viewPadding.top),
|
SizedBox(height: mediaQuery.viewPadding.top),
|
||||||
_buildNavigationBar(context),
|
_buildNavigationBar(context),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Stack(
|
child: SingleChildScrollView(
|
||||||
children: [
|
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
||||||
SingleChildScrollView(
|
padding: EdgeInsets.only(bottom: bottomPadding + 24.w),
|
||||||
keyboardDismissBehavior:
|
child: _buildForm(context),
|
||||||
ScrollViewKeyboardDismissBehavior.onDrag,
|
|
||||||
padding: EdgeInsets.only(bottom: bottomPadding + 116.w),
|
|
||||||
child: _buildForm(context),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
left: 24.w,
|
|
||||||
right: 24.w,
|
|
||||||
bottom: bottomPadding + 14.w,
|
|
||||||
child: _buildSaveButton(context),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -195,11 +198,50 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
|||||||
height: 1,
|
height: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
PositionedDirectional(
|
||||||
|
end: 16.w,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: _buildNavigationAction(context),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildNavigationAction(BuildContext context) {
|
||||||
|
final label =
|
||||||
|
_isBasicInfoEditing || isEdit
|
||||||
|
? SCAppLocalizations.of(context)!.save
|
||||||
|
: SCAppLocalizations.of(context)!.edit;
|
||||||
|
return SCDebounceWidget(
|
||||||
|
onTap: () {
|
||||||
|
if (!_isBasicInfoEditing && !isEdit) {
|
||||||
|
setState(() {
|
||||||
|
_isBasicInfoEditing = true;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
submit(context);
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
height: 44.w,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: SocialChatTheme.primaryColor,
|
||||||
|
fontSize: 14.sp,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildForm(BuildContext context) {
|
Widget _buildForm(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
@ -211,11 +253,12 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
|||||||
height: 40.w,
|
height: 40.w,
|
||||||
hintText: "Please enter a name",
|
hintText: "Please enter a name",
|
||||||
counterText: '$_roomNameCurrentLength/$_roomNameMaxLength',
|
counterText: '$_roomNameCurrentLength/$_roomNameMaxLength',
|
||||||
|
readOnly: !_isBasicInfoEditing,
|
||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
SCAccurateLengthLimitingTextInputFormatter(_roomNameMaxLength),
|
SCAccurateLengthLimitingTextInputFormatter(_roomNameMaxLength),
|
||||||
],
|
],
|
||||||
onChanged: (_) {
|
onChanged: (_) {
|
||||||
isEdit = true;
|
_markEdited();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SizedBox(height: 16.w),
|
SizedBox(height: 16.w),
|
||||||
@ -231,30 +274,38 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
|||||||
hintText: "Please enter a name",
|
hintText: "Please enter a name",
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
expands: true,
|
expands: true,
|
||||||
|
readOnly: !_isBasicInfoEditing,
|
||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
SCAccurateLengthLimitingTextInputFormatter(
|
SCAccurateLengthLimitingTextInputFormatter(
|
||||||
_roomAnnouncementMaxLength,
|
_roomAnnouncementMaxLength,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
onChanged: (_) {
|
onChanged: (_) {
|
||||||
isEdit = true;
|
_markEdited();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SizedBox(height: 16.w),
|
SizedBox(height: 16.w),
|
||||||
_buildLockHeader(),
|
_buildLockHeader(),
|
||||||
SizedBox(height: 8.w),
|
Visibility(
|
||||||
_buildTextInput(
|
visible: _isRoomPrivate,
|
||||||
controller: _roomLockPasswordController,
|
child: Column(
|
||||||
height: 40.w,
|
children: [
|
||||||
hintText: "Please enter password",
|
SizedBox(height: 8.w),
|
||||||
keyboardType: TextInputType.visiblePassword,
|
_buildTextInput(
|
||||||
obscureText: true,
|
controller: _roomLockPasswordController,
|
||||||
onChanged: (_) {
|
height: 40.w,
|
||||||
isEdit = true;
|
hintText: "Please enter password",
|
||||||
},
|
keyboardType: TextInputType.visiblePassword,
|
||||||
|
obscureText: true,
|
||||||
|
onChanged: (_) {
|
||||||
|
_markEdited();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.w),
|
||||||
|
_buildLockTips(),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 8.w),
|
|
||||||
_buildLockTips(),
|
|
||||||
if (widget.needRestCurrentRoomInfo != "true") ...[
|
if (widget.needRestCurrentRoomInfo != "true") ...[
|
||||||
SizedBox(height: 16.w),
|
SizedBox(height: 16.w),
|
||||||
_buildSettingRow(
|
_buildSettingRow(
|
||||||
@ -340,6 +391,7 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
|||||||
TextInputType? keyboardType,
|
TextInputType? keyboardType,
|
||||||
List<TextInputFormatter>? inputFormatters,
|
List<TextInputFormatter>? inputFormatters,
|
||||||
ValueChanged<String>? onChanged,
|
ValueChanged<String>? onChanged,
|
||||||
|
bool readOnly = false,
|
||||||
}) {
|
}) {
|
||||||
final isMultiline = maxLines == null || expands;
|
final isMultiline = maxLines == null || expands;
|
||||||
return Container(
|
return Container(
|
||||||
@ -365,6 +417,9 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
|||||||
cursorColor: SocialChatTheme.primaryColor,
|
cursorColor: SocialChatTheme.primaryColor,
|
||||||
keyboardType: keyboardType,
|
keyboardType: keyboardType,
|
||||||
obscureText: obscureText,
|
obscureText: obscureText,
|
||||||
|
readOnly: readOnly,
|
||||||
|
enableInteractiveSelection: !readOnly,
|
||||||
|
showCursor: !readOnly,
|
||||||
inputFormatters: inputFormatters,
|
inputFormatters: inputFormatters,
|
||||||
maxLines: maxLines,
|
maxLines: maxLines,
|
||||||
expands: expands,
|
expands: expands,
|
||||||
@ -425,6 +480,15 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _markEdited() {
|
||||||
|
if (isEdit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
isEdit = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildSectionHeader({required String title, String? counterText}) {
|
Widget _buildSectionHeader({required String title, String? counterText}) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10.w),
|
padding: EdgeInsets.symmetric(horizontal: 10.w),
|
||||||
@ -627,34 +691,9 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSaveButton(BuildContext context) {
|
|
||||||
return SCDebounceWidget(
|
|
||||||
onTap: () {
|
|
||||||
submit(context);
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
height: 44.w,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: SocialChatTheme.primaryColor,
|
|
||||||
borderRadius: BorderRadius.circular(22.w),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
SCAppLocalizations.of(context)!.save,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.black,
|
|
||||||
fontSize: 20.sp,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
height: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleBack() {
|
void _handleBack() {
|
||||||
if (widget.needRestCurrentRoomInfo == "true") {
|
if (widget.needRestCurrentRoomInfo == "true" &&
|
||||||
|
widget.setupPreviewData == null) {
|
||||||
Provider.of<RtcProvider>(context, listen: false).currenRoom = null;
|
Provider.of<RtcProvider>(context, listen: false).currenRoom = null;
|
||||||
}
|
}
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@ -700,11 +739,35 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
|||||||
);
|
);
|
||||||
final currentRtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
final currentRtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
||||||
final currentRtmProvider = Provider.of<RtmProvider>(context, listen: false);
|
final currentRtmProvider = Provider.of<RtmProvider>(context, listen: false);
|
||||||
|
final setupPreviewData = widget.setupPreviewData;
|
||||||
|
final currentRoomProfile =
|
||||||
|
rtcProvider?.currenRoom?.roomProfile?.roomProfile;
|
||||||
|
final currentRoom = rtcProvider?.currenRoom?.roomProfile;
|
||||||
final submittedRoomName = _roomNameController.text.trim();
|
final submittedRoomName = _roomNameController.text.trim();
|
||||||
final submittedRoomDesc = _roomAnnouncementController.text.trim();
|
final submittedRoomDesc = _roomAnnouncementController.text.trim();
|
||||||
final submittedRoomCover = roomCover.trim();
|
final submittedRoomCover = roomCover.trim();
|
||||||
final submittedRoomPassword = _roomLockPasswordController.text.trim();
|
final submittedRoomPassword = _roomLockPasswordController.text.trim();
|
||||||
final roomId = rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "";
|
final roomId = setupPreviewData?.roomId ?? currentRoomProfile?.id ?? "";
|
||||||
|
final roomAccount =
|
||||||
|
setupPreviewData?.roomAccount ?? currentRoomProfile?.roomAccount ?? "";
|
||||||
|
final roomUserId = setupPreviewData?.userId ?? currentRoomProfile?.userId;
|
||||||
|
final roomSetting =
|
||||||
|
setupPreviewData?.roomSetting ?? currentRoom?.roomSetting;
|
||||||
|
final ownerProfile =
|
||||||
|
setupPreviewData?.ownerProfile ?? currentRoom?.userProfile;
|
||||||
|
final roomAdminCount =
|
||||||
|
setupPreviewData?.roomAdminCount ??
|
||||||
|
currentRoom?.roomCounter?.adminCount;
|
||||||
|
final roomMemberCount =
|
||||||
|
setupPreviewData?.roomMemberCount ??
|
||||||
|
currentRoom?.roomCounter?.memberCount;
|
||||||
|
final entrantsRole =
|
||||||
|
setupPreviewData?.entrantsRole ??
|
||||||
|
rtcProvider?.currenRoom?.entrants?.roles;
|
||||||
|
if (roomId.trim().isEmpty || roomAccount.trim().isEmpty) {
|
||||||
|
SCTts.show("Room data is not ready");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (submittedRoomName.isEmpty) {
|
if (submittedRoomName.isEmpty) {
|
||||||
SCTts.show("Room name not empty!");
|
SCTts.show("Room name not empty!");
|
||||||
return;
|
return;
|
||||||
@ -733,6 +796,9 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final mergedRoomInfo = roomInfo.copyWith(
|
final mergedRoomInfo = roomInfo.copyWith(
|
||||||
|
id: _preferNonEmpty(roomInfo.id, roomId),
|
||||||
|
roomAccount: _preferNonEmpty(roomInfo.roomAccount, roomAccount),
|
||||||
|
userId: roomInfo.userId ?? roomUserId,
|
||||||
roomCover: _preferNonEmpty(roomInfo.roomCover, submittedRoomCover),
|
roomCover: _preferNonEmpty(roomInfo.roomCover, submittedRoomCover),
|
||||||
roomName: _preferNonEmpty(roomInfo.roomName, submittedRoomName),
|
roomName: _preferNonEmpty(roomInfo.roomName, submittedRoomName),
|
||||||
roomDesc: _preferNonEmpty(roomInfo.roomDesc, submittedRoomDesc),
|
roomDesc: _preferNonEmpty(roomInfo.roomDesc, submittedRoomDesc),
|
||||||
@ -761,7 +827,7 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
|||||||
if (widget.needRestCurrentRoomInfo == "true") {
|
if (widget.needRestCurrentRoomInfo == "true") {
|
||||||
///需要创建群组
|
///需要创建群组
|
||||||
var c = await currentRtmProvider.createRoomGroup(
|
var c = await currentRtmProvider.createRoomGroup(
|
||||||
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "",
|
roomAccount,
|
||||||
mergedRoomInfo.roomName ?? submittedRoomName,
|
mergedRoomInfo.roomName ?? submittedRoomName,
|
||||||
);
|
);
|
||||||
if (!context.mounted) {
|
if (!context.mounted) {
|
||||||
@ -770,31 +836,25 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
|||||||
}
|
}
|
||||||
if (_roomGroupReadyCodes.contains(c.code)) {
|
if (_roomGroupReadyCodes.contains(c.code)) {
|
||||||
SCLoadingManager.hide();
|
SCLoadingManager.hide();
|
||||||
|
SCTts.show(SCAppLocalizations.of(context)!.createRoomSuccsess);
|
||||||
SCNavigatorUtils.goBack(context);
|
SCNavigatorUtils.goBack(context);
|
||||||
currentRtcProvider.joinVoiceRoomSession(
|
currentRtcProvider.joinVoiceRoomSession(
|
||||||
context,
|
context,
|
||||||
mergedRoomInfo.id ??
|
mergedRoomInfo.id ?? roomId,
|
||||||
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ??
|
clearRoomData: setupPreviewData == null,
|
||||||
"",
|
|
||||||
clearRoomData: true,
|
|
||||||
previewData: RoomEntryPreviewData(
|
previewData: RoomEntryPreviewData(
|
||||||
roomId:
|
roomId: mergedRoomInfo.id ?? roomId,
|
||||||
mergedRoomInfo.id ??
|
roomAccount: mergedRoomInfo.roomAccount ?? roomAccount,
|
||||||
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id,
|
userId: mergedRoomInfo.userId ?? roomUserId,
|
||||||
roomAccount:
|
|
||||||
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount,
|
|
||||||
userId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.userId,
|
|
||||||
roomCover: mergedRoomInfo.roomCover,
|
roomCover: mergedRoomInfo.roomCover,
|
||||||
roomName: mergedRoomInfo.roomName ?? submittedRoomName,
|
roomName: mergedRoomInfo.roomName ?? submittedRoomName,
|
||||||
roomDesc: mergedRoomInfo.roomDesc ?? submittedRoomDesc,
|
roomDesc: mergedRoomInfo.roomDesc ?? submittedRoomDesc,
|
||||||
roomSetting: rtcProvider?.currenRoom?.roomProfile?.roomSetting,
|
roomSetting: roomSetting,
|
||||||
existsPassword: _isRoomPrivate,
|
existsPassword: _isRoomPrivate,
|
||||||
ownerProfile: rtcProvider?.currenRoom?.roomProfile?.userProfile,
|
ownerProfile: ownerProfile,
|
||||||
roomAdminCount:
|
roomAdminCount: roomAdminCount,
|
||||||
rtcProvider?.currenRoom?.roomProfile?.roomCounter?.adminCount,
|
roomMemberCount: roomMemberCount,
|
||||||
roomMemberCount:
|
entrantsRole: entrantsRole,
|
||||||
rtcProvider?.currenRoom?.roomProfile?.roomCounter?.memberCount,
|
|
||||||
entrantsRole: rtcProvider?.currenRoom?.entrants?.roles,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -558,31 +558,37 @@ class _SwipeDeleteTileState extends State<_SwipeDeleteTile> {
|
|||||||
duration: const Duration(milliseconds: 160),
|
duration: const Duration(milliseconds: 160),
|
||||||
curve: Curves.easeOutCubic,
|
curve: Curves.easeOutCubic,
|
||||||
transform: Matrix4.translationValues(_dragOffset, 0, 0),
|
transform: Matrix4.translationValues(_dragOffset, 0, 0),
|
||||||
child: Row(
|
child: SizedBox(
|
||||||
children: [
|
width: constraints.maxWidth + actionWidth,
|
||||||
SizedBox(width: constraints.maxWidth, child: widget.child),
|
child: Row(
|
||||||
SizedBox(
|
children: [
|
||||||
width: actionWidth,
|
SizedBox(
|
||||||
height: 56.w,
|
width: constraints.maxWidth,
|
||||||
child: GestureDetector(
|
child: widget.child,
|
||||||
behavior: HitTestBehavior.opaque,
|
),
|
||||||
onTap: widget.onDelete,
|
SizedBox(
|
||||||
child: Container(
|
width: actionWidth,
|
||||||
margin: EdgeInsetsDirectional.only(start: 4.w),
|
height: 56.w,
|
||||||
decoration: BoxDecoration(
|
child: GestureDetector(
|
||||||
color: const Color(0xFFE64A4A),
|
behavior: HitTestBehavior.opaque,
|
||||||
borderRadius: BorderRadius.circular(8.w),
|
onTap: widget.onDelete,
|
||||||
),
|
child: Container(
|
||||||
alignment: Alignment.center,
|
margin: EdgeInsetsDirectional.only(start: 4.w),
|
||||||
child: Image.asset(
|
decoration: BoxDecoration(
|
||||||
"sc_images/room/sc_music_material_delete.png",
|
color: const Color(0xFFE64A4A),
|
||||||
width: 22.w,
|
borderRadius: BorderRadius.circular(8.w),
|
||||||
height: 22.w,
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Image.asset(
|
||||||
|
"sc_images/room/sc_music_material_delete.png",
|
||||||
|
width: 22.w,
|
||||||
|
height: 22.w,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import '../../../ui_kit/components/sc_page_list.dart';
|
|||||||
import '../../../ui_kit/components/sc_tts.dart';
|
import '../../../ui_kit/components/sc_tts.dart';
|
||||||
import '../../../ui_kit/components/text/sc_text.dart';
|
import '../../../ui_kit/components/text/sc_text.dart';
|
||||||
import '../../../ui_kit/widgets/id/sc_special_id_badge.dart';
|
import '../../../ui_kit/widgets/id/sc_special_id_badge.dart';
|
||||||
import '../../../shared/tools/sc_lk_dialog_util.dart';
|
|
||||||
import '../../../ui_kit/widgets/badge/sc_user_badge_strip.dart';
|
import '../../../ui_kit/widgets/badge/sc_user_badge_strip.dart';
|
||||||
import '../../../ui_kit/widgets/room/room_user_info_card.dart';
|
import '../../../ui_kit/widgets/room/room_user_info_card.dart';
|
||||||
|
|
||||||
@ -44,7 +43,7 @@ class _RoomOnlinePageState
|
|||||||
if (dialogContext == null) {
|
if (dialogContext == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showBottomInBottomDialog(dialogContext, RoomUserInfoCard(userId: userId));
|
RoomUserInfoCard.showPreloaded(dialogContext, userId: userId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.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';
|
||||||
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart';
|
import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart';
|
||||||
import 'package:yumi/main.dart';
|
import 'package:yumi/main.dart';
|
||||||
|
|
||||||
import 'package:yumi/app_localizations.dart';
|
import 'package:yumi/app_localizations.dart';
|
||||||
@ -13,7 +13,6 @@ import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
|||||||
import 'package:yumi/ui_kit/components/sc_page_list.dart';
|
import 'package:yumi/ui_kit/components/sc_page_list.dart';
|
||||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||||
import 'package:yumi/shared/tools/sc_lk_dialog_util.dart';
|
|
||||||
import 'package:yumi/shared/business_logic/models/res/room_gift_rank_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/room_gift_rank_res.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/id/sc_special_id_badge.dart';
|
import 'package:yumi/ui_kit/widgets/id/sc_special_id_badge.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/room/room_user_info_card.dart';
|
import 'package:yumi/ui_kit/widgets/room/room_user_info_card.dart';
|
||||||
@ -98,11 +97,11 @@ class _RoomGiftRankTabPageState
|
|||||||
// headdress: userInfo.userProfile?.getHeaddress()?.sourceUrl,
|
// headdress: userInfo.userProfile?.getHeaddress()?.sourceUrl,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showBottomInBottomDialog(
|
|
||||||
navigatorKey.currentState!.context,
|
|
||||||
RoomUserInfoCard(userId: userProfile?.id),
|
|
||||||
);
|
|
||||||
SmartDialog.dismiss(tag: "showRoomGiftRankPage");
|
SmartDialog.dismiss(tag: "showRoomGiftRankPage");
|
||||||
|
RoomUserInfoCard.showPreloaded(
|
||||||
|
navigatorKey.currentState!.context,
|
||||||
|
userId: userProfile?.id,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SizedBox(width: 3.w),
|
SizedBox(width: 3.w),
|
||||||
@ -119,7 +118,7 @@ class _RoomGiftRankTabPageState
|
|||||||
needScroll:
|
needScroll:
|
||||||
(userProfile?.userNickname?.characters.length ??
|
(userProfile?.userNickname?.characters.length ??
|
||||||
0) >
|
0) >
|
||||||
14,
|
14,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -137,7 +136,7 @@ class _RoomGiftRankTabPageState
|
|||||||
animationHeight: 24.w,
|
animationHeight: 24.w,
|
||||||
showTextBesideAnimated: true,
|
showTextBesideAnimated: true,
|
||||||
animatedTextSpacing: 0,
|
animatedTextSpacing: 0,
|
||||||
showAnimatedGradientText:
|
showAnimatedGradientText:
|
||||||
userProfile
|
userProfile
|
||||||
?.shouldShowColoredSpecialIdText() ??
|
?.shouldShowColoredSpecialIdText() ??
|
||||||
false,
|
false,
|
||||||
@ -195,24 +194,24 @@ class _RoomGiftRankTabPageState
|
|||||||
SocialChatUserProfile? _mergeCurrentUserVip(
|
SocialChatUserProfile? _mergeCurrentUserVip(
|
||||||
SocialChatUserProfile? profile,
|
SocialChatUserProfile? profile,
|
||||||
) {
|
) {
|
||||||
if (profile == null || profile.vipLevelForColoredId > 0) {
|
if (profile == null || profile.vipLevelForColoredId > 0) {
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
final currentProfile = AccountStorage().getCurrentUser()?.userProfile;
|
final currentProfile = AccountStorage().getCurrentUser()?.userProfile;
|
||||||
if (currentProfile == null ||
|
if (currentProfile == null ||
|
||||||
currentProfile.vipLevelForColoredId <= 0 ||
|
currentProfile.vipLevelForColoredId <= 0 ||
|
||||||
currentProfile.id != profile.id) {
|
currentProfile.id != profile.id) {
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
return profile.copyWith(
|
return profile.copyWith(
|
||||||
vipLevel:
|
vipLevel:
|
||||||
currentProfile.vipLevel ??
|
currentProfile.vipLevel ??
|
||||||
currentProfile.vipLevelForColoredId.toString(),
|
currentProfile.vipLevelForColoredId.toString(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
empty() {
|
empty() {
|
||||||
return mainEmpty(
|
return mainEmpty(
|
||||||
image: Image.asset(
|
image: Image.asset(
|
||||||
'sc_images/general/sc_icon_loading.png',
|
'sc_images/general/sc_icon_loading.png',
|
||||||
|
|||||||
@ -225,7 +225,7 @@ class _SCSeatItemState extends State<SCSeatItem> with TickerProviderStateMixin {
|
|||||||
height: _seatLabelRowHeight.w,
|
height: _seatLabelRowHeight.w,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: text(
|
child: text(
|
||||||
"NO.${widget.index}",
|
"NO.${widget.index.toInt() + 1}",
|
||||||
fontSize: 10.sp,
|
fontSize: 10.sp,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -21,15 +21,19 @@ import 'package:yumi/services/music/room_music_manager.dart';
|
|||||||
import 'package:yumi/services/audio/rtm_manager.dart';
|
import 'package:yumi/services/audio/rtm_manager.dart';
|
||||||
import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart';
|
import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart';
|
||||||
import 'package:yumi/shared/tools/sc_lucky_gift_win_sound_player.dart';
|
import 'package:yumi/shared/tools/sc_lucky_gift_win_sound_player.dart';
|
||||||
|
import 'package:yumi/shared/tools/sc_cp_gift_relation_utils.dart';
|
||||||
import 'package:yumi/shared/tools/sc_network_image_utils.dart';
|
import 'package:yumi/shared/tools/sc_network_image_utils.dart';
|
||||||
import 'package:yumi/shared/tools/sc_path_utils.dart';
|
import 'package:yumi/shared/tools/sc_path_utils.dart';
|
||||||
import 'package:yumi/shared/tools/sc_room_effect_scheduler.dart';
|
import 'package:yumi/shared/tools/sc_room_effect_scheduler.dart';
|
||||||
|
import 'package:yumi/shared/tools/sc_room_top_layer_guard.dart';
|
||||||
|
import 'package:yumi/shared/data_sources/models/message/sc_floating_message.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart';
|
import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/room/anim/l_gift_animal_view.dart';
|
import 'package:yumi/ui_kit/widgets/room/anim/l_gift_animal_view.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/room/anim/room_gift_seat_flight_overlay.dart';
|
import 'package:yumi/ui_kit/widgets/room/anim/room_gift_seat_flight_overlay.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/room/anim/room_entrance_screen.dart';
|
import 'package:yumi/ui_kit/widgets/room/anim/room_entrance_screen.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/room/anim/room_entrance_widget.dart';
|
import 'package:yumi/ui_kit/widgets/room/anim/room_entrance_widget.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/room/effect/luck_gift_nomor_anim_widget.dart';
|
import 'package:yumi/ui_kit/widgets/room/effect/luck_gift_nomor_anim_widget.dart';
|
||||||
|
import 'package:yumi/ui_kit/widgets/room/effect/vapp_svga_layer_widget.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/room/room_head_widget.dart';
|
import 'package:yumi/ui_kit/widgets/room/room_head_widget.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/room/room_online_user_widget.dart';
|
import 'package:yumi/ui_kit/widgets/room/room_online_user_widget.dart';
|
||||||
@ -77,18 +81,16 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
int _shownRoomStartupFailureToken = 0;
|
int _shownRoomStartupFailureToken = 0;
|
||||||
RtcProvider? _rtcProvider;
|
RtcProvider? _rtcProvider;
|
||||||
bool _roomProviderRebuildScheduled = false;
|
bool _roomProviderRebuildScheduled = false;
|
||||||
|
bool _roomRouteVisibleScheduled = false;
|
||||||
|
bool _roomVisualEffectsEnsureScheduled = false;
|
||||||
PageRoute<dynamic>? _routeObserverRoute;
|
PageRoute<dynamic>? _routeObserverRoute;
|
||||||
|
SCRoomTopLayerGuardLease? _coveredRouteGuardLease;
|
||||||
bool _roomRouteVisible = false;
|
bool _roomRouteVisible = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_tabController = TabController(length: _chatTabCount, vsync: this);
|
_tabController = TabController(length: _chatTabCount, vsync: this);
|
||||||
Provider.of<RtcProvider>(
|
|
||||||
context,
|
|
||||||
listen: false,
|
|
||||||
).setVoiceRoomRouteVisible(true);
|
|
||||||
_enableRoomVisualEffects();
|
|
||||||
_tabController.addListener(_handleTabChange);
|
_tabController.addListener(_handleTabChange);
|
||||||
_subscription = eventBus.on<SCGiveRoomLuckPageDisposeEvent>().listen((
|
_subscription = eventBus.on<SCGiveRoomLuckPageDisposeEvent>().listen((
|
||||||
event,
|
event,
|
||||||
@ -124,15 +126,20 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
}
|
}
|
||||||
_routeObserverRoute = route;
|
_routeObserverRoute = route;
|
||||||
routeObserver.subscribe(this, route);
|
routeObserver.subscribe(this, route);
|
||||||
_setRoomRouteVisible(route.isCurrent);
|
if (route.isCurrent) {
|
||||||
|
_scheduleRoomRouteVisible();
|
||||||
|
} else {
|
||||||
|
_handleRoomRouteCovered();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ensureRoomVisualEffectsEnabled();
|
_scheduleEnsureRoomVisualEffectsEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
routeObserver.unsubscribe(this);
|
routeObserver.unsubscribe(this);
|
||||||
_routeObserverRoute = null;
|
_routeObserverRoute = null;
|
||||||
|
_releaseCoveredRouteGuard();
|
||||||
_setRoomRouteVisible(false);
|
_setRoomRouteVisible(false);
|
||||||
_rtcProvider?.removeListener(_handleRoomProviderChanged);
|
_rtcProvider?.removeListener(_handleRoomProviderChanged);
|
||||||
_rtcProvider = null;
|
_rtcProvider = null;
|
||||||
@ -146,22 +153,60 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void didPush() {
|
void didPush() {
|
||||||
_setRoomRouteVisible(true);
|
_handleRoomRouteVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didPopNext() {
|
void didPopNext() {
|
||||||
_setRoomRouteVisible(true);
|
_handleRoomRouteVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didPushNext() {
|
void didPushNext() {
|
||||||
_setRoomRouteVisible(false);
|
_handleRoomRouteCovered();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didPop() {
|
void didPop() {
|
||||||
|
_handleRoomRouteCovered();
|
||||||
|
_releaseCoveredRouteGuard();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleRoomRouteVisible() {
|
||||||
|
_releaseCoveredRouteGuard();
|
||||||
|
_setRoomRouteVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _scheduleRoomRouteVisible() {
|
||||||
|
if (_roomRouteVisibleScheduled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_roomRouteVisibleScheduled = true;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_roomRouteVisibleScheduled = false;
|
||||||
|
if (!mounted || !(ModalRoute.of(context)?.isCurrent ?? false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_handleRoomRouteVisible();
|
||||||
|
_ensureRoomVisualEffectsEnabled();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleRoomRouteCovered() {
|
||||||
_setRoomRouteVisible(false);
|
_setRoomRouteVisible(false);
|
||||||
|
_acquireCoveredRouteGuard();
|
||||||
|
_clearTransientRoomVisualEffects(reason: 'voice_room_route_covered');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _acquireCoveredRouteGuard() {
|
||||||
|
_coveredRouteGuardLease ??= SCRoomTopLayerGuard().acquire(
|
||||||
|
reason: 'voice_room_route_covered',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _releaseCoveredRouteGuard() {
|
||||||
|
_coveredRouteGuardLease?.release();
|
||||||
|
_coveredRouteGuardLease = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setRoomRouteVisible(bool visible) {
|
void _setRoomRouteVisible(bool visible) {
|
||||||
@ -216,6 +261,43 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
_enableRoomVisualEffects();
|
_enableRoomVisualEffects();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _scheduleEnsureRoomVisualEffectsEnabled() {
|
||||||
|
if (_roomVisualEffectsEnsureScheduled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_roomVisualEffectsEnsureScheduled = true;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_roomVisualEffectsEnsureScheduled = false;
|
||||||
|
if (!mounted || !(ModalRoute.of(context)?.isCurrent ?? false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ensureRoomVisualEffectsEnabled();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _clearTransientRoomVisualEffects({required String reason}) {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
||||||
|
final currentRoomId =
|
||||||
|
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? '';
|
||||||
|
|
||||||
|
RoomEntranceHelper.clearQueue();
|
||||||
|
Provider.of<GiftProvider>(context, listen: false).clearAllGiftData();
|
||||||
|
Provider.of<GiftAnimationManager>(
|
||||||
|
context,
|
||||||
|
listen: false,
|
||||||
|
).clearActiveAnimations();
|
||||||
|
_clearLuckyGiftComboSessions();
|
||||||
|
_clearGiftFlightBatchDedupeTimers();
|
||||||
|
_clearGiftVisualBatchDedupeTimers();
|
||||||
|
_giftSeatFlightController.clear();
|
||||||
|
OverlayManager().removeRoom(roomId: currentRoomId);
|
||||||
|
SCRoomEffectScheduler().clearDeferredTasks(reason: reason);
|
||||||
|
SCGiftVapSvgaManager().stopPlayback();
|
||||||
|
}
|
||||||
|
|
||||||
void _suspendRoomVisualEffects() {
|
void _suspendRoomVisualEffects() {
|
||||||
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
||||||
final currentRoomId =
|
final currentRoomId =
|
||||||
@ -240,9 +322,11 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
OverlayManager().removeRoom(roomId: currentRoomId);
|
OverlayManager().removeRoom(roomId: currentRoomId);
|
||||||
SCRoomEffectScheduler().clearDeferredTasks(reason: 'voice_room_suspend');
|
SCRoomEffectScheduler().clearDeferredTasks(reason: 'voice_room_suspend');
|
||||||
SCGiftVapSvgaManager().stopPlayback();
|
SCGiftVapSvgaManager().stopPlayback();
|
||||||
unawaited(
|
if (!SCFloatIchart().isShow()) {
|
||||||
Provider.of<RoomMusicManager>(context, listen: false).stopForRoomExit(),
|
unawaited(
|
||||||
);
|
Provider.of<RoomMusicManager>(context, listen: false).stopForRoomExit(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRoomStartupLayer() {
|
Widget _buildRoomStartupLayer() {
|
||||||
@ -305,6 +389,8 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
return SCAppLocalizations.of(context)!.enterRoomFailedRetry;
|
return SCAppLocalizations.of(context)!.enterRoomFailedRetry;
|
||||||
case RoomStartupFailureType.rtc:
|
case RoomStartupFailureType.rtc:
|
||||||
return SCAppLocalizations.of(context)!.voiceConnectionFailedRetry;
|
return SCAppLocalizations.of(context)!.voiceConnectionFailedRetry;
|
||||||
|
case RoomStartupFailureType.kicked:
|
||||||
|
return SCAppLocalizations.of(context)!.roomKickedEntryBlockedTips;
|
||||||
case RoomStartupFailureType.entry:
|
case RoomStartupFailureType.entry:
|
||||||
case RoomStartupFailureType.none:
|
case RoomStartupFailureType.none:
|
||||||
return SCAppLocalizations.of(context)!.operationFail;
|
return SCAppLocalizations.of(context)!.operationFail;
|
||||||
@ -418,6 +504,8 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
// _buildPlayViews(),
|
// _buildPlayViews(),
|
||||||
///幸运礼物中奖动画
|
///幸运礼物中奖动画
|
||||||
LuckGiftNomorAnimWidget(),
|
LuckGiftNomorAnimWidget(),
|
||||||
|
_buildRoomGiftSvgaLayer(),
|
||||||
|
_buildRoomGiftSeatFlightLayer(),
|
||||||
const RoomGiftComboFloatingLayer(),
|
const RoomGiftComboFloatingLayer(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -426,6 +514,43 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildRoomGiftSvgaLayer() {
|
||||||
|
if (!SCGlobalConfig.allowsHighCostAnimations) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
return Positioned.fill(
|
||||||
|
child: Selector<RtcProvider, bool>(
|
||||||
|
selector: (context, provider) => provider.shouldShowRoomVisualEffects,
|
||||||
|
builder: (context, shouldShowRoomVisualEffects, child) {
|
||||||
|
if (!shouldShowRoomVisualEffects) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
return child!;
|
||||||
|
},
|
||||||
|
child: const VapPlusSvgaPlayer(tag: "room_gift"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRoomGiftSeatFlightLayer() {
|
||||||
|
return Positioned.fill(
|
||||||
|
child: Selector<RtcProvider, bool>(
|
||||||
|
selector: (context, provider) => provider.shouldShowRoomVisualEffects,
|
||||||
|
builder: (context, shouldShowRoomVisualEffects, child) {
|
||||||
|
if (!shouldShowRoomVisualEffects) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
return RoomGiftSeatFlightOverlay(
|
||||||
|
controller: _giftSeatFlightController,
|
||||||
|
resolveTargetKey:
|
||||||
|
(userId) =>
|
||||||
|
context.read<RtcProvider>().getSeatGlobalKeyByIndex(userId),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
///消息
|
///消息
|
||||||
Widget _buildChatView(String chatRoomKey) {
|
Widget _buildChatView(String chatRoomKey) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
@ -607,6 +732,7 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
final giftPhoto = (msg.gift?.giftPhoto ?? "").trim();
|
final giftPhoto = (msg.gift?.giftPhoto ?? "").trim();
|
||||||
|
_enqueueCpGiftFloatingIfNeeded(msg);
|
||||||
if (_supportsComboMilestoneEffects(msg)) {
|
if (_supportsComboMilestoneEffects(msg)) {
|
||||||
_handleComboMilestoneVisuals(msg, targetGroupKey);
|
_handleComboMilestoneVisuals(msg, targetGroupKey);
|
||||||
}
|
}
|
||||||
@ -628,6 +754,48 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _enqueueCpGiftFloatingIfNeeded(Msg msg) {
|
||||||
|
final gift = msg.gift;
|
||||||
|
if (!scIsCpGift(gift) || gift == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final quantity = msg.number ?? 0;
|
||||||
|
if (quantity <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final roomId = _resolveGiftFloatingRoomId(msg);
|
||||||
|
OverlayManager().addMessage(
|
||||||
|
SCFloatingMessage(
|
||||||
|
type: 1,
|
||||||
|
priority: 900,
|
||||||
|
userId: msg.user?.id ?? "",
|
||||||
|
toUserId: msg.toUser?.id ?? "",
|
||||||
|
userAvatarUrl: msg.user?.userAvatar ?? "",
|
||||||
|
toUserAvatarUrl: msg.toUser?.userAvatar ?? "",
|
||||||
|
userName: msg.user?.userNickname ?? "",
|
||||||
|
toUserName: msg.toUser?.userNickname ?? "",
|
||||||
|
giftUrl: gift.giftPhoto ?? "",
|
||||||
|
giftId: gift.id,
|
||||||
|
giftTab: scCpRelationTypeForGift(gift),
|
||||||
|
number: quantity,
|
||||||
|
coins: quantity * (gift.giftCandy ?? 0),
|
||||||
|
roomId: roomId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _resolveGiftFloatingRoomId(Msg msg) {
|
||||||
|
final messageRoomId = (msg.msg ?? "").trim();
|
||||||
|
if (messageRoomId.isNotEmpty) {
|
||||||
|
return messageRoomId;
|
||||||
|
}
|
||||||
|
return Provider.of<RtcProvider>(
|
||||||
|
context,
|
||||||
|
listen: false,
|
||||||
|
).currenRoom?.roomProfile?.roomProfile?.id ??
|
||||||
|
"";
|
||||||
|
}
|
||||||
|
|
||||||
void _luckyGiftRewardTickerListener(Msg msg) {
|
void _luckyGiftRewardTickerListener(Msg msg) {
|
||||||
if (!Provider.of<RtcProvider>(
|
if (!Provider.of<RtcProvider>(
|
||||||
context,
|
context,
|
||||||
|
|||||||
@ -159,12 +159,14 @@ class VoiceRoomRoute implements SCIRouterProvider {
|
|||||||
static Future<T?> openRoomEdit<T>(
|
static Future<T?> openRoomEdit<T>(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required bool needRestCurrentRoomInfo,
|
required bool needRestCurrentRoomInfo,
|
||||||
|
RoomEntryPreviewData? setupPreviewData,
|
||||||
bool rootNavigator = true,
|
bool rootNavigator = true,
|
||||||
}) {
|
}) {
|
||||||
return Navigator.of(context, rootNavigator: rootNavigator).push<T>(
|
return Navigator.of(context, rootNavigator: rootNavigator).push<T>(
|
||||||
_buildRoute<T>(
|
_buildRoute<T>(
|
||||||
RoomEditPage(
|
RoomEditPage(
|
||||||
needRestCurrentRoomInfo: needRestCurrentRoomInfo ? "true" : "false",
|
needRestCurrentRoomInfo: needRestCurrentRoomInfo ? "true" : "false",
|
||||||
|
setupPreviewData: setupPreviewData,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
303
lib/modules/room_game/bridge/hotgame_js_bridge.dart
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
class HotgameBridgeActions {
|
||||||
|
static const String recharge = 'recharge';
|
||||||
|
static const String quit = 'quit';
|
||||||
|
static const String loadComplete = 'loadComplete';
|
||||||
|
static const String debugLog = 'debugLog';
|
||||||
|
|
||||||
|
static String normalize(String action) {
|
||||||
|
final trimmed = action.trim();
|
||||||
|
final lower = trimmed.toLowerCase();
|
||||||
|
switch (lower) {
|
||||||
|
case 'recharge':
|
||||||
|
case 'pay':
|
||||||
|
case 'gamepay':
|
||||||
|
case 'gamerecharge':
|
||||||
|
case 'insufficient':
|
||||||
|
case 'insufficientbalance':
|
||||||
|
return recharge;
|
||||||
|
case 'quit':
|
||||||
|
case 'exit':
|
||||||
|
case 'close':
|
||||||
|
case 'closegame':
|
||||||
|
case 'destroy':
|
||||||
|
return quit;
|
||||||
|
case 'loadcomplete':
|
||||||
|
case 'gameloaded':
|
||||||
|
case 'loaded':
|
||||||
|
case 'ready':
|
||||||
|
return loadComplete;
|
||||||
|
case 'debuglog':
|
||||||
|
return debugLog;
|
||||||
|
default:
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HotgameBridgeMessage {
|
||||||
|
const HotgameBridgeMessage({
|
||||||
|
required this.action,
|
||||||
|
this.payload = const <String, dynamic>{},
|
||||||
|
});
|
||||||
|
|
||||||
|
final String action;
|
||||||
|
final Map<String, dynamic> payload;
|
||||||
|
|
||||||
|
static HotgameBridgeMessage fromAction(String action, String rawMessage) {
|
||||||
|
return HotgameBridgeMessage(
|
||||||
|
action: HotgameBridgeActions.normalize(action),
|
||||||
|
payload: _parsePayload(rawMessage),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static HotgameBridgeMessage parse(String rawMessage) {
|
||||||
|
try {
|
||||||
|
final dynamic decoded = jsonDecode(rawMessage);
|
||||||
|
if (decoded is Map<String, dynamic>) {
|
||||||
|
return HotgameBridgeMessage(
|
||||||
|
action: HotgameBridgeActions.normalize(
|
||||||
|
(decoded['action'] ??
|
||||||
|
decoded['cmd'] ??
|
||||||
|
decoded['event'] ??
|
||||||
|
decoded['method'] ??
|
||||||
|
decoded['type'] ??
|
||||||
|
'')
|
||||||
|
.toString(),
|
||||||
|
),
|
||||||
|
payload: _parsePayload(
|
||||||
|
decoded['payload'] ?? decoded['data'] ?? decoded['params'],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
return HotgameBridgeMessage(
|
||||||
|
action: HotgameBridgeActions.normalize(rawMessage),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, dynamic> _parsePayload(dynamic rawPayload) {
|
||||||
|
if (rawPayload is Map<String, dynamic>) {
|
||||||
|
return rawPayload;
|
||||||
|
}
|
||||||
|
if (rawPayload is String) {
|
||||||
|
final trimmed = rawPayload.trim();
|
||||||
|
if (trimmed.isEmpty) {
|
||||||
|
return const <String, dynamic>{};
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final dynamic decoded = jsonDecode(trimmed);
|
||||||
|
if (decoded is Map<String, dynamic>) {
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
return <String, dynamic>{'raw': trimmed};
|
||||||
|
}
|
||||||
|
return const <String, dynamic>{};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HotgameJsBridge {
|
||||||
|
static const String channelName = 'HotgameBridgeChannel';
|
||||||
|
|
||||||
|
static String bootstrapScript({Map<String, dynamic>? launchPayload}) {
|
||||||
|
final safeLaunchPayload = jsonEncode(
|
||||||
|
launchPayload ?? const <String, dynamic>{},
|
||||||
|
);
|
||||||
|
return '''
|
||||||
|
(function() {
|
||||||
|
const launchPayload = $safeLaunchPayload;
|
||||||
|
|
||||||
|
function toPayload(input) {
|
||||||
|
if (typeof input === 'string') {
|
||||||
|
try {
|
||||||
|
return JSON.parse(input);
|
||||||
|
} catch (_) {
|
||||||
|
return input.trim() ? { raw: input.trim() } : {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (input && typeof input === 'object') {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringifyForLog(value) {
|
||||||
|
if (value == null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return value.length > 320 ? value.slice(0, 320) + '...' : value;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const text = JSON.stringify(value);
|
||||||
|
return text.length > 320 ? text.slice(0, 320) + '...' : text;
|
||||||
|
} catch (_) {
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function postMessage(action, input) {
|
||||||
|
const payload = toPayload(input);
|
||||||
|
$channelName.postMessage(JSON.stringify({
|
||||||
|
action: action,
|
||||||
|
payload: payload
|
||||||
|
}));
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
function postDebug(tag, payload) {
|
||||||
|
postMessage('${HotgameBridgeActions.debugLog}', {
|
||||||
|
tag: tag,
|
||||||
|
message: stringifyForLog(payload)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function dispatchLaunchPayload(payload) {
|
||||||
|
const normalizedPayload =
|
||||||
|
payload && typeof payload === 'object' ? payload : {};
|
||||||
|
const launchConfig =
|
||||||
|
normalizedPayload.launchConfig &&
|
||||||
|
typeof normalizedPayload.launchConfig === 'object'
|
||||||
|
? normalizedPayload.launchConfig
|
||||||
|
: {};
|
||||||
|
const entry =
|
||||||
|
normalizedPayload.entry && typeof normalizedPayload.entry === 'object'
|
||||||
|
? normalizedPayload.entry
|
||||||
|
: {};
|
||||||
|
window.__hotgameLaunchPayload = normalizedPayload;
|
||||||
|
window.hotgameLaunchPayload = normalizedPayload;
|
||||||
|
window.__hotgameLaunchConfig = launchConfig;
|
||||||
|
window.hotgameLaunchConfig = launchConfig;
|
||||||
|
window.__hotgameLaunchEntry = entry;
|
||||||
|
window.hotgameLaunchEntry = entry;
|
||||||
|
if (typeof window.onHotgameLaunchConfig === 'function') {
|
||||||
|
window.onHotgameLaunchConfig(launchConfig, normalizedPayload);
|
||||||
|
}
|
||||||
|
if (typeof window.dispatchEvent === 'function' && typeof CustomEvent === 'function') {
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent('hotgameLaunchConfig', { detail: launchConfig })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function installPostMessageObject(name, action) {
|
||||||
|
const existing = window[name];
|
||||||
|
if (!existing || typeof existing.postMessage !== 'function') {
|
||||||
|
window[name] = {
|
||||||
|
postMessage: function(body) {
|
||||||
|
return postMessage(action, body);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function installWebkitHandler(name, action) {
|
||||||
|
window.webkit = window.webkit || {};
|
||||||
|
window.webkit.messageHandlers = window.webkit.messageHandlers || {};
|
||||||
|
if (!window.webkit.messageHandlers[name] ||
|
||||||
|
typeof window.webkit.messageHandlers[name].postMessage !== 'function') {
|
||||||
|
window.webkit.messageHandlers[name] = {
|
||||||
|
postMessage: function(body) {
|
||||||
|
return postMessage(action, body);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function installJsBridgeFunction(name, action) {
|
||||||
|
window.JsBridge = window.JsBridge || {};
|
||||||
|
window.JsBridge[name] = function(body) {
|
||||||
|
return postMessage(action, body || {});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window.__hotgameBridgeReady) {
|
||||||
|
window.__hotgameBridgeReady = true;
|
||||||
|
installJsBridgeFunction('recharge', '${HotgameBridgeActions.recharge}');
|
||||||
|
installJsBridgeFunction('quit', '${HotgameBridgeActions.quit}');
|
||||||
|
installJsBridgeFunction('exit', '${HotgameBridgeActions.quit}');
|
||||||
|
installJsBridgeFunction('closeGame', '${HotgameBridgeActions.quit}');
|
||||||
|
installPostMessageObject('recharge', '${HotgameBridgeActions.recharge}');
|
||||||
|
installPostMessageObject('quit', '${HotgameBridgeActions.quit}');
|
||||||
|
installPostMessageObject('loadComplete', '${HotgameBridgeActions.loadComplete}');
|
||||||
|
installWebkitHandler('recharge', '${HotgameBridgeActions.recharge}');
|
||||||
|
installWebkitHandler('quit', '${HotgameBridgeActions.quit}');
|
||||||
|
installWebkitHandler('loadComplete', '${HotgameBridgeActions.loadComplete}');
|
||||||
|
window.NativeBridge = window.NativeBridge || {};
|
||||||
|
window.NativeBridge.recharge = function(body) {
|
||||||
|
return postMessage('${HotgameBridgeActions.recharge}', body || {});
|
||||||
|
};
|
||||||
|
window.NativeBridge.quit = function(body) {
|
||||||
|
return postMessage('${HotgameBridgeActions.quit}', body || {});
|
||||||
|
};
|
||||||
|
window.NativeBridge.closeGame = function(body) {
|
||||||
|
return postMessage('${HotgameBridgeActions.quit}', body || {});
|
||||||
|
};
|
||||||
|
window.NativeBridge.rechargeSuccess = function(body) {
|
||||||
|
return notifyRechargeSuccess(body || {});
|
||||||
|
};
|
||||||
|
window.__hotgameBridgeDebug = postDebug;
|
||||||
|
postDebug('bridge.ready', {
|
||||||
|
href: window.location && window.location.href,
|
||||||
|
hasJsBridge: !!window.JsBridge,
|
||||||
|
hasRechargeSuccess: typeof window.rechargeSuccess === 'function'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifyRechargeSuccess(body) {
|
||||||
|
const payload = body || {};
|
||||||
|
let notified = false;
|
||||||
|
if (typeof window.rechargeSuccess === 'function') {
|
||||||
|
window.rechargeSuccess();
|
||||||
|
notified = true;
|
||||||
|
}
|
||||||
|
if (typeof window.onRechargeSuccess === 'function') {
|
||||||
|
window.onRechargeSuccess(payload);
|
||||||
|
notified = true;
|
||||||
|
}
|
||||||
|
if (typeof window.dispatchEvent === 'function' && typeof CustomEvent === 'function') {
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent('rechargeSuccess', { detail: payload })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return notified;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.__hotgameNotifyRechargeSuccess = notifyRechargeSuccess;
|
||||||
|
dispatchLaunchPayload(launchPayload);
|
||||||
|
})();
|
||||||
|
''';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String buildRechargeSuccessScript({Map<String, dynamic>? payload}) {
|
||||||
|
final safePayload = jsonEncode(
|
||||||
|
payload ??
|
||||||
|
<String, dynamic>{'timestamp': DateTime.now().millisecondsSinceEpoch},
|
||||||
|
);
|
||||||
|
return '''
|
||||||
|
(function() {
|
||||||
|
const payload = $safePayload;
|
||||||
|
if (typeof window.__hotgameBridgeDebug === 'function') {
|
||||||
|
window.__hotgameBridgeDebug('recharge.success', payload);
|
||||||
|
}
|
||||||
|
if (typeof window.__hotgameNotifyRechargeSuccess === 'function') {
|
||||||
|
window.__hotgameNotifyRechargeSuccess(payload);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof window.rechargeSuccess === 'function') {
|
||||||
|
window.rechargeSuccess();
|
||||||
|
}
|
||||||
|
if (typeof window.onRechargeSuccess === 'function') {
|
||||||
|
window.onRechargeSuccess(payload);
|
||||||
|
}
|
||||||
|
if (typeof window.dispatchEvent === 'function' && typeof CustomEvent === 'function') {
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent('rechargeSuccess', { detail: payload })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
''';
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -127,6 +127,10 @@ class RoomGameListItemModel {
|
|||||||
bool get isLeader =>
|
bool get isLeader =>
|
||||||
provider.toUpperCase() == 'LEADER' || gameType.toUpperCase() == 'LEADER';
|
provider.toUpperCase() == 'LEADER' || gameType.toUpperCase() == 'LEADER';
|
||||||
|
|
||||||
|
bool get isHotgame =>
|
||||||
|
provider.toUpperCase() == 'HOTGAME' ||
|
||||||
|
gameType.toUpperCase() == 'HOTGAME';
|
||||||
|
|
||||||
factory RoomGameListItemModel.fromJson(Map<String, dynamic> json) {
|
factory RoomGameListItemModel.fromJson(Map<String, dynamic> json) {
|
||||||
return RoomGameListItemModel(
|
return RoomGameListItemModel(
|
||||||
gameId: _asString(json['gameId']),
|
gameId: _asString(json['gameId']),
|
||||||
|
|||||||
@ -6,24 +6,24 @@ class BaishunLoadingView extends StatelessWidget {
|
|||||||
const BaishunLoadingView({
|
const BaishunLoadingView({
|
||||||
super.key,
|
super.key,
|
||||||
this.message = 'Loading game...',
|
this.message = 'Loading game...',
|
||||||
|
this.backgroundColor = const Color(0xFF081915),
|
||||||
});
|
});
|
||||||
|
|
||||||
final String message;
|
final String message;
|
||||||
|
final Color backgroundColor;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Positioned.fill(
|
return Positioned.fill(
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.black.withValues(alpha: 0.42),
|
color: backgroundColor,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 22.w, vertical: 18.w),
|
padding: EdgeInsets.symmetric(horizontal: 22.w, vertical: 18.w),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFF102D28).withValues(alpha: 0.92),
|
color: const Color(0xFF102D28).withValues(alpha: 0.92),
|
||||||
borderRadius: BorderRadius.circular(16.w),
|
borderRadius: BorderRadius.circular(16.w),
|
||||||
border: Border.all(
|
border: Border.all(color: Colors.white.withValues(alpha: 0.1)),
|
||||||
color: Colors.white.withValues(alpha: 0.1),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|||||||
640
lib/modules/room_game/views/hotgame_game_page.dart
Normal file
@ -0,0 +1,640 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:webview_flutter/webview_flutter.dart';
|
||||||
|
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||||
|
import 'package:yumi/modules/room_game/bridge/hotgame_js_bridge.dart';
|
||||||
|
import 'package:yumi/modules/room_game/data/models/room_game_models.dart';
|
||||||
|
import 'package:yumi/modules/room_game/data/room_game_repository.dart';
|
||||||
|
import 'package:yumi/modules/room_game/utils/room_game_viewport.dart';
|
||||||
|
import 'package:yumi/modules/room_game/views/baishun_loading_view.dart';
|
||||||
|
import 'package:yumi/modules/wallet/wallet_route.dart';
|
||||||
|
import 'package:yumi/shared/tools/sc_h5_url_utils.dart';
|
||||||
|
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||||
|
|
||||||
|
class HotgameGamePage extends StatefulWidget {
|
||||||
|
const HotgameGamePage({
|
||||||
|
super.key,
|
||||||
|
required this.roomId,
|
||||||
|
required this.game,
|
||||||
|
required this.launchModel,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String roomId;
|
||||||
|
final RoomGameListItemModel game;
|
||||||
|
final BaishunLaunchModel launchModel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HotgameGamePage> createState() => _HotgameGamePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HotgameGamePageState extends State<HotgameGamePage> {
|
||||||
|
static const double _sevenScreenRatio = 0.72;
|
||||||
|
|
||||||
|
final RoomGameRepository _repository = RoomGameRepository();
|
||||||
|
final Set<Factory<OneSequenceGestureRecognizer>> _webGestureRecognizers =
|
||||||
|
<Factory<OneSequenceGestureRecognizer>>{
|
||||||
|
Factory<OneSequenceGestureRecognizer>(() => EagerGestureRecognizer()),
|
||||||
|
};
|
||||||
|
late final WebViewController _controller;
|
||||||
|
|
||||||
|
Timer? _bridgeBootstrapTimer;
|
||||||
|
Timer? _loadingFallbackTimer;
|
||||||
|
|
||||||
|
bool _isLoading = true;
|
||||||
|
bool _isClosing = false;
|
||||||
|
bool _didReceiveBridgeMessage = false;
|
||||||
|
bool _didFinishPageLoad = false;
|
||||||
|
bool _didInjectProgressBridge = false;
|
||||||
|
int _bridgeInjectCount = 0;
|
||||||
|
String? _errorMessage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller =
|
||||||
|
WebViewController()
|
||||||
|
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||||
|
..setBackgroundColor(Colors.black)
|
||||||
|
..addJavaScriptChannel(
|
||||||
|
HotgameJsBridge.channelName,
|
||||||
|
onMessageReceived: _handleBridgeMessage,
|
||||||
|
)
|
||||||
|
..addJavaScriptChannel(
|
||||||
|
HotgameBridgeActions.recharge,
|
||||||
|
onMessageReceived:
|
||||||
|
(JavaScriptMessage message) => _handleNamedChannelMessage(
|
||||||
|
HotgameBridgeActions.recharge,
|
||||||
|
message,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
..addJavaScriptChannel(
|
||||||
|
HotgameBridgeActions.quit,
|
||||||
|
onMessageReceived:
|
||||||
|
(JavaScriptMessage message) => _handleNamedChannelMessage(
|
||||||
|
HotgameBridgeActions.quit,
|
||||||
|
message,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
..addJavaScriptChannel(
|
||||||
|
HotgameBridgeActions.loadComplete,
|
||||||
|
onMessageReceived:
|
||||||
|
(JavaScriptMessage message) => _handleNamedChannelMessage(
|
||||||
|
HotgameBridgeActions.loadComplete,
|
||||||
|
message,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
..setNavigationDelegate(
|
||||||
|
NavigationDelegate(
|
||||||
|
onPageStarted: (String url) {
|
||||||
|
_log('page_started url=${_clip(url, 240)}');
|
||||||
|
_prepareForPageLoad();
|
||||||
|
},
|
||||||
|
onPageFinished: (String url) async {
|
||||||
|
_didFinishPageLoad = true;
|
||||||
|
_log('page_finished url=${_clip(url, 240)}');
|
||||||
|
await _injectBridge(reason: 'page_finished');
|
||||||
|
if (!mounted || _errorMessage != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onProgress: (int progress) {
|
||||||
|
if (_didInjectProgressBridge ||
|
||||||
|
progress < 10 ||
|
||||||
|
_errorMessage != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_didInjectProgressBridge = true;
|
||||||
|
unawaited(_injectBridge(reason: 'progress_$progress'));
|
||||||
|
},
|
||||||
|
onWebResourceError: (WebResourceError error) {
|
||||||
|
_log(
|
||||||
|
'web_resource_error code=${error.errorCode} '
|
||||||
|
'type=${error.errorType} desc=${_clip(error.description, 300)}',
|
||||||
|
);
|
||||||
|
_stopBridgeBootstrap(reason: 'web_resource_error');
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_errorMessage = error.description;
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
_log('init launch=${_stringifyForLog(_buildLaunchSummary())}');
|
||||||
|
unawaited(_loadGameEntry());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_stopBridgeBootstrap(reason: 'dispose');
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _prepareForPageLoad() {
|
||||||
|
_didReceiveBridgeMessage = false;
|
||||||
|
_didFinishPageLoad = false;
|
||||||
|
_didInjectProgressBridge = false;
|
||||||
|
_bridgeInjectCount = 0;
|
||||||
|
_stopBridgeBootstrap(reason: 'prepare_page_load');
|
||||||
|
_bridgeBootstrapTimer = Timer.periodic(const Duration(milliseconds: 250), (
|
||||||
|
Timer timer,
|
||||||
|
) {
|
||||||
|
if (!mounted || _didReceiveBridgeMessage || _errorMessage != null) {
|
||||||
|
timer.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unawaited(_injectBridge(reason: 'bootstrap'));
|
||||||
|
if (_didFinishPageLoad && timer.tick >= 12) {
|
||||||
|
timer.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_loadingFallbackTimer = Timer(const Duration(seconds: 6), () {
|
||||||
|
if (!mounted || _didReceiveBridgeMessage || _errorMessage != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_isLoading = true;
|
||||||
|
_errorMessage = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _stopBridgeBootstrap({String reason = 'manual'}) {
|
||||||
|
if (_bridgeBootstrapTimer != null || _loadingFallbackTimer != null) {
|
||||||
|
_log('stop_bootstrap reason=$reason');
|
||||||
|
}
|
||||||
|
_bridgeBootstrapTimer?.cancel();
|
||||||
|
_bridgeBootstrapTimer = null;
|
||||||
|
_loadingFallbackTimer?.cancel();
|
||||||
|
_loadingFallbackTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadGameEntry() async {
|
||||||
|
try {
|
||||||
|
_prepareForPageLoad();
|
||||||
|
final entryUrl = widget.launchModel.entry.entryUrl.trim();
|
||||||
|
_log(
|
||||||
|
'load_entry launchMode=${widget.launchModel.entry.launchMode} '
|
||||||
|
'entryUrl=${_clip(entryUrl, 240)}',
|
||||||
|
);
|
||||||
|
if (entryUrl.isEmpty || entryUrl.startsWith('mock://')) {
|
||||||
|
await _controller.loadHtmlString(_buildMockHtml());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final h5Url = SCH5UrlUtils.appendToken(entryUrl);
|
||||||
|
final uri = Uri.tryParse(h5Url);
|
||||||
|
if (uri == null) {
|
||||||
|
throw Exception('Invalid hotgame entry url: $entryUrl');
|
||||||
|
}
|
||||||
|
await _controller.loadRequest(uri);
|
||||||
|
} catch (error) {
|
||||||
|
_log('load_entry_error error=${_clip(error.toString(), 400)}');
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_errorMessage = error.toString();
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _injectBridge({String reason = 'manual'}) async {
|
||||||
|
_bridgeInjectCount += 1;
|
||||||
|
if (reason != 'bootstrap' ||
|
||||||
|
_bridgeInjectCount <= 3 ||
|
||||||
|
_bridgeInjectCount % 5 == 0) {
|
||||||
|
_log('inject_bridge reason=$reason count=$_bridgeInjectCount');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await _controller.runJavaScript(
|
||||||
|
HotgameJsBridge.bootstrapScript(launchPayload: _buildLaunchPayload()),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
_log(
|
||||||
|
'inject_bridge_error reason=$reason error=${_clip(error.toString(), 300)}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleBridgeMessage(JavaScriptMessage message) async {
|
||||||
|
_log('channel_message raw=${_clip(message.message, 600)}');
|
||||||
|
final bridgeMessage = HotgameBridgeMessage.parse(message.message);
|
||||||
|
await _dispatchBridgeMessage(bridgeMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleNamedChannelMessage(
|
||||||
|
String action,
|
||||||
|
JavaScriptMessage message,
|
||||||
|
) async {
|
||||||
|
final bridgeMessage = HotgameBridgeMessage.fromAction(
|
||||||
|
action,
|
||||||
|
message.message,
|
||||||
|
);
|
||||||
|
_log('named_channel action=$action raw=${_clip(message.message, 600)}');
|
||||||
|
await _dispatchBridgeMessage(bridgeMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _dispatchBridgeMessage(
|
||||||
|
HotgameBridgeMessage bridgeMessage,
|
||||||
|
) async {
|
||||||
|
if (bridgeMessage.action == HotgameBridgeActions.debugLog) {
|
||||||
|
final tag = bridgeMessage.payload['tag']?.toString().trim();
|
||||||
|
final message = bridgeMessage.payload['message']?.toString() ?? '';
|
||||||
|
_log('h5_debug tag=${tag ?? 'unknown'} message=${_clip(message, 800)}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_didReceiveBridgeMessage = true;
|
||||||
|
_stopBridgeBootstrap(reason: 'bridge_message_${bridgeMessage.action}');
|
||||||
|
_log(
|
||||||
|
'bridge_action action=${bridgeMessage.action} '
|
||||||
|
'payload=${_stringifyForLog(_sanitizeForLog(bridgeMessage.payload))}',
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (bridgeMessage.action) {
|
||||||
|
case HotgameBridgeActions.recharge:
|
||||||
|
await _openRechargeAndNotifyGame();
|
||||||
|
break;
|
||||||
|
case HotgameBridgeActions.quit:
|
||||||
|
await _closeAndExit(reason: 'h5_quit');
|
||||||
|
break;
|
||||||
|
case HotgameBridgeActions.loadComplete:
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
_errorMessage = null;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_log('bridge_action_unhandled action=${bridgeMessage.action}');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _openRechargeAndNotifyGame() async {
|
||||||
|
_log('hotgame_recharge open_wallet');
|
||||||
|
await SCNavigatorUtils.push(context, WalletRoute.recharge);
|
||||||
|
try {
|
||||||
|
await _controller.runJavaScript(
|
||||||
|
HotgameJsBridge.buildRechargeSuccessScript(),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
_log(
|
||||||
|
'recharge_success_notify_error error=${_clip(error.toString(), 300)}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _reload() async {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_errorMessage = null;
|
||||||
|
_isLoading = true;
|
||||||
|
});
|
||||||
|
await _loadGameEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _closeAndExit({String reason = 'user_exit'}) async {
|
||||||
|
if (_isClosing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_isClosing = true;
|
||||||
|
try {
|
||||||
|
final sessionId = widget.launchModel.gameSessionId.trim();
|
||||||
|
if (sessionId.isNotEmpty && !sessionId.startsWith('mock')) {
|
||||||
|
await _repository.closeGame(
|
||||||
|
provider: widget.game.provider,
|
||||||
|
roomId: widget.roomId,
|
||||||
|
gameSessionId: sessionId,
|
||||||
|
reason: reason,
|
||||||
|
params: const <String, dynamic>{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
_log('close_error error=${_clip(error.toString(), 300)}');
|
||||||
|
}
|
||||||
|
if (mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _resolveScreenMode() {
|
||||||
|
return resolveRoomGameScreenMode(
|
||||||
|
widget.game.launchParams,
|
||||||
|
fullScreen: widget.game.fullScreen,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _resolveSafeHeight() {
|
||||||
|
if (widget.launchModel.entry.safeHeight > 0) {
|
||||||
|
return widget.launchModel.entry.safeHeight;
|
||||||
|
}
|
||||||
|
if (widget.game.safeHeight > 0) {
|
||||||
|
return widget.game.safeHeight;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double _calculateBodyHeight(BuildContext context) {
|
||||||
|
final screenMode = _resolveScreenMode();
|
||||||
|
final maxDialogHeight = resolveRoomGameAvailableHeight(
|
||||||
|
context,
|
||||||
|
reservedTop: 12.w,
|
||||||
|
);
|
||||||
|
if (maxDialogHeight <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (screenMode == 'full') {
|
||||||
|
return maxDialogHeight;
|
||||||
|
}
|
||||||
|
if (screenMode == 'seven') {
|
||||||
|
return maxDialogHeight * _sevenScreenRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
return clampRoomGameHeight(
|
||||||
|
_calculatePreferredBodyHeight(context),
|
||||||
|
maxDialogHeight,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
double _calculatePreferredBodyHeight(BuildContext context) {
|
||||||
|
final mediaQuery = MediaQuery.maybeOf(context);
|
||||||
|
final screenSize = mediaQuery?.size ?? Size.zero;
|
||||||
|
final safeHeight = _resolveSafeHeight();
|
||||||
|
final fallback = screenSize.width;
|
||||||
|
|
||||||
|
if (safeHeight > 0 && screenSize.width > 0) {
|
||||||
|
final ratio = roomGameDesignWidth / safeHeight;
|
||||||
|
return screenSize.width / ratio;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _log(String message) {}
|
||||||
|
|
||||||
|
String _maskValue(String value, {int keepStart = 6, int keepEnd = 4}) {
|
||||||
|
final trimmed = value.trim();
|
||||||
|
if (trimmed.isEmpty) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (trimmed.length <= keepStart + keepEnd) {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
return '${trimmed.substring(0, keepStart)}***${trimmed.substring(trimmed.length - keepEnd)}';
|
||||||
|
}
|
||||||
|
|
||||||
|
Object? _sanitizeForLog(Object? value, {String key = ''}) {
|
||||||
|
final lowerKey = key.toLowerCase();
|
||||||
|
final shouldMask =
|
||||||
|
lowerKey.contains('code') ||
|
||||||
|
lowerKey.contains('token') ||
|
||||||
|
lowerKey.contains('authorization');
|
||||||
|
if (value is Map) {
|
||||||
|
final result = <String, dynamic>{};
|
||||||
|
for (final MapEntry<dynamic, dynamic> entry in value.entries) {
|
||||||
|
result[entry.key.toString()] = _sanitizeForLog(
|
||||||
|
entry.value,
|
||||||
|
key: entry.key.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (value is List) {
|
||||||
|
return value.map((Object? item) => _sanitizeForLog(item)).toList();
|
||||||
|
}
|
||||||
|
if (value is String) {
|
||||||
|
final normalized = shouldMask ? _maskValue(value) : value;
|
||||||
|
return _clip(normalized, 600);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _clip(String value, [int limit = 600]) {
|
||||||
|
final trimmed = value.trim();
|
||||||
|
if (trimmed.length <= limit) {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
return '${trimmed.substring(0, limit)}...';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _stringifyForLog(Object? value) {
|
||||||
|
if (value == null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return _clip(jsonEncode(value), 800);
|
||||||
|
} catch (_) {
|
||||||
|
return _clip(value.toString(), 800);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _buildLaunchPayload() {
|
||||||
|
return <String, dynamic>{
|
||||||
|
'provider': widget.launchModel.provider,
|
||||||
|
'gameId': widget.launchModel.gameId,
|
||||||
|
'providerGameId': widget.launchModel.providerGameId,
|
||||||
|
'gameSessionId': widget.launchModel.gameSessionId,
|
||||||
|
'entry': widget.launchModel.entry.toJson(),
|
||||||
|
'launchConfig': widget.launchModel.launchConfig.toJson(),
|
||||||
|
'roomState': widget.launchModel.roomState.toJson(),
|
||||||
|
'game': <String, dynamic>{
|
||||||
|
'gameId': widget.game.gameId,
|
||||||
|
'provider': widget.game.provider,
|
||||||
|
'gameType': widget.game.gameType,
|
||||||
|
'name': widget.game.name,
|
||||||
|
'launchMode': widget.game.launchMode,
|
||||||
|
'launchParams': widget.game.launchParams,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _buildLaunchSummary() {
|
||||||
|
final payload = _buildLaunchPayload();
|
||||||
|
return <String, dynamic>{
|
||||||
|
'roomId': widget.roomId,
|
||||||
|
'provider': widget.game.provider,
|
||||||
|
'gameId': widget.game.gameId,
|
||||||
|
'providerGameId': widget.launchModel.providerGameId,
|
||||||
|
'gameSessionId': widget.launchModel.gameSessionId,
|
||||||
|
'entry': _sanitizeForLog(payload['entry']),
|
||||||
|
'launchConfig': _sanitizeForLog(payload['launchConfig']),
|
||||||
|
'roomState': _sanitizeForLog(payload['roomState']),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
String _buildMockHtml() {
|
||||||
|
return '''
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style>
|
||||||
|
body { margin: 0; font-family: sans-serif; background: #111827; color: #fff; }
|
||||||
|
.wrap { padding: 24px; }
|
||||||
|
button {
|
||||||
|
margin: 8px 8px 0 0;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #2563eb;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
#log { margin-top: 16px; white-space: pre-wrap; color: #bfdbfe; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="wrap">
|
||||||
|
<h3>Hotgame Mock</h3>
|
||||||
|
<button onclick="loadComplete.postMessage(null)">loadComplete</button>
|
||||||
|
<button onclick="window.JsBridge.recharge()">JsBridge.recharge()</button>
|
||||||
|
<button onclick="window.JsBridge.quit()">JsBridge.quit()</button>
|
||||||
|
<button onclick="window.webkit.messageHandlers.recharge.postMessage(null)">webkit recharge</button>
|
||||||
|
<button onclick="window.webkit.messageHandlers.quit.postMessage(null)">webkit quit</button>
|
||||||
|
<button onclick="window.rechargeSuccess && window.rechargeSuccess()">rechargeSuccess()</button>
|
||||||
|
<div id="log"></div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
window.rechargeSuccess = function() {
|
||||||
|
document.getElementById('log').textContent += '\\nrechargeSuccess called';
|
||||||
|
};
|
||||||
|
document.getElementById('log').textContent =
|
||||||
|
'launchConfig=' + JSON.stringify(window.hotgameLaunchConfig || {});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
''';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final bodyHeight = _calculateBodyHeight(context);
|
||||||
|
final bottomFrameHeight = resolveRoomGameCompatibilityBottomHeight(context);
|
||||||
|
return PopScope(
|
||||||
|
canPop: false,
|
||||||
|
onPopInvokedWithResult: (bool didPop, Object? result) async {
|
||||||
|
if (didPop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await _closeAndExit();
|
||||||
|
},
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(24.w),
|
||||||
|
topRight: Radius.circular(24.w),
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
width: ScreenUtil().screenWidth,
|
||||||
|
height: bodyHeight + bottomFrameHeight,
|
||||||
|
color: const Color(0xFF081915),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: bottomFrameHeight,
|
||||||
|
child: WebViewWidget(
|
||||||
|
controller: _controller,
|
||||||
|
gestureRecognizers: _webGestureRecognizers,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (bottomFrameHeight > 0)
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
height: bottomFrameHeight,
|
||||||
|
child: RoomGameCompatibilityBottomFrame(
|
||||||
|
height: bottomFrameHeight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_errorMessage != null) _buildErrorState(),
|
||||||
|
if (_isLoading && _errorMessage == null)
|
||||||
|
const IgnorePointer(
|
||||||
|
ignoring: true,
|
||||||
|
child: BaishunLoadingView(
|
||||||
|
message: 'Waiting for Hotgame...',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildErrorState() {
|
||||||
|
return Positioned.fill(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black.withValues(alpha: 0.88),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 28.w),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.error_outline, color: Colors.white70, size: 34.w),
|
||||||
|
SizedBox(height: 12.w),
|
||||||
|
Text(
|
||||||
|
'Hotgame failed to load',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 16.sp,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.w),
|
||||||
|
Text(
|
||||||
|
_errorMessage ?? '',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(color: Colors.white70, fontSize: 12.sp),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.w),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
TextButton(onPressed: _reload, child: const Text('Retry')),
|
||||||
|
SizedBox(width: 10.w),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
SCTts.show('Please verify the Hotgame entry url');
|
||||||
|
},
|
||||||
|
child: const Text('Tips'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,9 +9,11 @@ import 'package:yumi/modules/room_game/data/models/room_game_models.dart';
|
|||||||
import 'package:yumi/modules/room_game/data/room_game_repository.dart';
|
import 'package:yumi/modules/room_game/data/room_game_repository.dart';
|
||||||
import 'package:yumi/modules/room_game/utils/room_game_viewport.dart';
|
import 'package:yumi/modules/room_game/utils/room_game_viewport.dart';
|
||||||
import 'package:yumi/modules/room_game/views/baishun_game_page.dart';
|
import 'package:yumi/modules/room_game/views/baishun_game_page.dart';
|
||||||
|
import 'package:yumi/modules/room_game/views/hotgame_game_page.dart';
|
||||||
import 'package:yumi/modules/room_game/views/leader_game_page.dart';
|
import 'package:yumi/modules/room_game/views/leader_game_page.dart';
|
||||||
import 'package:yumi/services/audio/rtc_manager.dart';
|
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||||
import 'package:yumi/shared/tools/sc_lk_dialog_util.dart';
|
import 'package:yumi/shared/tools/sc_lk_dialog_util.dart';
|
||||||
|
import 'package:yumi/shared/tools/sc_room_top_layer_guard.dart';
|
||||||
import 'package:yumi/ui_kit/components/custom_cached_image.dart';
|
import 'package:yumi/ui_kit/components/custom_cached_image.dart';
|
||||||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||||
@ -31,7 +33,6 @@ class RoomGameListSheet extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _RoomGameListSheetState extends State<RoomGameListSheet> {
|
class _RoomGameListSheetState extends State<RoomGameListSheet> {
|
||||||
static const String _logPrefix = '[RoomGameLaunch]';
|
|
||||||
static const int _itemsPerRow = 4;
|
static const int _itemsPerRow = 4;
|
||||||
static const String _sheetFrameAsset =
|
static const String _sheetFrameAsset =
|
||||||
'sc_images/room/sc_room_game_sheet_frame.png';
|
'sc_images/room/sc_room_game_sheet_frame.png';
|
||||||
@ -90,7 +91,9 @@ class _RoomGameListSheetState extends State<RoomGameListSheet> {
|
|||||||
String targetGameId,
|
String targetGameId,
|
||||||
) {
|
) {
|
||||||
final supportedItems =
|
final supportedItems =
|
||||||
items.where((item) => item.isBaishun || item.isLeader).toList();
|
items
|
||||||
|
.where((item) => item.isBaishun || item.isLeader || item.isHotgame)
|
||||||
|
.toList();
|
||||||
final candidates = supportedItems.isNotEmpty ? supportedItems : items;
|
final candidates = supportedItems.isNotEmpty ? supportedItems : items;
|
||||||
if (targetGameId == '0') {
|
if (targetGameId == '0') {
|
||||||
return candidates[math.Random().nextInt(candidates.length)];
|
return candidates[math.Random().nextInt(candidates.length)];
|
||||||
@ -117,7 +120,7 @@ class _RoomGameListSheetState extends State<RoomGameListSheet> {
|
|||||||
SCTts.show('roomId is empty');
|
SCTts.show('roomId is empty');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!game.isBaishun && !game.isLeader) {
|
if (!game.isBaishun && !game.isLeader && !game.isHotgame) {
|
||||||
SCTts.show('This game provider is not wired yet');
|
SCTts.show('This game provider is not wired yet');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -126,6 +129,7 @@ class _RoomGameListSheetState extends State<RoomGameListSheet> {
|
|||||||
_launchingGameId = game.gameId;
|
_launchingGameId = game.gameId;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
SCRoomTopLayerGuardLease? gameDialogGuard;
|
||||||
try {
|
try {
|
||||||
_log(
|
_log(
|
||||||
'launch_request roomId=$_roomId gameId=${game.gameId} '
|
'launch_request roomId=$_roomId gameId=${game.gameId} '
|
||||||
@ -147,23 +151,38 @@ class _RoomGameListSheetState extends State<RoomGameListSheet> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gameDialogGuard = SCRoomTopLayerGuard().acquire(reason: 'room_game_page');
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
await Future<void>.delayed(const Duration(milliseconds: 180));
|
await Future<void>.delayed(const Duration(milliseconds: 180));
|
||||||
if (!widget.roomContext.mounted) {
|
if (!widget.roomContext.mounted) {
|
||||||
|
gameDialogGuard.release();
|
||||||
|
gameDialogGuard = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final gamePage = _buildGamePage(game, launchModel);
|
final gamePage = _buildGamePage(game, launchModel);
|
||||||
if (gamePage == null) {
|
if (gamePage == null) {
|
||||||
|
gameDialogGuard.release();
|
||||||
|
gameDialogGuard = null;
|
||||||
SCTts.show('This game provider is not wired yet');
|
SCTts.show('This game provider is not wired yet');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showBottomInBottomDialog(
|
final activeGameDialogGuard = gameDialogGuard;
|
||||||
widget.roomContext,
|
gameDialogGuard = null;
|
||||||
gamePage,
|
try {
|
||||||
barrierColor: Colors.black54,
|
unawaited(
|
||||||
barrierDismissible: true,
|
showBottomInBottomDialog(
|
||||||
);
|
widget.roomContext,
|
||||||
|
gamePage,
|
||||||
|
barrierColor: Colors.black54,
|
||||||
|
barrierDismissible: true,
|
||||||
|
).whenComplete(activeGameDialogGuard.release),
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
activeGameDialogGuard.release();
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
gameDialogGuard?.release();
|
||||||
_log('launch_error error=${_clip(error.toString(), 400)}');
|
_log('launch_error error=${_clip(error.toString(), 400)}');
|
||||||
SCTts.show('Launch failed');
|
SCTts.show('Launch failed');
|
||||||
} finally {
|
} finally {
|
||||||
@ -251,6 +270,13 @@ class _RoomGameListSheetState extends State<RoomGameListSheet> {
|
|||||||
launchModel: launchModel,
|
launchModel: launchModel,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (game.isHotgame) {
|
||||||
|
return HotgameGamePage(
|
||||||
|
roomId: _roomId,
|
||||||
|
game: game,
|
||||||
|
launchModel: launchModel,
|
||||||
|
);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -178,7 +178,8 @@ class _SplashPageState extends State<SplashPage> {
|
|||||||
Image.asset(
|
Image.asset(
|
||||||
SCGlobalConfig.businessLogicStrategy.getSplashPageIcon(),
|
SCGlobalConfig.businessLogicStrategy.getSplashPageIcon(),
|
||||||
width: 107.w,
|
width: 107.w,
|
||||||
height: 159.w,
|
height: 97.w,
|
||||||
|
fit: BoxFit.contain,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
|||||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
|
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.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:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:yumi/ui_kit/components/appbar/socialchat_appbar.dart';
|
import 'package:yumi/ui_kit/components/appbar/socialchat_appbar.dart';
|
||||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||||
@ -17,8 +18,11 @@ import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
|||||||
import 'package:yumi/shared/tools/sc_lk_dialog_util.dart';
|
import 'package:yumi/shared/tools/sc_lk_dialog_util.dart';
|
||||||
import 'package:yumi/services/audio/rtc_manager.dart';
|
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||||
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
||||||
|
import 'package:yumi/modules/country/country_route.dart';
|
||||||
import '../../../shared/tools/sc_pick_utils.dart';
|
import '../../../shared/tools/sc_pick_utils.dart';
|
||||||
|
import '../../../shared/business_logic/models/res/country_res.dart';
|
||||||
import '../../../shared/business_logic/models/res/login_res.dart';
|
import '../../../shared/business_logic/models/res/login_res.dart';
|
||||||
|
import '../../../shared/business_logic/models/res/sc_user_identity_res.dart';
|
||||||
import '../../../shared/business_logic/usecases/sc_accurate_length_limiting_textInput_formatter.dart';
|
import '../../../shared/business_logic/usecases/sc_accurate_length_limiting_textInput_formatter.dart';
|
||||||
|
|
||||||
class EditUserInfoPage2 extends StatefulWidget {
|
class EditUserInfoPage2 extends StatefulWidget {
|
||||||
@ -37,6 +41,9 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
|||||||
String? bornMonth;
|
String? bornMonth;
|
||||||
String? autograph;
|
String? autograph;
|
||||||
String? hobby;
|
String? hobby;
|
||||||
|
String? countryId;
|
||||||
|
String? countryCode;
|
||||||
|
String? countryName;
|
||||||
String? bornDay;
|
String? bornDay;
|
||||||
String? bornYear;
|
String? bornYear;
|
||||||
String nickName = "";
|
String nickName = "";
|
||||||
@ -53,12 +60,32 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
|||||||
nickName = currentProfile?.userNickname ?? "";
|
nickName = currentProfile?.userNickname ?? "";
|
||||||
autograph = currentProfile?.autograph ?? "";
|
autograph = currentProfile?.autograph ?? "";
|
||||||
hobby = currentProfile?.hobby ?? "";
|
hobby = currentProfile?.hobby ?? "";
|
||||||
birthdayDate = _birthdayFromProfile(currentProfile);
|
countryId = currentProfile?.countryId ?? "";
|
||||||
bornMonth = _formatTwoDigits(currentProfile?.bornMonth);
|
countryCode = currentProfile?.countryCode ?? "";
|
||||||
bornDay = _formatTwoDigits(currentProfile?.bornDay);
|
countryName = currentProfile?.countryName ?? "";
|
||||||
bornYear = _formatYear(currentProfile?.bornYear);
|
birthdayDate =
|
||||||
|
_birthdayFromProfile(currentProfile) ??
|
||||||
|
_birthdayFromAge(currentProfile?.age);
|
||||||
|
bornMonth = _formatTwoDigits(
|
||||||
|
currentProfile?.bornMonth ?? birthdayDate?.month,
|
||||||
|
);
|
||||||
|
bornDay = _formatTwoDigits(currentProfile?.bornDay ?? birthdayDate?.day);
|
||||||
|
bornYear = _formatYear(currentProfile?.bornYear ?? birthdayDate?.year);
|
||||||
age = currentProfile?.age ?? _ageFromBirthday(birthdayDate);
|
age = currentProfile?.age ?? _ageFromBirthday(birthdayDate);
|
||||||
sex = currentProfile?.userSex;
|
sex = currentProfile?.userSex;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Provider.of<SocialChatUserProfileManager>(
|
||||||
|
context,
|
||||||
|
listen: false,
|
||||||
|
).getUserIdentity();
|
||||||
|
Provider.of<SCAppGeneralManager>(
|
||||||
|
context,
|
||||||
|
listen: false,
|
||||||
|
).fetchCountryList();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
String? _preferNonEmpty(String? primary, String? fallback) {
|
String? _preferNonEmpty(String? primary, String? fallback) {
|
||||||
@ -71,21 +98,6 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
|||||||
return primary ?? fallback;
|
return primary ?? fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isBrokenLocalMediaUrl(String? url) {
|
|
||||||
return (url ?? "").contains("/external/oss/local/");
|
|
||||||
}
|
|
||||||
|
|
||||||
String? _preferUsableAvatar(String? primary, String? fallback) {
|
|
||||||
if ((primary ?? "").trim().isNotEmpty && !_isBrokenLocalMediaUrl(primary)) {
|
|
||||||
return primary;
|
|
||||||
}
|
|
||||||
if ((fallback ?? "").trim().isNotEmpty &&
|
|
||||||
!_isBrokenLocalMediaUrl(fallback)) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
return _preferNonEmpty(primary, fallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
DateTime? _birthdayFromProfile([SocialChatUserProfile? profile]) {
|
DateTime? _birthdayFromProfile([SocialChatUserProfile? profile]) {
|
||||||
final targetProfile =
|
final targetProfile =
|
||||||
profile ?? AccountStorage().getCurrentUser()?.userProfile;
|
profile ?? AccountStorage().getCurrentUser()?.userProfile;
|
||||||
@ -125,6 +137,15 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
|||||||
return (DateTime.now().year - date.year).abs();
|
return (DateTime.now().year - date.year).abs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DateTime? _birthdayFromAge(num? age) {
|
||||||
|
final ageValue = age?.toInt() ?? 0;
|
||||||
|
if (ageValue <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final now = DateTime.now();
|
||||||
|
return DateTime(now.year - ageValue, now.month, now.day);
|
||||||
|
}
|
||||||
|
|
||||||
String _birthdayDisplayText() {
|
String _birthdayDisplayText() {
|
||||||
if ((bornYear ?? "").isEmpty ||
|
if ((bornYear ?? "").isEmpty ||
|
||||||
(bornMonth ?? "").isEmpty ||
|
(bornMonth ?? "").isEmpty ||
|
||||||
@ -144,16 +165,50 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
|||||||
return values.any((value) => !identical(value, _noChange));
|
return values.any((value) => !identical(value, _noChange));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _hasIdentity(SCUserIdentityRes? identity) {
|
||||||
|
if (identity == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return (identity.anchor ?? false) ||
|
||||||
|
(identity.bd ?? false) ||
|
||||||
|
(identity.agent ?? false) ||
|
||||||
|
(identity.bdLeader ?? false) ||
|
||||||
|
(identity.freightAgent ?? false) ||
|
||||||
|
(identity.superFreightAgent ?? false) ||
|
||||||
|
(identity.admin ?? false) ||
|
||||||
|
(identity.superAdmin ?? false) ||
|
||||||
|
(identity.manager ?? false) ||
|
||||||
|
(identity.yumiManager ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _canChangeCountry(SCUserIdentityRes? identity) {
|
||||||
|
return !_hasIdentity(identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _countryDisplayText() {
|
||||||
|
final displayName = (countryName ?? "").trim();
|
||||||
|
if (displayName.isNotEmpty) {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
return (countryCode ?? "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
void _syncLocalProfileState(SocialChatUserProfile profile) {
|
void _syncLocalProfileState(SocialChatUserProfile profile) {
|
||||||
userCover = _preferUsableAvatar(profile.userAvatar, userCover);
|
userCover = profile.userAvatar;
|
||||||
nickName = profile.userNickname ?? nickName;
|
nickName = profile.userNickname ?? nickName;
|
||||||
autograph = profile.autograph ?? autograph;
|
autograph = profile.autograph ?? autograph;
|
||||||
hobby = profile.hobby ?? hobby;
|
hobby = profile.hobby ?? hobby;
|
||||||
|
countryId = profile.countryId ?? countryId;
|
||||||
|
countryCode = profile.countryCode ?? countryCode;
|
||||||
|
countryName = profile.countryName ?? countryName;
|
||||||
sex = profile.userSex ?? sex;
|
sex = profile.userSex ?? sex;
|
||||||
birthdayDate = _birthdayFromProfile(profile) ?? birthdayDate;
|
birthdayDate =
|
||||||
bornYear = _formatYear(profile.bornYear);
|
_birthdayFromProfile(profile) ??
|
||||||
bornMonth = _formatTwoDigits(profile.bornMonth);
|
_birthdayFromAge(profile.age) ??
|
||||||
bornDay = _formatTwoDigits(profile.bornDay);
|
birthdayDate;
|
||||||
|
bornYear = _formatYear(profile.bornYear ?? birthdayDate?.year);
|
||||||
|
bornMonth = _formatTwoDigits(profile.bornMonth ?? birthdayDate?.month);
|
||||||
|
bornDay = _formatTwoDigits(profile.bornDay ?? birthdayDate?.day);
|
||||||
age = profile.age ?? _ageFromBirthday(birthdayDate) ?? age;
|
age = profile.age ?? _ageFromBirthday(birthdayDate) ?? age;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,11 +278,9 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
|||||||
String url,
|
String url,
|
||||||
) {
|
) {
|
||||||
if (success) {
|
if (success) {
|
||||||
userCover = url;
|
submit(userAvatarValue: url);
|
||||||
setState(() {});
|
|
||||||
submitAvatarOnly();
|
|
||||||
}
|
}
|
||||||
});
|
}, allowGif: true);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SizedBox(height: 3.w),
|
SizedBox(height: 3.w),
|
||||||
@ -243,9 +296,7 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
|||||||
sex == 1
|
sex == 1
|
||||||
? SCAppLocalizations.of(context)!.man
|
? SCAppLocalizations.of(context)!.man
|
||||||
: SCAppLocalizations.of(context)!.woman,
|
: SCAppLocalizations.of(context)!.woman,
|
||||||
() {
|
null,
|
||||||
_showSexDialog();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
|
||||||
_buildItem(
|
_buildItem(
|
||||||
@ -270,6 +321,12 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
|||||||
_showInputBioHobby(hobby ?? "", 2);
|
_showInputBioHobby(hobby ?? "", 2);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
if (_canChangeCountry(ref.userIdentity))
|
||||||
|
_buildItem(
|
||||||
|
"${SCAppLocalizations.of(context)!.countryRegion}:",
|
||||||
|
_countryDisplayText(),
|
||||||
|
_openCountryPicker,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -390,13 +447,6 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
|||||||
return eighteenYearsAgo;
|
return eighteenYearsAgo;
|
||||||
}
|
}
|
||||||
|
|
||||||
void submitAvatarOnly() {
|
|
||||||
if ((userCover ?? "").trim().isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
submit(userAvatarValue: userCover);
|
|
||||||
}
|
|
||||||
|
|
||||||
void submit({
|
void submit({
|
||||||
Object? userAvatarValue = _noChange,
|
Object? userAvatarValue = _noChange,
|
||||||
Object? userNicknameValue = _noChange,
|
Object? userNicknameValue = _noChange,
|
||||||
@ -407,6 +457,7 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
|||||||
Object? bornDayValue = _noChange,
|
Object? bornDayValue = _noChange,
|
||||||
Object? hobbyValue = _noChange,
|
Object? hobbyValue = _noChange,
|
||||||
Object? autographValue = _noChange,
|
Object? autographValue = _noChange,
|
||||||
|
Object? countryValue = _noChange,
|
||||||
}) async {
|
}) async {
|
||||||
if (!_hasChanges([
|
if (!_hasChanges([
|
||||||
userAvatarValue,
|
userAvatarValue,
|
||||||
@ -418,6 +469,7 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
|||||||
bornDayValue,
|
bornDayValue,
|
||||||
hobbyValue,
|
hobbyValue,
|
||||||
autographValue,
|
autographValue,
|
||||||
|
countryValue,
|
||||||
])) {
|
])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -432,6 +484,8 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
|||||||
_isSubmitting = true;
|
_isSubmitting = true;
|
||||||
SCLoadingManager.show();
|
SCLoadingManager.show();
|
||||||
try {
|
try {
|
||||||
|
final selectedCountry =
|
||||||
|
identical(countryValue, _noChange) ? null : countryValue as Country?;
|
||||||
final updatedProfile = await SCAccountRepository().updateUserInfo(
|
final updatedProfile = await SCAccountRepository().updateUserInfo(
|
||||||
userAvatar:
|
userAvatar:
|
||||||
identical(userAvatarValue, _noChange)
|
identical(userAvatarValue, _noChange)
|
||||||
@ -457,14 +511,9 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
|||||||
identical(autographValue, _noChange)
|
identical(autographValue, _noChange)
|
||||||
? null
|
? null
|
||||||
: autographValue as String?,
|
: autographValue as String?,
|
||||||
|
countryId: selectedCountry?.id,
|
||||||
);
|
);
|
||||||
final mergedProfile = updatedProfile.copyWith(
|
final mergedProfile = updatedProfile.copyWith(
|
||||||
userAvatar: _preferUsableAvatar(
|
|
||||||
updatedProfile.userAvatar,
|
|
||||||
identical(userAvatarValue, _noChange)
|
|
||||||
? userCover
|
|
||||||
: userAvatarValue as String?,
|
|
||||||
),
|
|
||||||
userNickname: _preferNonEmpty(
|
userNickname: _preferNonEmpty(
|
||||||
updatedProfile.userNickname,
|
updatedProfile.userNickname,
|
||||||
identical(userNicknameValue, _noChange)
|
identical(userNicknameValue, _noChange)
|
||||||
@ -502,6 +551,21 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
|||||||
(identical(bornYearValue, _noChange)
|
(identical(bornYearValue, _noChange)
|
||||||
? birthdayDate?.year
|
? birthdayDate?.year
|
||||||
: bornYearValue as num?),
|
: bornYearValue as num?),
|
||||||
|
countryId: _preferNonEmpty(
|
||||||
|
updatedProfile.countryId,
|
||||||
|
selectedCountry?.id,
|
||||||
|
),
|
||||||
|
countryCode: _preferNonEmpty(
|
||||||
|
updatedProfile.countryCode,
|
||||||
|
selectedCountry?.alphaTwo,
|
||||||
|
),
|
||||||
|
countryName: _preferNonEmpty(
|
||||||
|
updatedProfile.countryName,
|
||||||
|
_preferNonEmpty(
|
||||||
|
selectedCountry?.countryName,
|
||||||
|
selectedCountry?.aliasName,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
_syncLocalProfileState(mergedProfile);
|
_syncLocalProfileState(mergedProfile);
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
@ -515,13 +579,50 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
|||||||
true;
|
true;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
SCTts.show(SCAppLocalizations.of(context)!.operationSuccessful);
|
SCTts.show(SCAppLocalizations.of(context)!.operationSuccessful);
|
||||||
} catch (e) {
|
if (selectedCountry != null) {
|
||||||
|
AccountStorage().logout(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// The repository/network layer already shows request errors, so keep the
|
||||||
|
// current edit page state and only release the submit/loading guards here.
|
||||||
} finally {
|
} finally {
|
||||||
_isSubmitting = false;
|
_isSubmitting = false;
|
||||||
SCLoadingManager.hide();
|
SCLoadingManager.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _openCountryPicker() async {
|
||||||
|
final userIdentity =
|
||||||
|
Provider.of<SocialChatUserProfileManager>(
|
||||||
|
context,
|
||||||
|
listen: false,
|
||||||
|
).userIdentity;
|
||||||
|
if (!_canChangeCountry(userIdentity)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final generalManager = Provider.of<SCAppGeneralManager>(
|
||||||
|
context,
|
||||||
|
listen: false,
|
||||||
|
);
|
||||||
|
generalManager.clearCountrySelection();
|
||||||
|
await SCNavigatorUtils.push(context, CountryRoute.country, replace: false);
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final selectedCountry = generalManager.selectCountryInfo;
|
||||||
|
if (selectedCountry == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final selectedCountryId = (selectedCountry.id ?? "").trim();
|
||||||
|
if (selectedCountryId.isEmpty ||
|
||||||
|
selectedCountryId == (countryId ?? "").trim()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
submit(countryValue: selectedCountry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore: unused_element
|
||||||
void _showSexDialog() {
|
void _showSexDialog() {
|
||||||
showCenterDialog(
|
showCenterDialog(
|
||||||
context,
|
context,
|
||||||
@ -603,7 +704,7 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildItem(String title, String value, Function() func) {
|
_buildItem(String title, String value, Function()? func) {
|
||||||
return SCDebounceWidget(
|
return SCDebounceWidget(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@ -622,11 +723,12 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
|||||||
child: text(value, textColor: Colors.white, fontSize: 15.sp),
|
child: text(value, textColor: Colors.white, fontSize: 15.sp),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Icon(
|
if (func != null)
|
||||||
Icons.keyboard_arrow_right,
|
Icon(
|
||||||
size: 20.w,
|
Icons.keyboard_arrow_right,
|
||||||
color: Colors.white70,
|
size: 20.w,
|
||||||
),
|
color: Colors.white70,
|
||||||
|
),
|
||||||
SizedBox(width: 8.w),
|
SizedBox(width: 8.w),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -640,7 +742,7 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
func.call();
|
func?.call();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
@ -17,19 +19,26 @@ import 'package:yumi/shared/business_logic/models/res/sc_vip_res.dart';
|
|||||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
|
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_vip_repository_imp.dart';
|
import 'package:yumi/shared/data_sources/sources/repositories/sc_vip_repository_imp.dart';
|
||||||
|
import 'package:yumi/shared/tools/sc_lk_event_bus.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/widgets/id/sc_special_id_badge.dart';
|
import 'package:yumi/ui_kit/widgets/id/sc_special_id_badge.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/svga/sc_svga_asset_widget.dart';
|
import 'package:yumi/ui_kit/widgets/svga/sc_svga_asset_widget.dart';
|
||||||
|
|
||||||
class MePage2 extends StatefulWidget {
|
class MePage2 extends StatefulWidget {
|
||||||
const MePage2({super.key});
|
const MePage2({super.key, this.currentIndexListenable, this.tabIndex = 3});
|
||||||
|
|
||||||
|
final ValueListenable<int>? currentIndexListenable;
|
||||||
|
final int tabIndex;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MePage2> createState() => _MePage2State();
|
State<MePage2> createState() => _MePage2State();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MePage2State extends State<MePage2> {
|
class _MePage2State extends State<MePage2> with WidgetsBindingObserver {
|
||||||
|
static const Duration _counterStaleDuration = Duration(seconds: 30);
|
||||||
|
static const String _cpActivityUrl =
|
||||||
|
'https://h5.global-interaction.com/activity/cp-space/yumi.html';
|
||||||
static const String _inviteActivityUrl =
|
static const String _inviteActivityUrl =
|
||||||
'https://h5.haiyihy.com/app-invite/index.html';
|
'https://h5.haiyihy.com/app-invite/index.html';
|
||||||
static const double _inviteActivityHorizontalScale = 750 / 662;
|
static const double _inviteActivityHorizontalScale = 750 / 662;
|
||||||
@ -37,15 +46,52 @@ class _MePage2State extends State<MePage2> {
|
|||||||
Map<String, SCUserCounterRes> _counterMap = {};
|
Map<String, SCUserCounterRes> _counterMap = {};
|
||||||
SCVipStatusRes? _vipStatus;
|
SCVipStatusRes? _vipStatus;
|
||||||
bool _isVipStatusLoading = false;
|
bool _isVipStatusLoading = false;
|
||||||
|
bool _isCounterLoading = false;
|
||||||
|
bool _counterRefreshQueued = false;
|
||||||
|
bool _counterReloadAfterCurrent = false;
|
||||||
|
DateTime? _lastCounterLoadedAt;
|
||||||
|
StreamSubscription<SCUserRelationChangedEvent>? _relationChangedSubscription;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
widget.currentIndexListenable?.addListener(_handleTabVisibilityChanged);
|
||||||
|
_relationChangedSubscription = eventBus
|
||||||
|
.on<SCUserRelationChangedEvent>()
|
||||||
|
.listen(_handleUserRelationChanged);
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
_loadPageData();
|
_loadPageData();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant MePage2 oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (oldWidget.currentIndexListenable == widget.currentIndexListenable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
oldWidget.currentIndexListenable?.removeListener(
|
||||||
|
_handleTabVisibilityChanged,
|
||||||
|
);
|
||||||
|
widget.currentIndexListenable?.addListener(_handleTabVisibilityChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
widget.currentIndexListenable?.removeListener(_handleTabVisibilityChanged);
|
||||||
|
_relationChangedSubscription?.cancel();
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
if (state == AppLifecycleState.resumed && _isCurrentTabVisible) {
|
||||||
|
_refreshCounterIfNeeded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _loadPageData() {
|
void _loadPageData() {
|
||||||
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
||||||
context,
|
context,
|
||||||
@ -58,10 +104,89 @@ class _MePage2State extends State<MePage2> {
|
|||||||
_loadVipStatus();
|
_loadVipStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadCounter() async {
|
bool get _isCurrentTabVisible {
|
||||||
|
final currentIndex = widget.currentIndexListenable?.value;
|
||||||
|
return currentIndex == null || currentIndex == widget.tabIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get _isCounterStale {
|
||||||
|
final lastLoadedAt = _lastCounterLoadedAt;
|
||||||
|
if (lastLoadedAt == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return DateTime.now().difference(lastLoadedAt) >= _counterStaleDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleTabVisibilityChanged() {
|
||||||
|
if (!_isCurrentTabVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_refreshCounterIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _refreshCounterIfNeeded() {
|
||||||
|
if (_counterRefreshQueued || _isCounterStale) {
|
||||||
|
unawaited(_loadCounter(force: true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleUserRelationChanged(SCUserRelationChangedEvent event) {
|
||||||
|
final currentUserId =
|
||||||
|
AccountStorage().getCurrentUser()?.userProfile?.id?.trim() ?? "";
|
||||||
|
if (currentUserId.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final actorUserId = event.actorUserId.trim();
|
||||||
|
final targetUserId = event.targetUserId.trim();
|
||||||
|
final affectsCurrentUser =
|
||||||
|
actorUserId == currentUserId || targetUserId == currentUserId;
|
||||||
|
if (!affectsCurrentUser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final delta = event.isFollowing ? 1 : -1;
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
if (actorUserId == currentUserId) {
|
||||||
|
_applyCounterDelta("SUBSCRIPTION", delta);
|
||||||
|
}
|
||||||
|
if (targetUserId == currentUserId) {
|
||||||
|
_applyCounterDelta("FANS", delta);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_counterRefreshQueued = true;
|
||||||
|
if (_isCurrentTabVisible) {
|
||||||
|
unawaited(_loadCounter(force: true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _applyCounterDelta(String type, int delta) {
|
||||||
|
final currentCounter = _counterMap[type];
|
||||||
|
final currentQuantity = int.tryParse(currentCounter?.quantity ?? "0") ?? 0;
|
||||||
|
final nextQuantity = (currentQuantity + delta).clamp(0, 1 << 31);
|
||||||
|
_counterMap = {
|
||||||
|
..._counterMap,
|
||||||
|
type: (currentCounter ?? SCUserCounterRes(counterType: type)).copyWith(
|
||||||
|
quantity: nextQuantity.toString(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadCounter({bool force = false}) async {
|
||||||
|
if (_isCounterLoading) {
|
||||||
|
if (force || _counterRefreshQueued) {
|
||||||
|
_counterReloadAfterCurrent = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!force && !_counterRefreshQueued && !_isCounterStale) return;
|
||||||
final userId = AccountStorage().getCurrentUser()?.userProfile?.id ?? "";
|
final userId = AccountStorage().getCurrentUser()?.userProfile?.id ?? "";
|
||||||
if (userId.isEmpty) return;
|
if (userId.isEmpty) return;
|
||||||
|
|
||||||
|
_isCounterLoading = true;
|
||||||
try {
|
try {
|
||||||
final userCounterList = await SCAccountRepository().userCounter(userId);
|
final userCounterList = await SCAccountRepository().userCounter(userId);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
@ -71,8 +196,17 @@ class _MePage2State extends State<MePage2> {
|
|||||||
(counter) => MapEntry(counter.counterType ?? "", counter),
|
(counter) => MapEntry(counter.counterType ?? "", counter),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
_lastCounterLoadedAt = DateTime.now();
|
||||||
|
_counterRefreshQueued = false;
|
||||||
});
|
});
|
||||||
} catch (_) {}
|
} catch (_) {
|
||||||
|
} finally {
|
||||||
|
_isCounterLoading = false;
|
||||||
|
if (_counterReloadAfterCurrent && mounted) {
|
||||||
|
_counterReloadAfterCurrent = false;
|
||||||
|
unawaited(_loadCounter(force: true));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadVipStatus() async {
|
Future<void> _loadVipStatus() async {
|
||||||
@ -167,6 +301,21 @@ class _MePage2State extends State<MePage2> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _openCpActivity() {
|
||||||
|
final cpActivityUrl = _cpActivityUrl.trim();
|
||||||
|
if (cpActivityUrl.isEmpty) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint('[MePage2] cp activity url is empty');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SCNavigatorUtils.push(
|
||||||
|
context,
|
||||||
|
"${SCMainRoute.webViewPage}?url=${Uri.encodeComponent(cpActivityUrl)}&showTitle=false",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
String _appendToken(String url) {
|
String _appendToken(String url) {
|
||||||
final token = AccountStorage().getToken();
|
final token = AccountStorage().getToken();
|
||||||
if (token.isEmpty) return url;
|
if (token.isEmpty) return url;
|
||||||
@ -348,6 +497,14 @@ class _MePage2State extends State<MePage2> {
|
|||||||
iconPath: 'sc_images/index/sc_icon_bag.png',
|
iconPath: 'sc_images/index/sc_icon_bag.png',
|
||||||
onTap: () => SCNavigatorUtils.push(context, StoreRoute.bags),
|
onTap: () => SCNavigatorUtils.push(context, StoreRoute.bags),
|
||||||
),
|
),
|
||||||
|
SizedBox(width: 10.w),
|
||||||
|
_buildEntryItem(
|
||||||
|
title: 'cp',
|
||||||
|
iconPath: 'sc_images/index/sc_icon_cp_entry.png',
|
||||||
|
iconWidth: 47,
|
||||||
|
iconHeight: 50,
|
||||||
|
onTap: _openCpActivity,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
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:yumi/app_localizations.dart';
|
import 'package:yumi/app_localizations.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/text/sc_text.dart';
|
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:yumi/ui_kit/components/dialog/dialog_base.dart';
|
|
||||||
import 'package:yumi/ui_kit/components/sc_page_list.dart';
|
import 'package:yumi/ui_kit/components/sc_page_list.dart';
|
||||||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||||
@ -15,7 +13,6 @@ import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repositor
|
|||||||
import 'package:yumi/shared/business_logic/models/res/bags_list_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/bags_list_res.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
||||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/bag/props_bag_chatbox_detail_dialog.dart';
|
|
||||||
import 'package:yumi/ui_kit/widgets/countdown_timer.dart';
|
import 'package:yumi/ui_kit/widgets/countdown_timer.dart';
|
||||||
import 'package:yumi/modules/store/store_route.dart';
|
import 'package:yumi/modules/store/store_route.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
||||||
@ -155,52 +152,14 @@ class _BagsChatboxPageState
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (myChatbox?.id !=
|
final selected = selecteChatbox;
|
||||||
selecteChatbox?.propsResources?.id) {
|
if (selected == null) {
|
||||||
///佩戴
|
return;
|
||||||
SmartDialog.show(
|
|
||||||
tag: "showConfirmDialog",
|
|
||||||
alignment: Alignment.center,
|
|
||||||
debounce: true,
|
|
||||||
animationType: SmartAnimationType.fade,
|
|
||||||
builder: (_) {
|
|
||||||
return MsgDialog(
|
|
||||||
title: SCAppLocalizations.of(context)!.tips,
|
|
||||||
msg:
|
|
||||||
SCAppLocalizations.of(
|
|
||||||
context,
|
|
||||||
)!.confirmUseTips,
|
|
||||||
btnText:
|
|
||||||
SCAppLocalizations.of(context)!.confirm,
|
|
||||||
onEnsure: () {
|
|
||||||
_use(selecteChatbox!, false);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
///卸下
|
|
||||||
SmartDialog.show(
|
|
||||||
tag: "showConfirmDialog",
|
|
||||||
alignment: Alignment.center,
|
|
||||||
debounce: true,
|
|
||||||
animationType: SmartAnimationType.fade,
|
|
||||||
builder: (_) {
|
|
||||||
return MsgDialog(
|
|
||||||
title: SCAppLocalizations.of(context)!.tips,
|
|
||||||
msg:
|
|
||||||
SCAppLocalizations.of(
|
|
||||||
context,
|
|
||||||
)!.confirmUnUseTips,
|
|
||||||
btnText:
|
|
||||||
SCAppLocalizations.of(context)!.confirm,
|
|
||||||
onEnsure: () {
|
|
||||||
_use(selecteChatbox!, true);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
_use(
|
||||||
|
selected,
|
||||||
|
myChatbox?.id == selected.propsResources?.id,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -276,33 +235,18 @@ class _BagsChatboxPageState
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PositionedDirectional(
|
|
||||||
bottom: 5.w,
|
|
||||||
end: 5.w,
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
selecteChatbox = res;
|
|
||||||
setState(() {});
|
|
||||||
_showDetail(res);
|
|
||||||
},
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.all(5.w),
|
|
||||||
child: Image.asset(
|
|
||||||
"sc_images/store/sc_icon_shop_item_search.png",
|
|
||||||
width: 18.w,
|
|
||||||
height: 18.w,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
selecteChatbox = res;
|
selecteChatbox = res;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
if (myChatbox?.id != res.propsResources?.id) {
|
||||||
|
final success = await _use(res, false);
|
||||||
|
if (!success || !mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
showStoreBagFullScreenResourcePreview(
|
showStoreBagFullScreenResourcePreview(
|
||||||
context,
|
context,
|
||||||
resource: res.propsResources,
|
resource: res.propsResources,
|
||||||
@ -338,46 +282,42 @@ class _BagsChatboxPageState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showDetail(BagsListRes res) {
|
Future<bool> _use(BagsListRes res, bool unload) async {
|
||||||
SmartDialog.show(
|
|
||||||
tag: "showPropsDetail",
|
|
||||||
alignment: Alignment.center,
|
|
||||||
animationType: SmartAnimationType.fade,
|
|
||||||
builder: (_) {
|
|
||||||
return PropsBagChatboxDetailDialog(res);
|
|
||||||
},
|
|
||||||
onDismiss: () {
|
|
||||||
myChatbox = AccountStorage().getChatbox();
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _use(BagsListRes res, bool unload) {
|
|
||||||
SCLoadingManager.show(context: context);
|
SCLoadingManager.show(context: context);
|
||||||
SCStoreRepositoryImp()
|
final localizations = SCAppLocalizations.of(context)!;
|
||||||
.switchPropsUse(
|
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
||||||
SCPropsType.CHAT_BUBBLE.name,
|
context,
|
||||||
res.propsResources?.id ?? "",
|
listen: false,
|
||||||
unload,
|
);
|
||||||
)
|
try {
|
||||||
.then((value) async {
|
await SCStoreRepositoryImp().switchPropsUse(
|
||||||
setState(() {});
|
SCPropsType.CHAT_BUBBLE.name,
|
||||||
if (!unload) {
|
res.propsResources?.id ?? "",
|
||||||
myChatbox = res.propsResources;
|
unload,
|
||||||
SCTts.show(SCAppLocalizations.of(context)!.successfulWear);
|
);
|
||||||
} else {
|
try {
|
||||||
myChatbox = null;
|
await profileManager.fetchUserProfileData(loadGuardCount: false);
|
||||||
SCTts.show(SCAppLocalizations.of(context)!.successfullyUnloaded);
|
} catch (_) {}
|
||||||
}
|
if (!mounted) {
|
||||||
Provider.of<SocialChatUserProfileManager>(
|
return false;
|
||||||
context,
|
}
|
||||||
listen: false,
|
if (!unload) {
|
||||||
).fetchUserProfileData(loadGuardCount: false);
|
myChatbox = res.propsResources;
|
||||||
SCLoadingManager.hide();
|
SCTts.show(localizations.successfulWear);
|
||||||
})
|
} else {
|
||||||
.catchError((e) {
|
myChatbox = null;
|
||||||
SCLoadingManager.hide();
|
SCTts.show(localizations.successfullyUnloaded);
|
||||||
});
|
}
|
||||||
|
profileManager.syncCurrentUserChatBubble(
|
||||||
|
unload ? null : res.propsResources,
|
||||||
|
expireTime: res.expireTime?.toString(),
|
||||||
|
);
|
||||||
|
setState(() {});
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
SCLoadingManager.hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
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:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||||
import 'package:yumi/app_localizations.dart';
|
import 'package:yumi/app_localizations.dart';
|
||||||
@ -12,14 +11,12 @@ import 'package:yumi/shared/data_sources/models/enum/sc_props_type.dart';
|
|||||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart';
|
import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart';
|
||||||
import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
||||||
import 'package:yumi/ui_kit/components/dialog/dialog_base.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_page_list.dart';
|
import 'package:yumi/ui_kit/components/sc_page_list.dart';
|
||||||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||||
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/bag/props_bag_data_card_detail_dialog.dart';
|
|
||||||
import 'package:yumi/ui_kit/widgets/countdown_timer.dart';
|
import 'package:yumi/ui_kit/widgets/countdown_timer.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
||||||
|
|
||||||
@ -167,49 +164,11 @@ class BagsDataCardPageState
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!selectedInUse) {
|
final selected = selectedDataCard;
|
||||||
SmartDialog.show(
|
if (selected == null) {
|
||||||
tag: "showConfirmDialog",
|
|
||||||
alignment: Alignment.center,
|
|
||||||
debounce: true,
|
|
||||||
animationType: SmartAnimationType.fade,
|
|
||||||
builder: (_) {
|
|
||||||
return MsgDialog(
|
|
||||||
title: SCAppLocalizations.of(context)!.tips,
|
|
||||||
msg:
|
|
||||||
SCAppLocalizations.of(
|
|
||||||
context,
|
|
||||||
)!.confirmUseTips,
|
|
||||||
btnText:
|
|
||||||
SCAppLocalizations.of(context)!.confirm,
|
|
||||||
onEnsure: () {
|
|
||||||
_use(selectedDataCard!, false);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SmartDialog.show(
|
_use(selected, selectedInUse);
|
||||||
tag: "showConfirmDialog",
|
|
||||||
alignment: Alignment.center,
|
|
||||||
debounce: true,
|
|
||||||
animationType: SmartAnimationType.fade,
|
|
||||||
builder: (_) {
|
|
||||||
return MsgDialog(
|
|
||||||
title: SCAppLocalizations.of(context)!.tips,
|
|
||||||
msg:
|
|
||||||
SCAppLocalizations.of(
|
|
||||||
context,
|
|
||||||
)!.confirmUnUseTips,
|
|
||||||
btnText:
|
|
||||||
SCAppLocalizations.of(context)!.confirm,
|
|
||||||
onEnsure: () {
|
|
||||||
_use(selectedDataCard!, true);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -288,33 +247,29 @@ class BagsDataCardPageState
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PositionedDirectional(
|
|
||||||
bottom: 5.w,
|
|
||||||
end: 5.w,
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
selectedDataCard = res;
|
|
||||||
setState(() {});
|
|
||||||
_showDetail(res);
|
|
||||||
},
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.all(5.w),
|
|
||||||
child: Image.asset(
|
|
||||||
"sc_images/store/sc_icon_shop_item_search.png",
|
|
||||||
width: 18.w,
|
|
||||||
height: 18.w,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
selectedDataCard = res;
|
selectedDataCard = res;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
final expired =
|
||||||
|
(res.expireTime ?? 0) < DateTime.now().millisecondsSinceEpoch;
|
||||||
|
if (expired) {
|
||||||
|
showStoreBagFullScreenResourcePreview(
|
||||||
|
context,
|
||||||
|
resource: res.propsResources,
|
||||||
|
previewKind: SCStoreItemPreviewKind.dataCard,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (myDataCard?.id != res.propsResources?.id) {
|
||||||
|
final success = await _use(res, false);
|
||||||
|
if (!success || !mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
showStoreBagFullScreenResourcePreview(
|
showStoreBagFullScreenResourcePreview(
|
||||||
context,
|
context,
|
||||||
resource: res.propsResources,
|
resource: res.propsResources,
|
||||||
@ -348,52 +303,36 @@ class BagsDataCardPageState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showDetail(BagsListRes res) {
|
Future<bool> _use(BagsListRes res, bool unload) async {
|
||||||
SmartDialog.show(
|
|
||||||
tag: "showPropsDetail",
|
|
||||||
alignment: Alignment.center,
|
|
||||||
animationType: SmartAnimationType.fade,
|
|
||||||
builder: (_) {
|
|
||||||
return PropsBagDataCardDetailDialog(res);
|
|
||||||
},
|
|
||||||
onDismiss: () {
|
|
||||||
myDataCard = AccountStorage().getDataCard();
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _use(BagsListRes res, bool unload) {
|
|
||||||
SCLoadingManager.show(context: context);
|
SCLoadingManager.show(context: context);
|
||||||
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
||||||
context,
|
context,
|
||||||
listen: false,
|
listen: false,
|
||||||
);
|
);
|
||||||
final localizations = SCAppLocalizations.of(context)!;
|
final localizations = SCAppLocalizations.of(context)!;
|
||||||
SCStoreRepositoryImp()
|
try {
|
||||||
.switchPropsUse(
|
await SCStoreRepositoryImp().switchPropsUse(
|
||||||
SCPropsType.DATA_CARD.name,
|
SCPropsType.DATA_CARD.name,
|
||||||
res.propsResources?.id ?? "",
|
res.propsResources?.id ?? "",
|
||||||
unload,
|
unload,
|
||||||
)
|
);
|
||||||
.then((value) async {
|
if (!mounted) {
|
||||||
if (!mounted) {
|
return false;
|
||||||
SCLoadingManager.hide();
|
}
|
||||||
return;
|
if (!unload) {
|
||||||
}
|
myDataCard = res.propsResources;
|
||||||
setState(() {});
|
SCTts.show(localizations.successfulWear);
|
||||||
if (!unload) {
|
} else {
|
||||||
myDataCard = res.propsResources;
|
myDataCard = null;
|
||||||
SCTts.show(localizations.successfulWear);
|
SCTts.show(localizations.successfullyUnloaded);
|
||||||
} else {
|
}
|
||||||
myDataCard = null;
|
setState(() {});
|
||||||
SCTts.show(localizations.successfullyUnloaded);
|
profileManager.fetchUserProfileData(loadGuardCount: false);
|
||||||
}
|
return true;
|
||||||
profileManager.fetchUserProfileData(loadGuardCount: false);
|
} catch (_) {
|
||||||
SCLoadingManager.hide();
|
return false;
|
||||||
})
|
} finally {
|
||||||
.catchError((e) {
|
SCLoadingManager.hide();
|
||||||
SCLoadingManager.hide();
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
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:yumi/app_localizations.dart';
|
import 'package:yumi/app_localizations.dart';
|
||||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||||
import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
||||||
@ -11,13 +10,11 @@ import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
|||||||
import 'package:yumi/modules/store/store_route.dart';
|
import 'package:yumi/modules/store/store_route.dart';
|
||||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
import 'package:yumi/services/auth/user_profile_manager.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/dialog/dialog_base.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_page_list.dart';
|
import 'package:yumi/ui_kit/components/sc_page_list.dart';
|
||||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||||
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/bag/props_bag_headdress_detail_dialog.dart';
|
|
||||||
import 'package:yumi/ui_kit/widgets/countdown_timer.dart';
|
import 'package:yumi/ui_kit/widgets/countdown_timer.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
@ -155,52 +152,14 @@ class _BagsHeaddressPageState
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (myHeaddress?.id !=
|
final selected = selecteHeaddress;
|
||||||
selecteHeaddress?.propsResources?.id) {
|
if (selected == null) {
|
||||||
///佩戴
|
return;
|
||||||
SmartDialog.show(
|
|
||||||
tag: "showConfirmDialog",
|
|
||||||
alignment: Alignment.center,
|
|
||||||
debounce: true,
|
|
||||||
animationType: SmartAnimationType.fade,
|
|
||||||
builder: (_) {
|
|
||||||
return MsgDialog(
|
|
||||||
title: SCAppLocalizations.of(context)!.tips,
|
|
||||||
msg:
|
|
||||||
SCAppLocalizations.of(
|
|
||||||
context,
|
|
||||||
)!.confirmUseTips,
|
|
||||||
btnText:
|
|
||||||
SCAppLocalizations.of(context)!.confirm,
|
|
||||||
onEnsure: () {
|
|
||||||
_use(selecteHeaddress!, false);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
///卸下
|
|
||||||
SmartDialog.show(
|
|
||||||
tag: "showConfirmDialog",
|
|
||||||
alignment: Alignment.center,
|
|
||||||
debounce: true,
|
|
||||||
animationType: SmartAnimationType.fade,
|
|
||||||
builder: (_) {
|
|
||||||
return MsgDialog(
|
|
||||||
title: SCAppLocalizations.of(context)!.tips,
|
|
||||||
msg:
|
|
||||||
SCAppLocalizations.of(
|
|
||||||
context,
|
|
||||||
)!.confirmUnUseTips,
|
|
||||||
btnText:
|
|
||||||
SCAppLocalizations.of(context)!.confirm,
|
|
||||||
onEnsure: () {
|
|
||||||
_use(selecteHeaddress!, true);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
_use(
|
||||||
|
selected,
|
||||||
|
myHeaddress?.id == selected.propsResources?.id,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -276,33 +235,18 @@ class _BagsHeaddressPageState
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PositionedDirectional(
|
|
||||||
bottom: 5.w,
|
|
||||||
end: 5.w,
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
selecteHeaddress = res;
|
|
||||||
setState(() {});
|
|
||||||
_showDetail(res);
|
|
||||||
},
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.all(5.w),
|
|
||||||
child: Image.asset(
|
|
||||||
"sc_images/store/sc_icon_shop_item_search.png",
|
|
||||||
width: 13.w,
|
|
||||||
height: 13.w,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
selecteHeaddress = res;
|
selecteHeaddress = res;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
if (myHeaddress?.id != res.propsResources?.id) {
|
||||||
|
final success = await _use(res, false);
|
||||||
|
if (!success || !mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
showStoreBagFullScreenResourcePreview(
|
showStoreBagFullScreenResourcePreview(
|
||||||
context,
|
context,
|
||||||
resource: res.propsResources,
|
resource: res.propsResources,
|
||||||
@ -339,46 +283,36 @@ class _BagsHeaddressPageState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showDetail(BagsListRes res) {
|
Future<bool> _use(BagsListRes res, bool unload) async {
|
||||||
SmartDialog.show(
|
|
||||||
tag: "showPropsDetail",
|
|
||||||
alignment: Alignment.center,
|
|
||||||
animationType: SmartAnimationType.fade,
|
|
||||||
builder: (_) {
|
|
||||||
return PropsBagHeaddressDetailDialog(res);
|
|
||||||
},
|
|
||||||
onDismiss: () {
|
|
||||||
myHeaddress = AccountStorage().getHeaddress();
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _use(BagsListRes res, bool unload) {
|
|
||||||
SCLoadingManager.show(context: context);
|
SCLoadingManager.show(context: context);
|
||||||
SCStoreRepositoryImp()
|
final localizations = SCAppLocalizations.of(context)!;
|
||||||
.switchPropsUse(
|
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
||||||
SCPropsType.AVATAR_FRAME.name,
|
context,
|
||||||
res.propsResources?.id ?? "",
|
listen: false,
|
||||||
unload,
|
);
|
||||||
)
|
try {
|
||||||
.then((value) async {
|
await SCStoreRepositoryImp().switchPropsUse(
|
||||||
setState(() {});
|
SCPropsType.AVATAR_FRAME.name,
|
||||||
if (!unload) {
|
res.propsResources?.id ?? "",
|
||||||
myHeaddress = res.propsResources;
|
unload,
|
||||||
SCTts.show(SCAppLocalizations.of(context)!.successfulWear);
|
);
|
||||||
} else {
|
if (!mounted) {
|
||||||
myHeaddress = null;
|
return false;
|
||||||
SCTts.show(SCAppLocalizations.of(context)!.successfullyUnloaded);
|
}
|
||||||
}
|
if (!unload) {
|
||||||
Provider.of<SocialChatUserProfileManager>(
|
myHeaddress = res.propsResources;
|
||||||
context,
|
SCTts.show(localizations.successfulWear);
|
||||||
listen: false,
|
} else {
|
||||||
).fetchUserProfileData(loadGuardCount: false);
|
myHeaddress = null;
|
||||||
SCLoadingManager.hide();
|
SCTts.show(localizations.successfullyUnloaded);
|
||||||
})
|
}
|
||||||
.catchError((e) {
|
setState(() {});
|
||||||
SCLoadingManager.hide();
|
profileManager.fetchUserProfileData(loadGuardCount: false);
|
||||||
});
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
SCLoadingManager.hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
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:yumi/ui_kit/widgets/bag/props_bag_mountains_detail_dialog.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:yumi/app_localizations.dart';
|
import 'package:yumi/app_localizations.dart';
|
||||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||||
@ -21,7 +19,6 @@ import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
|||||||
|
|
||||||
import '../../../../shared/data_sources/models/enum/sc_props_type.dart';
|
import '../../../../shared/data_sources/models/enum/sc_props_type.dart';
|
||||||
import '../../../../ui_kit/components/sc_debounce_widget.dart';
|
import '../../../../ui_kit/components/sc_debounce_widget.dart';
|
||||||
import '../../../../ui_kit/components/dialog/dialog_base.dart';
|
|
||||||
import '../../../../ui_kit/theme/socialchat_theme.dart';
|
import '../../../../ui_kit/theme/socialchat_theme.dart';
|
||||||
|
|
||||||
///坐骑
|
///坐骑
|
||||||
@ -161,52 +158,14 @@ class _BagsMountainsPageState
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (myMountains?.id !=
|
final selected = selecteMountains;
|
||||||
selecteMountains?.propsResources?.id) {
|
if (selected == null) {
|
||||||
///佩戴
|
return;
|
||||||
SmartDialog.show(
|
|
||||||
tag: "showConfirmDialog",
|
|
||||||
alignment: Alignment.center,
|
|
||||||
debounce: true,
|
|
||||||
animationType: SmartAnimationType.fade,
|
|
||||||
builder: (_) {
|
|
||||||
return MsgDialog(
|
|
||||||
title: SCAppLocalizations.of(context)!.tips,
|
|
||||||
msg:
|
|
||||||
SCAppLocalizations.of(
|
|
||||||
context,
|
|
||||||
)!.confirmUseTips,
|
|
||||||
btnText:
|
|
||||||
SCAppLocalizations.of(context)!.confirm,
|
|
||||||
onEnsure: () {
|
|
||||||
_use(selecteMountains!, false);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
///卸下
|
|
||||||
SmartDialog.show(
|
|
||||||
tag: "showConfirmDialog",
|
|
||||||
alignment: Alignment.center,
|
|
||||||
debounce: true,
|
|
||||||
animationType: SmartAnimationType.fade,
|
|
||||||
builder: (_) {
|
|
||||||
return MsgDialog(
|
|
||||||
title: SCAppLocalizations.of(context)!.tips,
|
|
||||||
msg:
|
|
||||||
SCAppLocalizations.of(
|
|
||||||
context,
|
|
||||||
)!.confirmUnUseTips,
|
|
||||||
btnText:
|
|
||||||
SCAppLocalizations.of(context)!.confirm,
|
|
||||||
onEnsure: () {
|
|
||||||
_use(selecteMountains!, true);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
_use(
|
||||||
|
selected,
|
||||||
|
myMountains?.id == selected.propsResources?.id,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -285,9 +244,15 @@ class _BagsMountainsPageState
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
selecteMountains = res;
|
selecteMountains = res;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
if (myMountains?.id != res.propsResources?.id) {
|
||||||
|
final success = await _use(res, false);
|
||||||
|
if (!success || !mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
showStoreBagFullScreenResourcePreview(
|
showStoreBagFullScreenResourcePreview(
|
||||||
context,
|
context,
|
||||||
resource: res.propsResources,
|
resource: res.propsResources,
|
||||||
@ -323,46 +288,36 @@ class _BagsMountainsPageState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showDetail(BagsListRes res) {
|
Future<bool> _use(BagsListRes res, bool unload) async {
|
||||||
SmartDialog.show(
|
|
||||||
tag: "showPropsDetail",
|
|
||||||
alignment: Alignment.center,
|
|
||||||
animationType: SmartAnimationType.fade,
|
|
||||||
builder: (_) {
|
|
||||||
return PropsBagMountainsDetailDialog(res);
|
|
||||||
},
|
|
||||||
onDismiss: () {
|
|
||||||
myMountains = AccountStorage().getMountains();
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _use(BagsListRes res, bool unload) {
|
|
||||||
SCLoadingManager.show(context: context);
|
SCLoadingManager.show(context: context);
|
||||||
SCStoreRepositoryImp()
|
final localizations = SCAppLocalizations.of(context)!;
|
||||||
.switchPropsUse(
|
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
||||||
SCPropsType.RIDE.name,
|
context,
|
||||||
res.propsResources?.id ?? "",
|
listen: false,
|
||||||
unload,
|
);
|
||||||
)
|
try {
|
||||||
.then((value) async {
|
await SCStoreRepositoryImp().switchPropsUse(
|
||||||
setState(() {});
|
SCPropsType.RIDE.name,
|
||||||
if (!unload) {
|
res.propsResources?.id ?? "",
|
||||||
myMountains = res.propsResources;
|
unload,
|
||||||
SCTts.show(SCAppLocalizations.of(context)!.successfulWear);
|
);
|
||||||
} else {
|
if (!mounted) {
|
||||||
myMountains = null;
|
return false;
|
||||||
SCTts.show(SCAppLocalizations.of(context)!.successfullyUnloaded);
|
}
|
||||||
}
|
if (!unload) {
|
||||||
Provider.of<SocialChatUserProfileManager>(
|
myMountains = res.propsResources;
|
||||||
context,
|
SCTts.show(localizations.successfulWear);
|
||||||
listen: false,
|
} else {
|
||||||
).fetchUserProfileData(loadGuardCount: false);
|
myMountains = null;
|
||||||
SCLoadingManager.hide();
|
SCTts.show(localizations.successfullyUnloaded);
|
||||||
})
|
}
|
||||||
.catchError((e) {
|
setState(() {});
|
||||||
SCLoadingManager.hide();
|
profileManager.fetchUserProfileData(loadGuardCount: false);
|
||||||
});
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
SCLoadingManager.hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
|||||||
import 'package:yumi/ui_kit/components/sc_page_list.dart';
|
import 'package:yumi/ui_kit/components/sc_page_list.dart';
|
||||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart';
|
import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/bag/props_bag_theme_detail_dialog.dart';
|
|
||||||
import 'package:yumi/ui_kit/widgets/countdown_timer.dart';
|
import 'package:yumi/ui_kit/widgets/countdown_timer.dart';
|
||||||
import 'package:yumi/modules/store/store_route.dart';
|
import 'package:yumi/modules/store/store_route.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
@ -20,7 +19,6 @@ import '../../../../shared/business_logic/models/res/sc_room_theme_list_res.dart
|
|||||||
import '../../../../services/audio/rtc_manager.dart';
|
import '../../../../services/audio/rtc_manager.dart';
|
||||||
import '../../../../services/audio/rtm_manager.dart';
|
import '../../../../services/audio/rtm_manager.dart';
|
||||||
import '../../../../ui_kit/components/sc_debounce_widget.dart';
|
import '../../../../ui_kit/components/sc_debounce_widget.dart';
|
||||||
import '../../../../ui_kit/components/dialog/dialog_base.dart';
|
|
||||||
import '../../../../ui_kit/components/sc_tts.dart';
|
import '../../../../ui_kit/components/sc_tts.dart';
|
||||||
import '../../../../ui_kit/widgets/room/room_msg_item.dart';
|
import '../../../../ui_kit/widgets/room/room_msg_item.dart';
|
||||||
|
|
||||||
@ -149,51 +147,11 @@ class _BagsThemePageState
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (!(selecteTheme?.useTheme ?? false)) {
|
final selected = selecteTheme;
|
||||||
///佩戴
|
if (selected == null) {
|
||||||
SmartDialog.show(
|
return;
|
||||||
tag: "showConfirmDialog",
|
|
||||||
alignment: Alignment.center,
|
|
||||||
debounce: true,
|
|
||||||
animationType: SmartAnimationType.fade,
|
|
||||||
builder: (_) {
|
|
||||||
return MsgDialog(
|
|
||||||
title: SCAppLocalizations.of(context)!.tips,
|
|
||||||
msg:
|
|
||||||
SCAppLocalizations.of(
|
|
||||||
context,
|
|
||||||
)!.confirmUseTips,
|
|
||||||
btnText:
|
|
||||||
SCAppLocalizations.of(context)!.confirm,
|
|
||||||
onEnsure: () {
|
|
||||||
_use(selecteTheme!, false);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
///卸下
|
|
||||||
SmartDialog.show(
|
|
||||||
tag: "showConfirmDialog",
|
|
||||||
alignment: Alignment.center,
|
|
||||||
debounce: true,
|
|
||||||
animationType: SmartAnimationType.fade,
|
|
||||||
builder: (_) {
|
|
||||||
return MsgDialog(
|
|
||||||
title: SCAppLocalizations.of(context)!.tips,
|
|
||||||
msg:
|
|
||||||
SCAppLocalizations.of(
|
|
||||||
context,
|
|
||||||
)!.confirmUnUseTips,
|
|
||||||
btnText:
|
|
||||||
SCAppLocalizations.of(context)!.confirm,
|
|
||||||
onEnsure: () {
|
|
||||||
_use(selecteTheme!, true);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
_use(selected, selected.useTheme ?? false);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -272,9 +230,15 @@ class _BagsThemePageState
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
selecteTheme = res;
|
selecteTheme = res;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
if (!(res.useTheme ?? false)) {
|
||||||
|
final success = await _use(res, false);
|
||||||
|
if (!success || !mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
showStoreBagFullScreenResourcePreview(
|
showStoreBagFullScreenResourcePreview(
|
||||||
context,
|
context,
|
||||||
sourceUrl: res.themeBack,
|
sourceUrl: res.themeBack,
|
||||||
@ -308,62 +272,46 @@ class _BagsThemePageState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _use(SCRoomThemeListRes res, bool unload) {
|
Future<bool> _use(SCRoomThemeListRes res, bool unload) async {
|
||||||
SCLoadingManager.show();
|
SCLoadingManager.show();
|
||||||
SCStoreRepositoryImp()
|
final localizations = SCAppLocalizations.of(context)!;
|
||||||
.themeSwitchUse(res.id ?? "", unload)
|
final rtcManager = Provider.of<RealTimeCommunicationManager>(
|
||||||
.then((value) async {
|
context,
|
||||||
SCLoadingManager.hide();
|
listen: false,
|
||||||
SmartDialog.dismiss(tag: "showPropsDetail");
|
|
||||||
if (!unload) {
|
|
||||||
SCTts.show(SCAppLocalizations.of(context)!.successfulWear);
|
|
||||||
Provider.of<RealTimeCommunicationManager>(
|
|
||||||
context!,
|
|
||||||
listen: false,
|
|
||||||
).updateRoomBG(res);
|
|
||||||
} else {
|
|
||||||
Provider.of<RealTimeCommunicationManager>(
|
|
||||||
context!,
|
|
||||||
listen: false,
|
|
||||||
).updateRoomBG(SCRoomThemeListRes());
|
|
||||||
selecteTheme = null;
|
|
||||||
SCTts.show(SCAppLocalizations.of(context)!.successfullyUnloaded);
|
|
||||||
}
|
|
||||||
loadData(1);
|
|
||||||
|
|
||||||
///发送一条消息更新远端房间信息
|
|
||||||
String? groupId =
|
|
||||||
Provider.of<RealTimeCommunicationManager>(
|
|
||||||
context,
|
|
||||||
listen: false,
|
|
||||||
).currenRoom?.roomProfile?.roomProfile?.roomAccount;
|
|
||||||
if (groupId != null) {
|
|
||||||
Msg msg = Msg(
|
|
||||||
groupId: groupId,
|
|
||||||
msg: unload ? "" : jsonEncode(res.toJson()),
|
|
||||||
type: SCRoomMsgType.roomBGUpdate,
|
|
||||||
);
|
|
||||||
Provider.of<RtmProvider>(
|
|
||||||
context!,
|
|
||||||
listen: false,
|
|
||||||
).dispatchMessage(msg, addLocal: false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catchError((e) {
|
|
||||||
SCLoadingManager.hide();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showDetail(SCRoomThemeListRes res) {
|
|
||||||
SmartDialog.show(
|
|
||||||
tag: "showPropsDetail",
|
|
||||||
alignment: Alignment.center,
|
|
||||||
animationType: SmartAnimationType.fade,
|
|
||||||
builder: (_) {
|
|
||||||
return PropsBagThemeDetailDialog(res, () {
|
|
||||||
loadData(1);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
final rtmProvider = Provider.of<RtmProvider>(context, listen: false);
|
||||||
|
try {
|
||||||
|
await SCStoreRepositoryImp().themeSwitchUse(res.id ?? "", unload);
|
||||||
|
if (!mounted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SmartDialog.dismiss(tag: "showPropsDetail");
|
||||||
|
if (!unload) {
|
||||||
|
SCTts.show(localizations.successfulWear);
|
||||||
|
rtcManager.updateRoomBG(res);
|
||||||
|
} else {
|
||||||
|
rtcManager.updateRoomBG(SCRoomThemeListRes());
|
||||||
|
selecteTheme = null;
|
||||||
|
SCTts.show(localizations.successfullyUnloaded);
|
||||||
|
}
|
||||||
|
loadData(1);
|
||||||
|
|
||||||
|
///发送一条消息更新远端房间信息
|
||||||
|
String? groupId =
|
||||||
|
rtcManager.currenRoom?.roomProfile?.roomProfile?.roomAccount;
|
||||||
|
if (groupId != null) {
|
||||||
|
Msg msg = Msg(
|
||||||
|
groupId: groupId,
|
||||||
|
msg: unload ? "" : jsonEncode(res.toJson()),
|
||||||
|
type: SCRoomMsgType.roomBGUpdate,
|
||||||
|
);
|
||||||
|
rtmProvider.dispatchMessage(msg, addLocal: false);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
SCLoadingManager.hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
|||||||
import 'package:yumi/ui_kit/components/sc_debounce_widget.dart';
|
import 'package:yumi/ui_kit/components/sc_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';
|
import 'package:yumi/ui_kit/widgets/props/sc_data_card_resource_view.dart';
|
||||||
|
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
||||||
|
|
||||||
class VipDetailPage extends StatefulWidget {
|
class VipDetailPage extends StatefulWidget {
|
||||||
const VipDetailPage({super.key});
|
const VipDetailPage({super.key});
|
||||||
@ -470,8 +471,7 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
|
|
||||||
Widget _buildLevelCard() {
|
Widget _buildLevelCard() {
|
||||||
final assetLevel = _assetLevel;
|
final assetLevel = _assetLevel;
|
||||||
final backgroundCard = _selectedBackgroundCard;
|
final effectImage = _selectedEffectImage;
|
||||||
final effectImage = _selectedEffectImage ?? _selectedLongBadge;
|
|
||||||
return Center(
|
return Center(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 351.w,
|
width: 351.w,
|
||||||
@ -482,10 +482,7 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(8.w),
|
borderRadius: BorderRadius.circular(8.w),
|
||||||
child: _buildVipCardBackground(
|
child: _buildVipCardBackground(assetLevel: assetLevel),
|
||||||
resource: backgroundCard,
|
|
||||||
assetLevel: assetLevel,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
@ -525,41 +522,30 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PositionedDirectional(
|
if (_hasResourceUrl(effectImage))
|
||||||
end: 24.w,
|
PositionedDirectional(
|
||||||
top: -36.w,
|
end: 24.w,
|
||||||
child: _buildDynamicResourceVisual(
|
top: -36.w,
|
||||||
resource: effectImage,
|
child: _buildDynamicResourceVisual(
|
||||||
width: 135.w,
|
resource: effectImage,
|
||||||
height: 135.w,
|
width: 135.w,
|
||||||
fallbackIcon: Icons.workspace_premium_outlined,
|
height: 135.w,
|
||||||
|
fallbackIcon: Icons.workspace_premium_outlined,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildVipCardBackground({
|
Widget _buildVipCardBackground({required int assetLevel}) {
|
||||||
required SCVipResourceRes? resource,
|
return Image.asset(
|
||||||
required int assetLevel,
|
|
||||||
}) {
|
|
||||||
final fallback = Image.asset(
|
|
||||||
'sc_images/vip/sc_vip_card_bg_$assetLevel.png',
|
'sc_images/vip/sc_vip_card_bg_$assetLevel.png',
|
||||||
fit: BoxFit.fill,
|
fit: BoxFit.fill,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 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() {
|
||||||
@ -632,19 +618,27 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDecorationGrid(List<_VipFeatureData> items) {
|
Widget _buildDecorationGrid(List<_VipFeatureData> items) {
|
||||||
return Wrap(
|
return LayoutBuilder(
|
||||||
spacing: 10.w,
|
builder: (context, constraints) {
|
||||||
runSpacing: 10.w,
|
final spacing = 10.w;
|
||||||
children:
|
final maxWidth =
|
||||||
items
|
constraints.maxWidth.isFinite ? constraints.maxWidth : 314.w;
|
||||||
.map(
|
final itemWidth = (maxWidth - spacing * 2) / 3;
|
||||||
(item) => _buildFeatureTile(
|
return Wrap(
|
||||||
item,
|
spacing: spacing,
|
||||||
width: item.wide ? 206.w : 98.w,
|
runSpacing: spacing,
|
||||||
height: 100.w,
|
children:
|
||||||
),
|
items
|
||||||
)
|
.map(
|
||||||
.toList(),
|
(item) => _buildFeatureTile(
|
||||||
|
item,
|
||||||
|
width: itemWidth,
|
||||||
|
height: 100.w,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -772,6 +766,7 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
resource: _selectedAvatarFrame,
|
resource: _selectedAvatarFrame,
|
||||||
fallbackTitle: 'Avatar Frame',
|
fallbackTitle: 'Avatar Frame',
|
||||||
fallbackIcon: Icons.person_pin_circle_outlined,
|
fallbackIcon: Icons.person_pin_circle_outlined,
|
||||||
|
previewKind: SCStoreItemPreviewKind.avatarFrame,
|
||||||
);
|
);
|
||||||
_addResourceItem(
|
_addResourceItem(
|
||||||
items,
|
items,
|
||||||
@ -790,14 +785,14 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
resource: _selectedEntryEffect,
|
resource: _selectedEntryEffect,
|
||||||
fallbackTitle: 'Entry Effect',
|
fallbackTitle: 'Entry Effect',
|
||||||
fallbackIcon: Icons.airline_seat_recline_extra,
|
fallbackIcon: Icons.airline_seat_recline_extra,
|
||||||
wide: true,
|
|
||||||
);
|
);
|
||||||
_addResourceItem(
|
_addResourceItem(
|
||||||
items,
|
items,
|
||||||
resource: _selectedBackgroundCard,
|
resource: _selectedBackgroundCard,
|
||||||
fallbackTitle: 'Profile Card',
|
fallbackTitle: 'Profile Card',
|
||||||
fallbackIcon: Icons.badge_outlined,
|
fallbackIcon: Icons.badge_outlined,
|
||||||
wide: true,
|
previewKind: SCStoreItemPreviewKind.dataCard,
|
||||||
|
previewFit: BoxFit.cover,
|
||||||
);
|
);
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
@ -815,6 +810,9 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
required String fallbackTitle,
|
required String fallbackTitle,
|
||||||
IconData? fallbackIcon,
|
IconData? fallbackIcon,
|
||||||
bool wide = false,
|
bool wide = false,
|
||||||
|
SCStoreItemPreviewKind previewKind = SCStoreItemPreviewKind.resource,
|
||||||
|
BoxFit previewFit = BoxFit.contain,
|
||||||
|
BoxFit tileFit = BoxFit.contain,
|
||||||
}) {
|
}) {
|
||||||
if (!_hasResource(resource)) {
|
if (!_hasResource(resource)) {
|
||||||
return;
|
return;
|
||||||
@ -824,8 +822,12 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
_resourceTitle(resource, fallbackTitle),
|
_resourceTitle(resource, fallbackTitle),
|
||||||
null,
|
null,
|
||||||
icon: fallbackIcon,
|
icon: fallbackIcon,
|
||||||
resourceUrl: resource?.previewUrl,
|
sourceUrl: resource?.sourceResourceUrl,
|
||||||
|
coverUrl: _resourceCoverUrl(resource),
|
||||||
wide: wide,
|
wide: wide,
|
||||||
|
previewKind: previewKind,
|
||||||
|
previewFit: previewFit,
|
||||||
|
tileFit: tileFit,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -846,6 +848,18 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
return fallbackTitle;
|
return fallbackTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? _resourceCoverUrl(SCVipResourceRes? resource) {
|
||||||
|
final coverUrl = resource?.coverUrl?.trim();
|
||||||
|
if (coverUrl != null && coverUrl.isNotEmpty) {
|
||||||
|
return coverUrl;
|
||||||
|
}
|
||||||
|
final cover = resource?.cover?.trim();
|
||||||
|
if (cover != null && cover.isNotEmpty) {
|
||||||
|
return cover;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildDynamicResourceVisual({
|
Widget _buildDynamicResourceVisual({
|
||||||
required SCVipResourceRes? resource,
|
required SCVipResourceRes? resource,
|
||||||
required double width,
|
required double width,
|
||||||
@ -946,7 +960,7 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFeatureIcon(_VipFeatureData item, {required double size}) {
|
Widget _buildFeatureIcon(_VipFeatureData item, {required double size}) {
|
||||||
final url = item.resourceUrl?.trim();
|
final url = item.coverUrl?.trim();
|
||||||
if (url != null && url.isNotEmpty) {
|
if (url != null && url.isNotEmpty) {
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(6.w),
|
borderRadius: BorderRadius.circular(6.w),
|
||||||
@ -973,15 +987,15 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFeatureVisual(_VipFeatureData item, {required bool isWide}) {
|
Widget _buildFeatureVisual(_VipFeatureData item, {required bool isWide}) {
|
||||||
final url = item.resourceUrl?.trim();
|
final coverUrl = item.coverUrl?.trim();
|
||||||
if (url != null && url.isNotEmpty) {
|
if (coverUrl != null && coverUrl.isNotEmpty) {
|
||||||
return netImage(
|
return netImage(
|
||||||
url: url,
|
url: coverUrl,
|
||||||
fit: BoxFit.contain,
|
fit: item.tileFit,
|
||||||
noDefaultImg: true,
|
noDefaultImg: true,
|
||||||
errorWidget:
|
errorWidget:
|
||||||
item.assetPath != null
|
item.assetPath != null
|
||||||
? Image.asset(item.assetPath!, fit: BoxFit.contain)
|
? Image.asset(item.assetPath!, fit: item.tileFit)
|
||||||
: isWide
|
: isWide
|
||||||
? Center(child: _buildWideRewardPlaceholder())
|
? Center(child: _buildWideRewardPlaceholder())
|
||||||
: _buildRewardPlaceholder(item.icon ?? Icons.auto_awesome),
|
: _buildRewardPlaceholder(item.icon ?? Icons.auto_awesome),
|
||||||
@ -1062,32 +1076,32 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
|||||||
required double height,
|
required double height,
|
||||||
}) {
|
}) {
|
||||||
final isWide = item.wide;
|
final isWide = item.wide;
|
||||||
return Container(
|
return GestureDetector(
|
||||||
width: width,
|
behavior: HitTestBehavior.opaque,
|
||||||
height: height,
|
onTap:
|
||||||
decoration: BoxDecoration(
|
item.canPreview
|
||||||
borderRadius: BorderRadius.circular(6.w),
|
? () => showStoreBagFullScreenResourcePreview(
|
||||||
border: Border.all(color: const Color(0xFFEBCB76), width: 1.w),
|
context,
|
||||||
color: Colors.black.withValues(alpha: 0.38),
|
sourceUrl: item.sourceUrl,
|
||||||
),
|
coverUrl: item.coverUrl,
|
||||||
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 8.w),
|
previewKind: item.previewKind,
|
||||||
child: Column(
|
fit: item.previewFit,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
)
|
||||||
children: [
|
: null,
|
||||||
Expanded(child: _buildFeatureVisual(item, isWide: isWide)),
|
child: Semantics(
|
||||||
SizedBox(height: 5.w),
|
label: item.title,
|
||||||
Text(
|
button: item.canPreview,
|
||||||
item.title,
|
child: Container(
|
||||||
maxLines: 2,
|
width: width,
|
||||||
textAlign: TextAlign.center,
|
height: height,
|
||||||
overflow: TextOverflow.ellipsis,
|
decoration: BoxDecoration(
|
||||||
style: TextStyle(
|
borderRadius: BorderRadius.circular(6.w),
|
||||||
color: Colors.white,
|
border: Border.all(color: const Color(0xFFEBCB76), width: 1.w),
|
||||||
fontSize: 12.sp,
|
color: Colors.black.withValues(alpha: 0.38),
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 8.w),
|
||||||
|
child: Center(child: _buildFeatureVisual(item, isWide: isWide)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1420,15 +1434,27 @@ class _VipFeatureData {
|
|||||||
this.title,
|
this.title,
|
||||||
this.assetPath, {
|
this.assetPath, {
|
||||||
this.icon,
|
this.icon,
|
||||||
this.resourceUrl,
|
this.sourceUrl,
|
||||||
|
this.coverUrl,
|
||||||
this.wide = false,
|
this.wide = false,
|
||||||
|
this.previewKind = SCStoreItemPreviewKind.resource,
|
||||||
|
this.previewFit = BoxFit.contain,
|
||||||
|
this.tileFit = BoxFit.contain,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final String? assetPath;
|
final String? assetPath;
|
||||||
final IconData? icon;
|
final IconData? icon;
|
||||||
final String? resourceUrl;
|
final String? sourceUrl;
|
||||||
|
final String? coverUrl;
|
||||||
final bool wide;
|
final bool wide;
|
||||||
|
final SCStoreItemPreviewKind previewKind;
|
||||||
|
final BoxFit previewFit;
|
||||||
|
final BoxFit tileFit;
|
||||||
|
|
||||||
|
bool get canPreview =>
|
||||||
|
(sourceUrl?.trim().isNotEmpty ?? false) ||
|
||||||
|
(coverUrl?.trim().isNotEmpty ?? false);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _VipActionButtonConfig {
|
class _VipActionButtonConfig {
|
||||||
|
|||||||
@ -37,11 +37,6 @@ class _RechargePageState extends State<RechargePage> {
|
|||||||
context,
|
context,
|
||||||
listen: false,
|
listen: false,
|
||||||
).initializePaymentProcessor(context);
|
).initializePaymentProcessor(context);
|
||||||
} else if (Platform.isIOS) {
|
|
||||||
Provider.of<IOSPaymentProcessor>(
|
|
||||||
context,
|
|
||||||
listen: false,
|
|
||||||
).initializePaymentProcessor(context);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +55,7 @@ class _RechargePageState extends State<RechargePage> {
|
|||||||
.getRechargePageScaffoldBackgroundColor(),
|
.getRechargePageScaffoldBackgroundColor(),
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
appBar: SocialChatStandardAppBar(
|
appBar: SocialChatStandardAppBar(
|
||||||
title: SCAppLocalizations.of(context)!.recharge,
|
title: SCAppLocalizations.of(context)!.wallet,
|
||||||
actions: [
|
actions: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: _showRecordDialog,
|
onTap: _showRecordDialog,
|
||||||
@ -83,38 +78,27 @@ class _RechargePageState extends State<RechargePage> {
|
|||||||
_buildWalletSection(),
|
_buildWalletSection(),
|
||||||
SizedBox(height: 36.w),
|
SizedBox(height: 36.w),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child:
|
||||||
padding: EdgeInsets.fromLTRB(12.w, 8.w, 12.w, 0),
|
Platform.isIOS
|
||||||
child: Column(
|
? const SizedBox.shrink()
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
: Padding(
|
||||||
children: [
|
padding: EdgeInsets.fromLTRB(12.w, 8.w, 12.w, 0),
|
||||||
_buildMethodSection(),
|
child: Column(
|
||||||
SizedBox(height: 14.w),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Expanded(child: _buildNativeProductList()),
|
children: [
|
||||||
if (Platform.isIOS) ...[
|
_buildMethodSection(),
|
||||||
SizedBox(height: 12.w),
|
SizedBox(height: 14.w),
|
||||||
_buildFooterButton(
|
Expanded(child: _buildNativeProductList()),
|
||||||
title:
|
SizedBox(height: 12.w),
|
||||||
SCAppLocalizations.of(
|
_buildFooterButton(
|
||||||
context,
|
title:
|
||||||
)!.restorePurchases,
|
SCAppLocalizations.of(context)!.recharge,
|
||||||
onTap: () {
|
onTap: _processNativePurchase,
|
||||||
Provider.of<IOSPaymentProcessor>(
|
),
|
||||||
context,
|
SizedBox(height: 14.w),
|
||||||
listen: false,
|
],
|
||||||
).recoverTransactions();
|
),
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
|
||||||
SizedBox(height: 12.w),
|
|
||||||
_buildFooterButton(
|
|
||||||
title: SCAppLocalizations.of(context)!.recharge,
|
|
||||||
onTap: _processNativePurchase,
|
|
||||||
),
|
|
||||||
SizedBox(height: 14.w),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import 'package:yumi/app/constants/sc_screen.dart';
|
|||||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||||
import 'package:yumi/shared/tools/sc_deviceId_utils.dart';
|
import 'package:yumi/shared/tools/sc_deviceId_utils.dart';
|
||||||
import 'package:yumi/shared/tools/sc_h5_url_utils.dart';
|
import 'package:yumi/shared/tools/sc_h5_url_utils.dart';
|
||||||
|
import 'package:yumi/shared/tools/sc_room_top_layer_guard.dart';
|
||||||
import 'package:yumi/shared/tools/sc_room_utils.dart';
|
import 'package:yumi/shared/tools/sc_room_utils.dart';
|
||||||
import 'package:yumi/shared/tools/sc_url_launcher_utils.dart';
|
import 'package:yumi/shared/tools/sc_url_launcher_utils.dart';
|
||||||
import 'package:yumi/main.dart';
|
import 'package:yumi/main.dart';
|
||||||
@ -40,6 +41,7 @@ class WebViewPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _WebViewPageState extends State<WebViewPage> {
|
class _WebViewPageState extends State<WebViewPage> {
|
||||||
late WebViewController _controller;
|
late WebViewController _controller;
|
||||||
|
late final SCRoomTopLayerGuardLease _topLayerGuardLease;
|
||||||
String _title = "";
|
String _title = "";
|
||||||
double _progress = 0.0;
|
double _progress = 0.0;
|
||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
@ -48,6 +50,7 @@ class _WebViewPageState extends State<WebViewPage> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_topLayerGuardLease = SCRoomTopLayerGuard().acquire(reason: 'webview_page');
|
||||||
urlLink = SCH5UrlUtils.appendAppParams(widget.url);
|
urlLink = SCH5UrlUtils.appendAppParams(widget.url);
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
debugPrint('[WebViewPage] load url=${_maskSensitiveUrl(urlLink)}');
|
debugPrint('[WebViewPage] load url=${_maskSensitiveUrl(urlLink)}');
|
||||||
@ -177,6 +180,7 @@ class _WebViewPageState extends State<WebViewPage> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_controller.loadRequest(Uri.parse('about:blank'));
|
_controller.loadRequest(Uri.parse('about:blank'));
|
||||||
|
_topLayerGuardLease.release();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,9 +20,11 @@ class RoomRtcEnterRoomConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class RoomRtcAnchorConfig {
|
class RoomRtcAnchorConfig {
|
||||||
const RoomRtcAnchorConfig({required this.muted});
|
const RoomRtcAnchorConfig({required this.muted, bool? publishMuted})
|
||||||
|
: publishMuted = publishMuted ?? muted;
|
||||||
|
|
||||||
final bool muted;
|
final bool muted;
|
||||||
|
final bool publishMuted;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoomRtcAudioMixingConfig {
|
class RoomRtcAudioMixingConfig {
|
||||||
|
|||||||
@ -34,6 +34,7 @@ abstract class RoomTrtcAudioEffectManager {
|
|||||||
void resumePlayMusic(int id);
|
void resumePlayMusic(int id);
|
||||||
void setMusicPublishVolume(int id, int volume);
|
void setMusicPublishVolume(int id, int volume);
|
||||||
void setMusicPlayoutVolume(int id, int volume);
|
void setMusicPlayoutVolume(int id, int volume);
|
||||||
|
void setVoiceCaptureVolume(int volume);
|
||||||
int getMusicCurrentPosInMS(int id);
|
int getMusicCurrentPosInMS(int id);
|
||||||
int getMusicDurationInMS(String path);
|
int getMusicDurationInMS(String path);
|
||||||
void seekMusicToPosInTime(int id, int pts);
|
void seekMusicToPosInTime(int id, int pts);
|
||||||
@ -156,6 +157,11 @@ class TencentRoomTrtcAudioEffectManager implements RoomTrtcAudioEffectManager {
|
|||||||
_manager.setMusicPlayoutVolume(id, volume);
|
_manager.setMusicPlayoutVolume(id, volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void setVoiceCaptureVolume(int volume) {
|
||||||
|
_manager.setVoiceCaptureVolume(volume);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int getMusicCurrentPosInMS(int id) {
|
int getMusicCurrentPosInMS(int id) {
|
||||||
return _manager.getMusicCurrentPosInMS(id);
|
return _manager.getMusicCurrentPosInMS(id);
|
||||||
@ -364,7 +370,8 @@ class TrtcRoomRtcEngineAdapter implements RoomRtcEngineAdapter {
|
|||||||
try {
|
try {
|
||||||
cloud.switchRole(TRTCRoleType.anchor);
|
cloud.switchRole(TRTCRoleType.anchor);
|
||||||
cloud.startLocalAudio(TRTCAudioQuality.defaultMode);
|
cloud.startLocalAudio(TRTCAudioQuality.defaultMode);
|
||||||
cloud.muteLocalAudio(config.muted);
|
_musicManager?.setVoiceCaptureVolume(config.muted ? 0 : 100);
|
||||||
|
cloud.muteLocalAudio(config.publishMuted);
|
||||||
_isLocalAudioStarted = true;
|
_isLocalAudioStarted = true;
|
||||||
return true;
|
return true;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:yumi/app_localizations.dart';
|
import 'package:yumi/app_localizations.dart';
|
||||||
@ -12,8 +14,17 @@ import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
|||||||
import 'package:yumi/shared/business_logic/models/res/room_user_card_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/room_user_card_res.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/sc_user_identity_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/sc_user_identity_res.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/sc_vip_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/sc_vip_res.dart';
|
||||||
|
import 'package:yumi/shared/data_sources/models/enum/sc_props_type.dart';
|
||||||
|
import 'package:yumi/shared/tools/sc_cp_relation_level_utils.dart';
|
||||||
|
import 'package:yumi/shared/tools/sc_cp_relation_notice_utils.dart';
|
||||||
|
|
||||||
class SocialChatUserProfileManager extends ChangeNotifier {
|
class SocialChatUserProfileManager extends ChangeNotifier {
|
||||||
|
static const List<String> _cpProfileRelationTypes = [
|
||||||
|
'CP',
|
||||||
|
'BROTHER',
|
||||||
|
'SISTERS',
|
||||||
|
];
|
||||||
|
|
||||||
SCUserProfileCmd? editUser;
|
SCUserProfileCmd? editUser;
|
||||||
|
|
||||||
void updateUserAvatar(String avatar) {
|
void updateUserAvatar(String avatar) {
|
||||||
@ -76,9 +87,38 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
|||||||
currentUser.setUserProfile(profile);
|
currentUser.setUserProfile(profile);
|
||||||
AccountStorage().setCurrentUser(currentUser);
|
AccountStorage().setCurrentUser(currentUser);
|
||||||
}
|
}
|
||||||
|
if ((userProfile?.id ?? "").isNotEmpty && userProfile?.id == profile.id) {
|
||||||
|
userProfile = profile;
|
||||||
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void syncCurrentUserChatBubble(
|
||||||
|
PropsResources? chatBubble, {
|
||||||
|
String? expireTime,
|
||||||
|
}) {
|
||||||
|
final profile = currentUserProfile;
|
||||||
|
if (profile == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final useProps = <UseProps>[
|
||||||
|
...?profile.useProps?.where(
|
||||||
|
(item) => item.propsResources?.type != SCPropsType.CHAT_BUBBLE.name,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
if (chatBubble != null) {
|
||||||
|
useProps.add(
|
||||||
|
UseProps(
|
||||||
|
expireTime: expireTime ?? "4102444800000",
|
||||||
|
propsResources: chatBubble,
|
||||||
|
userId: profile.id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
syncCurrentUserProfile(profile.copyWith(useProps: useProps));
|
||||||
|
}
|
||||||
|
|
||||||
Future fetchUserProfileData({
|
Future fetchUserProfileData({
|
||||||
bool loadGuardCount = true,
|
bool loadGuardCount = true,
|
||||||
bool refreshFamilyData = false,
|
bool refreshFamilyData = false,
|
||||||
@ -90,6 +130,7 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
var userInfo = await SCAccountRepository().loadUserInfo(userId);
|
var userInfo = await SCAccountRepository().loadUserInfo(userId);
|
||||||
userInfo = await _withCurrentVipLevel(userInfo, userId);
|
userInfo = await _withCurrentVipLevel(userInfo, userId);
|
||||||
|
userInfo = await _withCpCabinProfiles(userInfo, userId);
|
||||||
syncCurrentUserProfile(userInfo);
|
syncCurrentUserProfile(userInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,25 +161,208 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
|||||||
|
|
||||||
/// 当前正在查看的个人详情资料,不用于承载登录用户自己的 Me 页数据。
|
/// 当前正在查看的个人详情资料,不用于承载登录用户自己的 Me 页数据。
|
||||||
SocialChatUserProfile? userProfile;
|
SocialChatUserProfile? userProfile;
|
||||||
|
Object? userProfileLoadError;
|
||||||
|
bool isUserProfileLoading = false;
|
||||||
|
int _userInfoRequestSerial = 0;
|
||||||
|
|
||||||
void getUserInfoById(String userId) async {
|
Future<void> getUserInfoById(String userId) {
|
||||||
userProfile = null;
|
return _loadUserInfoById(userId, clearBeforeLoad: true);
|
||||||
final loadedProfile = await SCAccountRepository().loadUserInfo(userId);
|
}
|
||||||
userProfile = await _withCurrentVipLevel(loadedProfile, userId);
|
|
||||||
|
|
||||||
|
Future<void> reloadUserInfoById(String userId) {
|
||||||
|
return _loadUserInfoById(userId, clearBeforeLoad: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadUserInfoById(
|
||||||
|
String userId, {
|
||||||
|
required bool clearBeforeLoad,
|
||||||
|
}) async {
|
||||||
|
final requestSerial = ++_userInfoRequestSerial;
|
||||||
|
final normalizedUserId = userId.trim();
|
||||||
|
if (clearBeforeLoad) {
|
||||||
|
userProfile = null;
|
||||||
|
}
|
||||||
|
userProfileLoadError = null;
|
||||||
|
isUserProfileLoading = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
|
if (normalizedUserId.isEmpty) {
|
||||||
|
if (requestSerial == _userInfoRequestSerial) {
|
||||||
|
userProfileLoadError = ArgumentError('userId is empty');
|
||||||
|
isUserProfileLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final loadedProfile = await SCAccountRepository().loadUserInfo(
|
||||||
|
normalizedUserId,
|
||||||
|
);
|
||||||
|
if (requestSerial != _userInfoRequestSerial) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
userProfile = loadedProfile;
|
||||||
|
userProfileLoadError = null;
|
||||||
|
isUserProfileLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
unawaited(
|
||||||
|
_hydrateUserInfoById(
|
||||||
|
profile: loadedProfile,
|
||||||
|
userId: normalizedUserId,
|
||||||
|
requestSerial: requestSerial,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
if (requestSerial != _userInfoRequestSerial) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
userProfile = null;
|
||||||
|
userProfileLoadError = error;
|
||||||
|
_debugProfileLoadFailure(
|
||||||
|
scope: 'detail',
|
||||||
|
userId: normalizedUserId,
|
||||||
|
error: error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
if (requestSerial == _userInfoRequestSerial && isUserProfileLoading) {
|
||||||
|
isUserProfileLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///房间资料卡
|
///房间资料卡
|
||||||
RoomUserCardRes? userCardInfo;
|
RoomUserCardRes? userCardInfo;
|
||||||
|
Object? userCardLoadError;
|
||||||
|
bool isUserCardLoading = false;
|
||||||
|
bool isUserCardRelationLoading = false;
|
||||||
|
int _userCardRequestSerial = 0;
|
||||||
|
int _userCardSessionSerial = 0;
|
||||||
|
int? _activeUserCardSessionSerial;
|
||||||
|
|
||||||
// List<UserCountGuardRes>? userCountGuardResList;
|
// List<UserCountGuardRes>? userCountGuardResList;
|
||||||
|
|
||||||
void roomUserCard(String roomId, String userId) async {
|
int beginRoomUserCardSession() {
|
||||||
|
final sessionSerial = ++_userCardSessionSerial;
|
||||||
|
_activeUserCardSessionSerial = sessionSerial;
|
||||||
|
return sessionSerial;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<RoomUserCardRes?> roomUserCard(
|
||||||
|
String roomId,
|
||||||
|
String userId, {
|
||||||
|
int? sessionSerial,
|
||||||
|
}) async {
|
||||||
|
final activeSessionSerial = sessionSerial ?? beginRoomUserCardSession();
|
||||||
|
_activeUserCardSessionSerial = activeSessionSerial;
|
||||||
|
final requestSerial = ++_userCardRequestSerial;
|
||||||
|
final normalizedRoomId = roomId.trim();
|
||||||
|
final normalizedUserId = userId.trim();
|
||||||
userCardInfo = null;
|
userCardInfo = null;
|
||||||
userCardInfo = await SCChatRoomRepository().roomUserCard(roomId, userId);
|
userCardLoadError = null;
|
||||||
userCardInfo = await _withCurrentVipLevelForCard(userCardInfo, userId);
|
isUserCardLoading = true;
|
||||||
|
isUserCardRelationLoading = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
|
if (normalizedUserId.isEmpty) {
|
||||||
|
if (requestSerial == _userCardRequestSerial) {
|
||||||
|
userCardLoadError = ArgumentError('userId is empty');
|
||||||
|
isUserCardLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final card = await _loadHydratedRoomUserCard(
|
||||||
|
roomId: normalizedRoomId,
|
||||||
|
userId: normalizedUserId,
|
||||||
|
);
|
||||||
|
if (requestSerial != _userCardRequestSerial ||
|
||||||
|
activeSessionSerial != _activeUserCardSessionSerial) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
userCardInfo = card;
|
||||||
|
userCardLoadError = null;
|
||||||
|
isUserCardLoading = false;
|
||||||
|
isUserCardRelationLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
return card;
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
if (requestSerial != _userCardRequestSerial) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
userCardInfo = null;
|
||||||
|
userCardLoadError = error;
|
||||||
|
isUserCardRelationLoading = false;
|
||||||
|
_debugProfileLoadFailure(
|
||||||
|
scope: 'roomCard',
|
||||||
|
userId: normalizedUserId,
|
||||||
|
error: error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
if (requestSerial == _userCardRequestSerial && isUserCardLoading) {
|
||||||
|
isUserCardLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<RoomUserCardRes?> preloadRoomUserCardForOpen(
|
||||||
|
String roomId,
|
||||||
|
String userId, {
|
||||||
|
int? sessionSerial,
|
||||||
|
}) {
|
||||||
|
return roomUserCard(roomId, userId, sessionSerial: sessionSerial);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<RoomUserCardRes?> _loadHydratedRoomUserCard({
|
||||||
|
required String roomId,
|
||||||
|
required String userId,
|
||||||
|
}) async {
|
||||||
|
final card = await SCChatRoomRepository().roomUserCard(roomId, userId);
|
||||||
|
return _withCurrentVipLevelForCard(card, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _hydrateUserInfoById({
|
||||||
|
required SocialChatUserProfile profile,
|
||||||
|
required String userId,
|
||||||
|
required int requestSerial,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
var mergedProfile = await _withCurrentVipLevel(profile, userId);
|
||||||
|
mergedProfile = await _withCpCabinProfiles(mergedProfile, userId);
|
||||||
|
if (requestSerial != _userInfoRequestSerial) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
userProfile = mergedProfile;
|
||||||
|
userProfileLoadError = null;
|
||||||
|
notifyListeners();
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
_debugProfileLoadFailure(
|
||||||
|
scope: 'detailHydrate',
|
||||||
|
userId: userId,
|
||||||
|
error: error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearRoomUserCard({int? sessionSerial}) {
|
||||||
|
if (sessionSerial != null &&
|
||||||
|
sessionSerial != _activeUserCardSessionSerial) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_userCardRequestSerial++;
|
||||||
|
_activeUserCardSessionSerial = null;
|
||||||
|
userCardInfo = null;
|
||||||
|
userCardLoadError = null;
|
||||||
|
isUserCardLoading = false;
|
||||||
|
isUserCardRelationLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<RoomUserCardRes?> _withCurrentVipLevelForCard(
|
Future<RoomUserCardRes?> _withCurrentVipLevelForCard(
|
||||||
@ -149,13 +373,373 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
|||||||
if (card == null || profile == null) {
|
if (card == null || profile == null) {
|
||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
final mergedProfile = await _withCurrentVipLevel(profile, userId);
|
var mergedProfile = await _withCurrentVipLevel(profile, userId);
|
||||||
|
mergedProfile = await _withCpCabinProfiles(mergedProfile, userId);
|
||||||
if (mergedProfile == profile) {
|
if (mergedProfile == profile) {
|
||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
return card.copyWith(userProfile: mergedProfile);
|
return card.copyWith(userProfile: mergedProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _debugProfileLoadFailure({
|
||||||
|
required String scope,
|
||||||
|
required String userId,
|
||||||
|
required Object error,
|
||||||
|
required StackTrace stackTrace,
|
||||||
|
}) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint(
|
||||||
|
'[ProfileLoad][$scope] failed userId=$userId error=$error\n$stackTrace',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<SocialChatUserProfile> _withCpCabinProfiles(
|
||||||
|
SocialChatUserProfile profile,
|
||||||
|
String userId,
|
||||||
|
) async {
|
||||||
|
if (userId.trim().isEmpty) {
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
final fallbackCpPairs = <CPRes>[];
|
||||||
|
final fallbackCloseFriendPairs = <CPRes>[];
|
||||||
|
_addCpRelationPairs(
|
||||||
|
cpPairs: fallbackCpPairs,
|
||||||
|
closeFriendPairs: fallbackCloseFriendPairs,
|
||||||
|
relations: profile.cpList,
|
||||||
|
fallbackRelationType: 'CP',
|
||||||
|
);
|
||||||
|
_addCpRelationPairs(
|
||||||
|
cpPairs: fallbackCpPairs,
|
||||||
|
closeFriendPairs: fallbackCloseFriendPairs,
|
||||||
|
relations: profile.closeFriendList,
|
||||||
|
);
|
||||||
|
final cpPairs = <CPRes>[];
|
||||||
|
final closeFriendPairs = <CPRes>[];
|
||||||
|
_addCpRelationPairs(
|
||||||
|
cpPairs: cpPairs,
|
||||||
|
closeFriendPairs: closeFriendPairs,
|
||||||
|
relations: fallbackCpPairs,
|
||||||
|
);
|
||||||
|
_addCpRelationPairs(
|
||||||
|
cpPairs: cpPairs,
|
||||||
|
closeFriendPairs: closeFriendPairs,
|
||||||
|
relations: fallbackCloseFriendPairs,
|
||||||
|
);
|
||||||
|
var loadedAnyCabin = false;
|
||||||
|
if (kDebugMode) {
|
||||||
|
final currentUserId =
|
||||||
|
AccountStorage().getCurrentUser()?.userProfile?.id?.trim() ?? "";
|
||||||
|
debugPrint(
|
||||||
|
'[CP][Profile] source userId=$userId currentUserId=$currentUserId '
|
||||||
|
'profileCp=${profile.cpList?.length ?? 0} '
|
||||||
|
'profileCloseFriends=${profile.closeFriendList?.length ?? 0}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await Future.wait(
|
||||||
|
_cpProfileRelationTypes.map((relationType) async {
|
||||||
|
final pairs = await _safeLoadCpRelationshipPairs(userId, relationType);
|
||||||
|
_addCpRelationPairs(
|
||||||
|
cpPairs: cpPairs,
|
||||||
|
closeFriendPairs: closeFriendPairs,
|
||||||
|
relations: pairs,
|
||||||
|
fallbackRelationType: relationType,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await Future.wait(
|
||||||
|
_cpProfileRelationTypes.map((relationType) async {
|
||||||
|
final cabin = await _safeLoadCpCabin(userId, relationType);
|
||||||
|
if (cabin != null) {
|
||||||
|
loadedAnyCabin = true;
|
||||||
|
}
|
||||||
|
final cpPair = cabin?.cpPairUserProfileCO;
|
||||||
|
if (cpPair == null || !_hasCpPair(cpPair)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final normalizedType = _normalizeCpRelationType(
|
||||||
|
cpPair.relationType ?? relationType,
|
||||||
|
);
|
||||||
|
final cabinLevelText = scCpRelationLevelTextFromCabin(
|
||||||
|
cabin,
|
||||||
|
fallbackText: cpPair.levelText,
|
||||||
|
);
|
||||||
|
final rightsLevelText = await _safeLoadCurrentUserCpLevelText(
|
||||||
|
userId: userId,
|
||||||
|
cpPair: cpPair,
|
||||||
|
relationType: normalizedType,
|
||||||
|
fallbackText: cabinLevelText,
|
||||||
|
);
|
||||||
|
final enrichedPair = cpPair.copyWith(
|
||||||
|
relationType: normalizedType,
|
||||||
|
cpValue: cpPair.cpValue ?? cabin?.cpVal?.toDouble(),
|
||||||
|
levelText: rightsLevelText ?? cabinLevelText,
|
||||||
|
dismissCost: cpPair.dismissCost ?? _cpDismissCostFromCabin(cabin),
|
||||||
|
);
|
||||||
|
_addCpRelationPair(
|
||||||
|
cpPairs: cpPairs,
|
||||||
|
closeFriendPairs: closeFriendPairs,
|
||||||
|
relation: enrichedPair,
|
||||||
|
fallbackRelationType: normalizedType,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint(
|
||||||
|
'[CP][Profile] hydrate userId=$userId cp=${cpPairs.length} '
|
||||||
|
'closeFriends=${closeFriendPairs.length}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!loadedAnyCabin) {
|
||||||
|
return profile.copyWith(
|
||||||
|
cpList: cpPairs,
|
||||||
|
closeFriendList: closeFriendPairs,
|
||||||
|
isCpRelation: cpPairs.isNotEmpty,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return profile.copyWith(
|
||||||
|
cpList: cpPairs,
|
||||||
|
closeFriendList: closeFriendPairs,
|
||||||
|
isCpRelation: cpPairs.isNotEmpty,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addCpRelationPairs({
|
||||||
|
required List<CPRes> cpPairs,
|
||||||
|
required List<CPRes> closeFriendPairs,
|
||||||
|
required List<CPRes>? relations,
|
||||||
|
String? fallbackRelationType,
|
||||||
|
}) {
|
||||||
|
for (final relation in relations ?? const <CPRes>[]) {
|
||||||
|
_addCpRelationPair(
|
||||||
|
cpPairs: cpPairs,
|
||||||
|
closeFriendPairs: closeFriendPairs,
|
||||||
|
relation: relation,
|
||||||
|
fallbackRelationType: fallbackRelationType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addCpRelationPair({
|
||||||
|
required List<CPRes> cpPairs,
|
||||||
|
required List<CPRes> closeFriendPairs,
|
||||||
|
required CPRes relation,
|
||||||
|
String? fallbackRelationType,
|
||||||
|
}) {
|
||||||
|
if (!_hasCpPair(relation)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final relationTypeSource =
|
||||||
|
(relation.relationType?.trim().isNotEmpty ?? false)
|
||||||
|
? relation.relationType
|
||||||
|
: fallbackRelationType;
|
||||||
|
if ((relationTypeSource ?? "").trim().isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final normalizedType = _normalizeCpRelationType(relationTypeSource);
|
||||||
|
final normalizedRelation = relation.copyWith(relationType: normalizedType);
|
||||||
|
final target = normalizedType == 'CP' ? cpPairs : closeFriendPairs;
|
||||||
|
final relationKey = _cpRelationPairKey(normalizedRelation);
|
||||||
|
final existingIndex = target.indexWhere(
|
||||||
|
(item) => _cpRelationPairKey(item) == relationKey,
|
||||||
|
);
|
||||||
|
if (existingIndex >= 0) {
|
||||||
|
target[existingIndex] = normalizedRelation;
|
||||||
|
} else {
|
||||||
|
target.add(normalizedRelation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _cpRelationPairKey(CPRes relation) {
|
||||||
|
final type = _normalizeCpRelationType(relation.relationType);
|
||||||
|
final userIds = <String>[
|
||||||
|
relation.meUserId?.trim() ?? "",
|
||||||
|
relation.cpUserId?.trim() ?? "",
|
||||||
|
]..removeWhere((value) => value.isEmpty);
|
||||||
|
if (userIds.length >= 2) {
|
||||||
|
userIds.sort();
|
||||||
|
return '$type|id|${userIds.join("|")}';
|
||||||
|
}
|
||||||
|
final accounts = <String>[
|
||||||
|
relation.meAccount?.trim() ?? "",
|
||||||
|
relation.cpAccount?.trim() ?? "",
|
||||||
|
]..removeWhere((value) => value.isEmpty);
|
||||||
|
if (accounts.length >= 2) {
|
||||||
|
accounts.sort();
|
||||||
|
return '$type|account|${accounts.join("|")}';
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
type,
|
||||||
|
relation.meUserId?.trim() ?? "",
|
||||||
|
relation.meAccount?.trim() ?? "",
|
||||||
|
relation.cpUserId?.trim() ?? "",
|
||||||
|
relation.cpAccount?.trim() ?? "",
|
||||||
|
relation.cpUserNickname?.trim() ?? "",
|
||||||
|
relation.meUserNickname?.trim() ?? "",
|
||||||
|
].join('|');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<CPRes>> _safeLoadCpRelationshipPairs(
|
||||||
|
String userId,
|
||||||
|
String relationType,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final pairs = await SCAccountRepository().cpRelationshipPairs(
|
||||||
|
relationType: relationType,
|
||||||
|
userId: userId,
|
||||||
|
);
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint(
|
||||||
|
'[CP][Profile] pair loaded userId=$userId relationType=$relationType '
|
||||||
|
'count=${pairs.length}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return pairs;
|
||||||
|
} catch (error) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint(
|
||||||
|
'[CP][Profile] pair failed userId=$userId relationType=$relationType error=$error',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const <CPRes>[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<SCCpCabinRes?> _safeLoadCpCabin(
|
||||||
|
String userId,
|
||||||
|
String relationType,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final cabin = await SCAccountRepository().cpCabin(
|
||||||
|
userId,
|
||||||
|
relationType: relationType,
|
||||||
|
);
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint(
|
||||||
|
'[CP][Profile] cabin loaded userId=$userId relationType=$relationType '
|
||||||
|
'hasPair=${_hasCpPair(cabin.cpPairUserProfileCO)} cpVal=${cabin.cpVal}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return cabin;
|
||||||
|
} catch (error) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint(
|
||||||
|
'[CP][Profile] cabin failed userId=$userId '
|
||||||
|
'relationType=$relationType error=$error',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> _safeLoadCurrentUserCpLevelText({
|
||||||
|
required String userId,
|
||||||
|
required CPRes cpPair,
|
||||||
|
required String relationType,
|
||||||
|
String? fallbackText,
|
||||||
|
}) async {
|
||||||
|
final partnerUserId = _cpPartnerUserIdForCurrentUser(cpPair);
|
||||||
|
if (partnerUserId.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final config = await SCAccountRepository().cpRightsConfig(
|
||||||
|
relationType: relationType,
|
||||||
|
cpUserId: partnerUserId,
|
||||||
|
);
|
||||||
|
return scCpRelationLevelTextFromRightsConfig(
|
||||||
|
config,
|
||||||
|
fallbackText: fallbackText,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint(
|
||||||
|
'[CP][Profile] rightsConfig failed userId=$userId '
|
||||||
|
'partnerUserId=$partnerUserId relationType=$relationType '
|
||||||
|
'error=$error',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _cpPartnerUserIdForCurrentUser(CPRes cpPair) {
|
||||||
|
final currentUserId =
|
||||||
|
AccountStorage().getCurrentUser()?.userProfile?.id?.trim() ?? "";
|
||||||
|
if (currentUserId.isEmpty) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
final meUserId = cpPair.meUserId?.trim() ?? "";
|
||||||
|
final cpUserId = cpPair.cpUserId?.trim() ?? "";
|
||||||
|
if (currentUserId == meUserId) {
|
||||||
|
return cpUserId;
|
||||||
|
}
|
||||||
|
if (currentUserId == cpUserId) {
|
||||||
|
return meUserId;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _hasCpPair(CPRes? pair) {
|
||||||
|
return (pair?.cpUserId?.trim().isNotEmpty ?? false) ||
|
||||||
|
(pair?.cpUserAvatar?.trim().isNotEmpty ?? false) ||
|
||||||
|
(pair?.cpUserNickname?.trim().isNotEmpty ?? false) ||
|
||||||
|
(pair?.meUserId?.trim().isNotEmpty ?? false) ||
|
||||||
|
(pair?.meUserAvatar?.trim().isNotEmpty ?? false) ||
|
||||||
|
(pair?.meUserNickname?.trim().isNotEmpty ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _normalizeCpRelationType(String? relationType) {
|
||||||
|
return scNormalizeCpRelationType(relationType);
|
||||||
|
}
|
||||||
|
|
||||||
|
int? _cpDismissCostFromCabin(SCCpCabinRes? cabin) {
|
||||||
|
final directCost = cabin?.dismissCost;
|
||||||
|
if (directCost != null) {
|
||||||
|
return directCost;
|
||||||
|
}
|
||||||
|
final config = cabin?.currentCpCabinConfigCO;
|
||||||
|
if (config is! Map) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return _cpIntValue(
|
||||||
|
_firstCpConfigValue(config, const [
|
||||||
|
"dismissCost",
|
||||||
|
"dismissGold",
|
||||||
|
"dismissCoins",
|
||||||
|
"dismissCoin",
|
||||||
|
"cancelCost",
|
||||||
|
"cancelGold",
|
||||||
|
"removeCost",
|
||||||
|
"partWaysCost",
|
||||||
|
"consumeGold",
|
||||||
|
"goldCost",
|
||||||
|
"cost",
|
||||||
|
"coins",
|
||||||
|
"priceGold",
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic _firstCpConfigValue(Map source, List<String> keys) {
|
||||||
|
for (final key in keys) {
|
||||||
|
if (source.containsKey(key) && source[key] != null) {
|
||||||
|
return source[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int? _cpIntValue(dynamic value) {
|
||||||
|
if (value is int) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (value is num) {
|
||||||
|
return value.toInt();
|
||||||
|
}
|
||||||
|
return int.tryParse(value?.toString() ?? "");
|
||||||
|
}
|
||||||
|
|
||||||
Future<SocialChatUserProfile> _withCurrentVipLevel(
|
Future<SocialChatUserProfile> _withCurrentVipLevel(
|
||||||
SocialChatUserProfile profile,
|
SocialChatUserProfile profile,
|
||||||
String userId,
|
String userId,
|
||||||
@ -193,10 +777,12 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _isCurrentUserId(String userId) {
|
bool _isCurrentUserId(String userId) {
|
||||||
final currentUserId = AccountStorage().getCurrentUser()?.userProfile?.id;
|
final currentUserId =
|
||||||
|
AccountStorage().getCurrentUser()?.userProfile?.id?.trim();
|
||||||
|
final normalizedUserId = userId.trim();
|
||||||
return currentUserId != null &&
|
return currentUserId != null &&
|
||||||
currentUserId.isNotEmpty &&
|
currentUserId.isNotEmpty &&
|
||||||
currentUserId == userId;
|
currentUserId == normalizedUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SCVipStatusRes?> _loadCurrentVipStatus() async {
|
Future<SCVipStatusRes?> _loadCurrentVipStatus() async {
|
||||||
@ -318,7 +904,7 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
void cpRlationshipProcessApply(
|
Future<bool> cpRlationshipProcessApply(
|
||||||
BuildContext ct,
|
BuildContext ct,
|
||||||
String id,
|
String id,
|
||||||
bool status,
|
bool status,
|
||||||
@ -326,8 +912,183 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
|||||||
final successMessage = SCAppLocalizations.of(ct)!.operationSuccessful;
|
final successMessage = SCAppLocalizations.of(ct)!.operationSuccessful;
|
||||||
try {
|
try {
|
||||||
await SCAccountRepository().cpRelationshipProcessApply(id, status);
|
await SCAccountRepository().cpRelationshipProcessApply(id, status);
|
||||||
fetchUserProfileData(loadGuardCount: false);
|
await fetchUserProfileData(loadGuardCount: false);
|
||||||
|
await refreshLoadedCpProfiles();
|
||||||
SCTts.show(successMessage);
|
SCTts.show(successMessage);
|
||||||
} catch (e) {}
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refreshLoadedCpProfiles() async {
|
||||||
|
await _refreshLoadedCpProfilesForUserIds(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refreshLoadedCpProfilesForUsers(Set<String> userIds) async {
|
||||||
|
final normalizedUserIds =
|
||||||
|
userIds.map((userId) => userId.trim()).where((userId) {
|
||||||
|
return userId.isNotEmpty;
|
||||||
|
}).toSet();
|
||||||
|
if (normalizedUserIds.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await _refreshLoadedCpProfilesForUserIds(normalizedUserIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeLoadedCpRelation({
|
||||||
|
required String userId,
|
||||||
|
required String peerUserId,
|
||||||
|
required String relationType,
|
||||||
|
}) {
|
||||||
|
final normalizedUserId = userId.trim();
|
||||||
|
final normalizedPeerUserId = peerUserId.trim();
|
||||||
|
final normalizedRelationType = _normalizeCpRelationType(relationType);
|
||||||
|
if (normalizedUserId.isEmpty ||
|
||||||
|
normalizedPeerUserId.isEmpty ||
|
||||||
|
normalizedRelationType.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var changed = false;
|
||||||
|
final currentProfile = currentUserProfile;
|
||||||
|
if (currentProfile != null) {
|
||||||
|
final nextCurrentProfile = _profileWithoutCpRelation(
|
||||||
|
currentProfile,
|
||||||
|
userId: normalizedUserId,
|
||||||
|
peerUserId: normalizedPeerUserId,
|
||||||
|
relationType: normalizedRelationType,
|
||||||
|
);
|
||||||
|
if (!identical(nextCurrentProfile, currentProfile)) {
|
||||||
|
final currentUser = AccountStorage().getCurrentUser();
|
||||||
|
currentUser?.setUserProfile(nextCurrentProfile);
|
||||||
|
if (currentUser != null) {
|
||||||
|
AccountStorage().setCurrentUser(currentUser);
|
||||||
|
}
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final detailProfile = userProfile;
|
||||||
|
if (detailProfile != null) {
|
||||||
|
final nextDetailProfile = _profileWithoutCpRelation(
|
||||||
|
detailProfile,
|
||||||
|
userId: normalizedUserId,
|
||||||
|
peerUserId: normalizedPeerUserId,
|
||||||
|
relationType: normalizedRelationType,
|
||||||
|
);
|
||||||
|
if (!identical(nextDetailProfile, detailProfile)) {
|
||||||
|
userProfile = nextDetailProfile;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final cardProfile = userCardInfo?.userProfile;
|
||||||
|
if (cardProfile != null) {
|
||||||
|
final nextCardProfile = _profileWithoutCpRelation(
|
||||||
|
cardProfile,
|
||||||
|
userId: normalizedUserId,
|
||||||
|
peerUserId: normalizedPeerUserId,
|
||||||
|
relationType: normalizedRelationType,
|
||||||
|
);
|
||||||
|
if (!identical(nextCardProfile, cardProfile)) {
|
||||||
|
userCardInfo = userCardInfo?.copyWith(userProfile: nextCardProfile);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SocialChatUserProfile _profileWithoutCpRelation(
|
||||||
|
SocialChatUserProfile profile, {
|
||||||
|
required String userId,
|
||||||
|
required String peerUserId,
|
||||||
|
required String relationType,
|
||||||
|
}) {
|
||||||
|
var changed = false;
|
||||||
|
List<CPRes>? filterRelations(List<CPRes>? relations) {
|
||||||
|
if (relations == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final filtered =
|
||||||
|
relations.where((relation) {
|
||||||
|
return !_matchesCpRelation(
|
||||||
|
relation,
|
||||||
|
userId: userId,
|
||||||
|
peerUserId: peerUserId,
|
||||||
|
relationType: relationType,
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
if (filtered.length != relations.length) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
final nextCpList = filterRelations(profile.cpList);
|
||||||
|
final nextCloseFriendList = filterRelations(profile.closeFriendList);
|
||||||
|
if (!changed) {
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
return profile.copyWith(
|
||||||
|
cpList: nextCpList,
|
||||||
|
closeFriendList: nextCloseFriendList,
|
||||||
|
isCpRelation: nextCpList?.isNotEmpty == true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _matchesCpRelation(
|
||||||
|
CPRes relation, {
|
||||||
|
required String userId,
|
||||||
|
required String peerUserId,
|
||||||
|
required String relationType,
|
||||||
|
}) {
|
||||||
|
if (_normalizeCpRelationType(relation.relationType) != relationType) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final relationUserIds = <String>{
|
||||||
|
relation.meUserId?.trim() ?? "",
|
||||||
|
relation.cpUserId?.trim() ?? "",
|
||||||
|
}..remove("");
|
||||||
|
return relationUserIds.contains(userId) &&
|
||||||
|
relationUserIds.contains(peerUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _refreshLoadedCpProfilesForUserIds(Set<String>? userIds) async {
|
||||||
|
String refreshedUserId(String? userId) {
|
||||||
|
final normalizedUserId = userId?.trim() ?? "";
|
||||||
|
if (normalizedUserId.isEmpty) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return userIds == null || userIds.contains(normalizedUserId)
|
||||||
|
? normalizedUserId
|
||||||
|
: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
final currentProfile = currentUserProfile;
|
||||||
|
final currentUserId = refreshedUserId(currentProfile?.id);
|
||||||
|
if (currentProfile != null && currentUserId.isNotEmpty) {
|
||||||
|
syncCurrentUserProfile(
|
||||||
|
await _withCpCabinProfiles(currentProfile, currentUserId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final detailUserId = refreshedUserId(userProfile?.id);
|
||||||
|
if (userProfile != null && detailUserId.isNotEmpty) {
|
||||||
|
userProfile = await _withCpCabinProfiles(userProfile!, detailUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
final cardProfile = userCardInfo?.userProfile;
|
||||||
|
final cardUserId = refreshedUserId(cardProfile?.id);
|
||||||
|
if (userCardInfo != null && cardProfile != null && cardUserId.isNotEmpty) {
|
||||||
|
userCardInfo = userCardInfo?.copyWith(
|
||||||
|
userProfile: await _withCpCabinProfiles(cardProfile, cardUserId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import 'package:yumi/shared/business_logic/models/res/sc_banner_leaderboard_res.
|
|||||||
import 'package:yumi/shared/business_logic/models/res/country_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/country_res.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/gift_backpack_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/gift_backpack_res.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/gift_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/gift_res.dart';
|
||||||
|
import 'package:yumi/shared/business_logic/models/res/sc_first_recharge_reward_res.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/sc_index_banner_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/sc_index_banner_res.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/sc_room_emoji_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/sc_room_emoji_res.dart';
|
||||||
|
|
||||||
@ -36,6 +37,9 @@ class SCAppGeneralManager extends ChangeNotifier {
|
|||||||
bool _hasGiftListLoaded = false;
|
bool _hasGiftListLoaded = false;
|
||||||
bool _isFetchingGiftBackpack = false;
|
bool _isFetchingGiftBackpack = false;
|
||||||
bool _hasGiftBackpackLoaded = false;
|
bool _hasGiftBackpackLoaded = false;
|
||||||
|
bool _isFetchingFirstRechargeRewardHome = false;
|
||||||
|
bool _hasFirstRechargeRewardHomeLoaded = false;
|
||||||
|
SCFirstRechargeRewardHomeRes? _firstRechargeRewardHome;
|
||||||
|
|
||||||
Map<String, List<SocialChatGiftRes>> giftByTab = {};
|
Map<String, List<SocialChatGiftRes>> giftByTab = {};
|
||||||
Map<String, List<SocialChatGiftRes>> activityGiftByTab = {};
|
Map<String, List<SocialChatGiftRes>> activityGiftByTab = {};
|
||||||
@ -59,10 +63,40 @@ class SCAppGeneralManager extends ChangeNotifier {
|
|||||||
bool get isGiftBackpackLoading =>
|
bool get isGiftBackpackLoading =>
|
||||||
_isFetchingGiftBackpack && !_hasGiftBackpackLoaded;
|
_isFetchingGiftBackpack && !_hasGiftBackpackLoaded;
|
||||||
|
|
||||||
|
bool get shouldShowFirstRechargeReward =>
|
||||||
|
_firstRechargeRewardHome?.shouldShowRewardDialog ?? false;
|
||||||
|
|
||||||
|
SCFirstRechargeRewardHomeRes? get firstRechargeRewardHome =>
|
||||||
|
_firstRechargeRewardHome;
|
||||||
|
|
||||||
bool isGiftTabLoading(String type) {
|
bool isGiftTabLoading(String type) {
|
||||||
return type == backpackGiftTab ? isGiftBackpackLoading : isGiftListLoading;
|
return type == backpackGiftTab ? isGiftBackpackLoading : isGiftListLoading;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<SCFirstRechargeRewardHomeRes?> refreshFirstRechargeRewardHome({
|
||||||
|
bool forceRefresh = false,
|
||||||
|
}) async {
|
||||||
|
if (_isFetchingFirstRechargeRewardHome) {
|
||||||
|
return _firstRechargeRewardHome;
|
||||||
|
}
|
||||||
|
if (_hasFirstRechargeRewardHomeLoaded && !forceRefresh) {
|
||||||
|
return _firstRechargeRewardHome;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isFetchingFirstRechargeRewardHome = true;
|
||||||
|
try {
|
||||||
|
final home = await SCConfigRepositoryImp().firstRechargeRewardHome();
|
||||||
|
_firstRechargeRewardHome = home;
|
||||||
|
_hasFirstRechargeRewardHomeLoaded = true;
|
||||||
|
return home;
|
||||||
|
} catch (_) {
|
||||||
|
return _firstRechargeRewardHome;
|
||||||
|
} finally {
|
||||||
|
_isFetchingFirstRechargeRewardHome = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///加载国家列表
|
///加载国家列表
|
||||||
Future<void> fetchCountryList() async {
|
Future<void> fetchCountryList() async {
|
||||||
if (_isFetchingCountryList) {
|
if (_isFetchingCountryList) {
|
||||||
@ -179,24 +213,28 @@ class SCAppGeneralManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _displayGiftTab(String? giftTab) {
|
||||||
|
return giftTab == "EXCLUSIVE" ? "CUSTOMIZED" : (giftTab ?? "");
|
||||||
|
}
|
||||||
|
|
||||||
void _rebuildGiftTabs({required bool includeCustomized}) {
|
void _rebuildGiftTabs({required bool includeCustomized}) {
|
||||||
giftByTab.clear();
|
giftByTab.clear();
|
||||||
_giftByIdMap.clear();
|
_giftByIdMap.clear();
|
||||||
_giftByStandardIdMap.clear();
|
_giftByStandardIdMap.clear();
|
||||||
for (var gift in giftResList) {
|
for (var gift in giftResList) {
|
||||||
giftByTab[gift.giftTab];
|
final tab = _displayGiftTab(gift.giftTab);
|
||||||
var gmap = giftByTab[gift.giftTab];
|
var gmap = giftByTab[tab];
|
||||||
gmap ??= [];
|
gmap ??= [];
|
||||||
gmap.add(gift);
|
gmap.add(gift);
|
||||||
giftByTab[gift.giftTab!] = gmap;
|
giftByTab[tab] = gmap;
|
||||||
var gAllMap = giftByTab["ALL"];
|
var gAllMap = giftByTab["ALL"];
|
||||||
gAllMap ??= [];
|
gAllMap ??= [];
|
||||||
if (gift.giftTab != "NSCIONAL_FLAG" &&
|
if (tab != "NSCIONAL_FLAG" &&
|
||||||
gift.giftTab != "ACTIVITY" &&
|
tab != "ACTIVITY" &&
|
||||||
gift.giftTab != "LUCKY_GIFT" &&
|
tab != "LUCKY_GIFT" &&
|
||||||
gift.giftTab != "CP" &&
|
tab != "CP" &&
|
||||||
gift.giftTab != "CUSTOMIZED" &&
|
tab != "CUSTOMIZED" &&
|
||||||
gift.giftTab != "MAGIC") {
|
tab != "MAGIC") {
|
||||||
gAllMap.add(gift);
|
gAllMap.add(gift);
|
||||||
}
|
}
|
||||||
giftByTab["ALL"] = gAllMap;
|
giftByTab["ALL"] = gAllMap;
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
import 'package:just_audio/just_audio.dart';
|
||||||
import 'package:on_audio_query/on_audio_query.dart';
|
import 'package:on_audio_query/on_audio_query.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:yumi/shared/data_sources/models/sc_music_folder_mode.dart';
|
import 'package:yumi/shared/data_sources/models/sc_music_folder_mode.dart';
|
||||||
import 'package:yumi/shared/data_sources/models/sc_music_mode.dart';
|
import 'package:yumi/shared/data_sources/models/sc_music_mode.dart';
|
||||||
import 'package:yumi/shared/tools/sc_permission_utils.dart';
|
import 'package:yumi/shared/tools/sc_permission_utils.dart';
|
||||||
@ -15,6 +19,10 @@ class LocalMusicScanner {
|
|||||||
Future<List<SCMusicFolderMode>> scanMp3Folders({
|
Future<List<SCMusicFolderMode>> scanMp3Folders({
|
||||||
List<SCMusicMode> addedSongs = const [],
|
List<SCMusicMode> addedSongs = const [],
|
||||||
}) async {
|
}) async {
|
||||||
|
if (Platform.isIOS) {
|
||||||
|
return _pickIosMp3Files(addedSongs: addedSongs);
|
||||||
|
}
|
||||||
|
|
||||||
final hasPermission = await SCPermissionUtils.checkAudioPermission();
|
final hasPermission = await SCPermissionUtils.checkAudioPermission();
|
||||||
if (!hasPermission) {
|
if (!hasPermission) {
|
||||||
return const <SCMusicFolderMode>[];
|
return const <SCMusicFolderMode>[];
|
||||||
@ -96,6 +104,98 @@ class LocalMusicScanner {
|
|||||||
return List<SCMusicFolderMode>.unmodifiable(folders);
|
return List<SCMusicFolderMode>.unmodifiable(folders);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<SCMusicFolderMode>> _pickIosMp3Files({
|
||||||
|
required List<SCMusicMode> addedSongs,
|
||||||
|
}) async {
|
||||||
|
final FilePickerResult? result;
|
||||||
|
try {
|
||||||
|
result = await FilePicker.pickFiles(
|
||||||
|
type: FileType.custom,
|
||||||
|
allowedExtensions: const ['mp3'],
|
||||||
|
allowMultiple: true,
|
||||||
|
withData: false,
|
||||||
|
withReadStream: false,
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
return const <SCMusicFolderMode>[];
|
||||||
|
}
|
||||||
|
if (result == null || result.files.isEmpty) {
|
||||||
|
return const <SCMusicFolderMode>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
final addedIds = addedSongs.map((item) => item.id).toSet();
|
||||||
|
final addedPaths = addedSongs.map((item) => item.localPath).toSet();
|
||||||
|
final storageDir = await _iosRoomMusicDirectory();
|
||||||
|
final player = AudioPlayer();
|
||||||
|
final songs = <SCMusicMode>[];
|
||||||
|
final seen = <String>{};
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (final file in result.files) {
|
||||||
|
final sourcePath = (file.path ?? '').trim();
|
||||||
|
if (sourcePath.isEmpty ||
|
||||||
|
!_isMp3FileName(file.name) ||
|
||||||
|
!_fileExists(sourcePath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final sourceId = _firstNonEmpty([
|
||||||
|
file.identifier,
|
||||||
|
'${file.name}:${file.size}',
|
||||||
|
sourcePath,
|
||||||
|
]);
|
||||||
|
if (sourceId.isEmpty || !seen.add(sourceId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final storedPath = await _copyIosPickedFile(
|
||||||
|
sourcePath: sourcePath,
|
||||||
|
fileName: file.name,
|
||||||
|
sourceId: sourceId,
|
||||||
|
storageDir: storageDir,
|
||||||
|
);
|
||||||
|
final durationMs = await _readLocalMp3DurationMs(player, storedPath);
|
||||||
|
final storedFile = File(storedPath);
|
||||||
|
final safeFileName = p.basename(storedPath);
|
||||||
|
songs.add(
|
||||||
|
SCMusicMode(
|
||||||
|
id: sourceId,
|
||||||
|
title: p.basenameWithoutExtension(file.name).trim(),
|
||||||
|
artist: '',
|
||||||
|
durationMs: durationMs,
|
||||||
|
localPath: storedPath,
|
||||||
|
contentUri: '',
|
||||||
|
folderPath: storageDir.path,
|
||||||
|
fileName: safeFileName,
|
||||||
|
size: storedFile.existsSync() ? storedFile.lengthSync() : file.size,
|
||||||
|
addedAt: DateTime.now().millisecondsSinceEpoch,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await player.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (songs.isEmpty) {
|
||||||
|
return const <SCMusicFolderMode>[];
|
||||||
|
}
|
||||||
|
songs.sort(
|
||||||
|
(a, b) => a.title.toLowerCase().compareTo(b.title.toLowerCase()),
|
||||||
|
);
|
||||||
|
final addedCount =
|
||||||
|
songs.where((song) {
|
||||||
|
return addedIds.contains(song.id) ||
|
||||||
|
addedPaths.contains(song.localPath);
|
||||||
|
}).length;
|
||||||
|
return <SCMusicFolderMode>[
|
||||||
|
SCMusicFolderMode(
|
||||||
|
id: 'ios_file_picker',
|
||||||
|
name: 'Selected Music',
|
||||||
|
path: 'Files',
|
||||||
|
songs: List<SCMusicMode>.unmodifiable(songs),
|
||||||
|
addedCount: addedCount,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
bool _isMp3(SongModel song) {
|
bool _isMp3(SongModel song) {
|
||||||
final extension = _songString(
|
final extension = _songString(
|
||||||
song,
|
song,
|
||||||
@ -108,6 +208,72 @@ class LocalMusicScanner {
|
|||||||
_songString(song, "_data").toLowerCase().endsWith(".mp3");
|
_songString(song, "_data").toLowerCase().endsWith(".mp3");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isMp3FileName(String fileName) {
|
||||||
|
return fileName.toLowerCase().endsWith('.mp3');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Directory> _iosRoomMusicDirectory() async {
|
||||||
|
final supportDir = await getApplicationSupportDirectory();
|
||||||
|
final directory = Directory(p.join(supportDir.path, 'room_music'));
|
||||||
|
if (!directory.existsSync()) {
|
||||||
|
await directory.create(recursive: true);
|
||||||
|
}
|
||||||
|
return directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> _copyIosPickedFile({
|
||||||
|
required String sourcePath,
|
||||||
|
required String fileName,
|
||||||
|
required String sourceId,
|
||||||
|
required Directory storageDir,
|
||||||
|
}) async {
|
||||||
|
if (p.isWithin(storageDir.path, sourcePath)) {
|
||||||
|
return sourcePath;
|
||||||
|
}
|
||||||
|
final sourceFile = File(sourcePath);
|
||||||
|
final targetFile = File(
|
||||||
|
p.join(storageDir.path, _iosStoredFileName(fileName, sourceId)),
|
||||||
|
);
|
||||||
|
if (targetFile.existsSync() &&
|
||||||
|
targetFile.lengthSync() == sourceFile.lengthSync()) {
|
||||||
|
return targetFile.path;
|
||||||
|
}
|
||||||
|
await sourceFile.copy(targetFile.path);
|
||||||
|
return targetFile.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _iosStoredFileName(String fileName, String sourceId) {
|
||||||
|
final baseName = p.basenameWithoutExtension(fileName).trim();
|
||||||
|
final safeBaseName =
|
||||||
|
baseName
|
||||||
|
.replaceAll(RegExp(r'[^A-Za-z0-9._ -]+'), '_')
|
||||||
|
.replaceAll(RegExp(r'\s+'), ' ')
|
||||||
|
.trim();
|
||||||
|
final token = base64Url.encode(utf8.encode(sourceId)).replaceAll('=', '');
|
||||||
|
final suffix = token.length > 12 ? token.substring(0, 12) : token;
|
||||||
|
return '${safeBaseName.isEmpty ? 'music' : safeBaseName}_$suffix.mp3';
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> _readLocalMp3DurationMs(AudioPlayer player, String path) async {
|
||||||
|
try {
|
||||||
|
final duration = await player.setFilePath(path);
|
||||||
|
await player.stop();
|
||||||
|
return duration?.inMilliseconds ?? 0;
|
||||||
|
} catch (_) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _firstNonEmpty(List<String?> values) {
|
||||||
|
for (final value in values) {
|
||||||
|
final text = value?.trim() ?? '';
|
||||||
|
if (text.isNotEmpty) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
bool _fileExists(String path) {
|
bool _fileExists(String path) {
|
||||||
try {
|
try {
|
||||||
return File(path).existsSync();
|
return File(path).existsSync();
|
||||||
|
|||||||