Compare commits
No commits in common. "main" and "test" have entirely different histories.
@ -244,7 +244,6 @@
|
||||
"reject": "رفض",
|
||||
"sendUser": "إرسال المستخدم",
|
||||
"bio": "معلومات شخصية",
|
||||
"introduction": "مقدمة",
|
||||
"delete": "حذف",
|
||||
"sendCoupontips": "هل أنت متأكد أنك تريد إرسال هذه القسيمة لهذا المستخدم؟",
|
||||
"recallThisMessage": "هل تريد استرجاع هذه الرسالة؟",
|
||||
@ -258,21 +257,6 @@
|
||||
"inviteYouToBecomeBD": "ندعوك للانضمام كـ BD.",
|
||||
"confirmAcceptTheInvitation": "هل تريد تأكيد قبول الدعوة؟",
|
||||
"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",
|
||||
"friends": "أصدقاء",
|
||||
"language": "اللغة",
|
||||
"feedback": "ملاحظات",
|
||||
@ -571,7 +555,6 @@
|
||||
"unFollow": "إلغاء المتابعة",
|
||||
"searchInputHint": "أدخل رقم الحساب / الغرفة",
|
||||
"kickRoomTips": "لقد تم طردك من الغرفة",
|
||||
"roomKickedEntryBlockedTips": "لقد أخرجك أحد المشرفين من الغرفة، ولا يمكنك الدخول مؤقتًا.",
|
||||
"joinRoomTips": "انضم إلى الغرفة!",
|
||||
"roomSetting": "إعدادات الغرفة",
|
||||
"roomDetails": "تفاصيل الغرفة",
|
||||
|
||||
@ -247,7 +247,6 @@
|
||||
"delete": "মুছে ফেলুন",
|
||||
"copy": "কপি করুন",
|
||||
"bio": "জীবনী",
|
||||
"introduction": "পরিচিতি",
|
||||
"useCoupontips": "আপনি কি কুপন ব্যবহার করতে চান?",
|
||||
"searchUserId": "ব্যবহারকারী ID সার্চ করুন",
|
||||
"sendUser": "ব্যবহারকারীকে পাঠান",
|
||||
@ -325,21 +324,6 @@
|
||||
"adminInviteRechargeAgent": "আপনাকে রিচার্জ এজেন্ট হতে আমন্ত্রণ জানাচ্ছি।",
|
||||
"confirmAcceptTheInvitation": "আপনি কি আমন্ত্রণ গ্রহণ করতে নিশ্চিত করছেন?",
|
||||
"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": "হোস্ট",
|
||||
"following": "ফলো করা হচ্ছে",
|
||||
"agent": "এজেন্ট",
|
||||
@ -501,7 +485,6 @@
|
||||
"customized": "কাস্টমাইজড",
|
||||
"searchInputHint": "অ্যাকাউন্ট/রুম নম্বর লিখুন",
|
||||
"kickRoomTips": "আপনাকে রুম থেকে বের করা হয়েছে।",
|
||||
"roomKickedEntryBlockedTips": "একজন অ্যাডমিন আপনাকে রুম থেকে সরিয়ে দিয়েছেন, আপাতত আপনি প্রবেশ করতে পারবেন না।",
|
||||
"joinRoomTips": "রুমে যোগদান করেছেন !",
|
||||
"roomSetting": "রুম সেটিংস",
|
||||
"roomDetails": "রুম বিবরণ",
|
||||
|
||||
@ -217,7 +217,6 @@
|
||||
"delete": "Delete",
|
||||
"copy": "Copy",
|
||||
"bio": "Bio",
|
||||
"introduction": "Introduction",
|
||||
"useCoupontips": "Are you sure you want to use the coupon?",
|
||||
"searchUserId": "Search user's ID",
|
||||
"sendUser": "Send User",
|
||||
@ -295,21 +294,6 @@
|
||||
"adminInviteRechargeAgent": "Invite you to become a Recharge agent.",
|
||||
"confirmAcceptTheInvitation": "Confirm to accept 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",
|
||||
"following": "Following",
|
||||
"agent": "Agency",
|
||||
@ -471,7 +455,6 @@
|
||||
"customized": "Customized",
|
||||
"searchInputHint": "Enter account/room number",
|
||||
"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!",
|
||||
"roomSetting": "Room Setting",
|
||||
"roomDetails": "Room Details",
|
||||
@ -575,7 +558,7 @@
|
||||
"openTheMic": "Open the mic",
|
||||
"muteTheMic": "Mute the mic",
|
||||
"unlockTheMic": "Unlock the mic",
|
||||
"leavelTheMic": "Leave mic",
|
||||
"leavelTheMic": "Leave the mic",
|
||||
"lockTheMic": "Lock the mic",
|
||||
"removeTheMic": "Remove the mic",
|
||||
"inviteToTheMicrophone": "Invite to the microphone",
|
||||
|
||||
@ -217,7 +217,6 @@
|
||||
"delete": "Supprimer",
|
||||
"copy": "Copier",
|
||||
"bio": "Biographie",
|
||||
"introduction": "Présentation",
|
||||
"useCoupontips": "Êtes-vous sûr de vouloir utiliser le coupon ?",
|
||||
"searchUserId": "Rechercher l'ID de l'utilisateur",
|
||||
"sendUser": "Envoyer l'utilisateur",
|
||||
@ -295,21 +294,6 @@
|
||||
"adminInviteRechargeAgent": "Vous invite à devenir agent Recharge.",
|
||||
"confirmAcceptTheInvitation": "Confirmer pour accepter 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",
|
||||
"following": "Suite",
|
||||
"agent": "Agence",
|
||||
@ -471,7 +455,6 @@
|
||||
"customized": "Personnalisé",
|
||||
"searchInputHint": "Entrez le numéro de compte/de chambre",
|
||||
"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 !",
|
||||
"roomSetting": "Cadre de la pièce",
|
||||
"roomDetails": "Détails de la chambre",
|
||||
|
||||
@ -217,7 +217,6 @@
|
||||
"delete": "Elimina",
|
||||
"copy": "Copia",
|
||||
"bio": "Bio",
|
||||
"introduction": "Introduzione",
|
||||
"useCoupontips": "Sei sicuro di voler utilizzare il coupon?",
|
||||
"searchUserId": "Cerca l'ID dell'utente",
|
||||
"sendUser": "Invia utente",
|
||||
@ -295,21 +294,6 @@
|
||||
"adminInviteRechargeAgent": "Invitati a diventare un agente Recharge.",
|
||||
"confirmAcceptTheInvitation": "Confermare per accettare 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",
|
||||
"following": "Segue",
|
||||
"agent": "Agenzia",
|
||||
@ -471,7 +455,6 @@
|
||||
"customized": "Personalizzato",
|
||||
"searchInputHint": "Inserisci il numero di conto/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!",
|
||||
"roomSetting": "Impostazione della stanza",
|
||||
"roomDetails": "Dettagli della camera",
|
||||
|
||||
@ -217,7 +217,6 @@
|
||||
"delete": "Excluir",
|
||||
"copy": "Copiar",
|
||||
"bio": "Biografia",
|
||||
"introduction": "Introdução",
|
||||
"useCoupontips": "Tem certeza que deseja usar o cupom?",
|
||||
"searchUserId": "Pesquisar ID do usuário",
|
||||
"sendUser": "Enviar usuário",
|
||||
@ -295,21 +294,6 @@
|
||||
"adminInviteRechargeAgent": "Convido você a se tornar um agente Recharge.",
|
||||
"confirmAcceptTheInvitation": "Confirmar para aceitar o 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",
|
||||
"following": "Seguindo",
|
||||
"agent": "Agência",
|
||||
@ -471,7 +455,6 @@
|
||||
"customized": "Personalizado",
|
||||
"searchInputHint": "Insira o número da conta/quarto",
|
||||
"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!",
|
||||
"roomSetting": "Configuração da sala",
|
||||
"roomDetails": "Detalhes do quarto",
|
||||
|
||||
@ -206,7 +206,6 @@
|
||||
"delete": "Sil",
|
||||
"copy": "Kopyala",
|
||||
"bio": "Biyografi",
|
||||
"introduction": "Tanıtım",
|
||||
"useCoupontips": "Kuponu kullanmak istediğinizden emin misiniz?",
|
||||
"searchUserId": "Kullanıcı ID'sini Ara",
|
||||
"sendUser": "Kullanıcıya Gönder",
|
||||
@ -294,21 +293,6 @@
|
||||
"adminInviteRechargeAgent": "Sizi Yükleme temsilcisi yapmaya davet ediyoruz.",
|
||||
"confirmAcceptTheInvitation": "Daveti kabul etmek için onaylıyor musunuz?",
|
||||
"confirmDeclineTheInvitation": "Daveti reddetmek için onaylıyor musunuz?",
|
||||
"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",
|
||||
"agent": "Temsilcilik",
|
||||
@ -470,7 +454,6 @@
|
||||
"customized": "Özel",
|
||||
"searchInputHint": "Hesap/oda numarasını girin",
|
||||
"kickRoomTips": "Odadan atıldınız.",
|
||||
"roomKickedEntryBlockedTips": "Bir yönetici tarafından odadan çıkarıldınız, şu anda giremezsiniz.",
|
||||
"joinRoomTips": "odaya katıldı !",
|
||||
"roomSetting": "Oda Ayarları",
|
||||
"roomDetails": "Oda Detayları",
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
<?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
|
||||
platform :ios, '13.0'
|
||||
# platform :ios, '13.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
@ -64,7 +64,7 @@ post_install do |installer|
|
||||
## 6. 禁用 Siri/助手 (NSSiriUsageDescription)
|
||||
'PERMISSION_ASSISTANT=0',
|
||||
|
||||
## 7. 启用 媒体库 (NSAppleMusicUsageDescription)
|
||||
## 7. 禁用 媒体库 (NSAppleMusicUsageDescription)
|
||||
'PERMISSION_MEDIA_LIBRARY=1',
|
||||
|
||||
## 8. 禁用 蓝牙 (如果你没用到蓝牙,建议也关掉)
|
||||
|
||||
@ -11,47 +11,11 @@ PODS:
|
||||
- GoogleUtilities/Environment (~> 8.0)
|
||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- audio_session (0.0.1):
|
||||
- Flutter
|
||||
- audioplayers_darwin (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- device_info_plus (0.0.1):
|
||||
- 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/CoreOnly
|
||||
- FirebaseAuth (~> 11.15.0)
|
||||
@ -177,9 +141,6 @@ PODS:
|
||||
- in_app_purchase_storekit (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- just_audio (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- libpag (4.5.52)
|
||||
- libwebp (1.5.0):
|
||||
- libwebp/demux (= 1.5.0)
|
||||
@ -209,9 +170,6 @@ PODS:
|
||||
- pag (0.0.1):
|
||||
- Flutter
|
||||
- libpag
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- PromisesObjC (2.4.0)
|
||||
@ -233,7 +191,6 @@ PODS:
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- SwiftyBeaver (1.9.5)
|
||||
- SwiftyGif (5.4.5)
|
||||
- tancent_vap (0.0.1):
|
||||
- Flutter
|
||||
- tencent_cloud_chat_sdk (8.0.0):
|
||||
@ -266,10 +223,8 @@ PODS:
|
||||
|
||||
DEPENDENCIES:
|
||||
- app_links (from `.symlinks/plugins/app_links/ios`)
|
||||
- audio_session (from `.symlinks/plugins/audio_session/ios`)
|
||||
- audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/darwin`)
|
||||
- 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_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||
- firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`)
|
||||
@ -280,11 +235,9 @@ DEPENDENCIES:
|
||||
- image_cropper (from `.symlinks/plugins/image_cropper/ios`)
|
||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||
- 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`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/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`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`)
|
||||
@ -302,8 +255,6 @@ SPEC REPOS:
|
||||
trunk:
|
||||
- AppAuth
|
||||
- AppCheckCore
|
||||
- DKImagePickerController
|
||||
- DKPhotoGallery
|
||||
- Firebase
|
||||
- FirebaseAppCheckInterop
|
||||
- FirebaseAuth
|
||||
@ -331,7 +282,6 @@ SPEC REPOS:
|
||||
- SDWebImage
|
||||
- SDWebImageWebPCoder
|
||||
- SwiftyBeaver
|
||||
- SwiftyGif
|
||||
- TOCropViewController
|
||||
- TXCustomBeautyProcesserPlugin
|
||||
- TXIMSDK_Plus_iOS_XCFramework
|
||||
@ -340,14 +290,10 @@ SPEC REPOS:
|
||||
EXTERNAL SOURCES:
|
||||
app_links:
|
||||
:path: ".symlinks/plugins/app_links/ios"
|
||||
audio_session:
|
||||
:path: ".symlinks/plugins/audio_session/ios"
|
||||
audioplayers_darwin:
|
||||
:path: ".symlinks/plugins/audioplayers_darwin/darwin"
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
file_picker:
|
||||
:path: ".symlinks/plugins/file_picker/ios"
|
||||
firebase_auth:
|
||||
:path: ".symlinks/plugins/firebase_auth/ios"
|
||||
firebase_core:
|
||||
@ -368,16 +314,12 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||
in_app_purchase_storekit:
|
||||
:path: ".symlinks/plugins/in_app_purchase_storekit/darwin"
|
||||
just_audio:
|
||||
:path: ".symlinks/plugins/just_audio/darwin"
|
||||
on_audio_query_ios:
|
||||
:path: ".symlinks/plugins/on_audio_query_ios/ios"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
pag:
|
||||
:path: ".symlinks/plugins/pag/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
shared_preferences_foundation:
|
||||
@ -407,12 +349,8 @@ SPEC CHECKSUMS:
|
||||
app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a
|
||||
AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73
|
||||
AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
|
||||
audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
|
||||
audioplayers_darwin: 835ced6edd4c9fc8ebb0a7cc9e294a91d99917d5
|
||||
device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896
|
||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
|
||||
firebase_auth: 50af8366c87bb88c80ebeae62eb60189c7246b9b
|
||||
firebase_core: 995454a784ff288be5689b796deb9e9fa3601818
|
||||
@ -440,7 +378,6 @@ SPEC CHECKSUMS:
|
||||
image_cropper: 655b3ba703c9e15e3111e79151624d6154288774
|
||||
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
|
||||
in_app_purchase_storekit: 22cca7d08eebca9babdf4d07d0baccb73325d3c8
|
||||
just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed
|
||||
libpag: 98742fad4b3ac2a2ee31e383d2483495fb9a5fb4
|
||||
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
|
||||
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
|
||||
@ -448,7 +385,6 @@ SPEC CHECKSUMS:
|
||||
on_audio_query_ios: 28a780e2d0d85d92d500ba6e12c6c8167022b2fa
|
||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||
pag: f01aa9017ab0e04a83ba4a20d6070a50f0ac9da8
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
|
||||
@ -459,7 +395,6 @@ SPEC CHECKSUMS:
|
||||
sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
SwiftyBeaver: 84069991dd5dca07d7069100985badaca7f0ce82
|
||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||
tancent_vap: fa8ad93814a9f950514a7074c662d2c937084c68
|
||||
tencent_cloud_chat_sdk: 55e5fffe20f6b7937a26a674ccccb639563a9790
|
||||
tencent_rtc_sdk: f77558b6b436a149d378557c2a837f73c09061bc
|
||||
@ -468,11 +403,11 @@ SPEC CHECKSUMS:
|
||||
TXIMSDK_Plus_iOS_XCFramework: 5d1933192fb3b7ef2fe933f1623de4a0486a7fe2
|
||||
TXLiteAVSDK_Professional: e792e6084a208ecef6196c5f28237ad8078b4d7c
|
||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||
video_player_avfoundation: 3453f792138786248960ca029747fcd9f318ef52
|
||||
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
|
||||
video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140
|
||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||
webview_flutter_wkwebview: 8ebf4fded22593026f7dbff1fbff31ea98573c8d
|
||||
|
||||
PODFILE CHECKSUM: b724feb0f01aec7228c34cc4763485e911aa9322
|
||||
PODFILE CHECKSUM: a6f49a93e5f85201a2efdadcd7cf184b0e310894
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@ -18,7 +18,6 @@
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
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 */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -71,7 +70,6 @@
|
||||
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>"; };
|
||||
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; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@ -172,7 +170,6 @@
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||
B7A1D58F2EE51A0000000002 /* GiftMp4VideoPlatformView.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
);
|
||||
path = Runner;
|
||||
@ -213,7 +210,6 @@
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
F1AFD9E9E8DBA4F1609CE22F /* [CP] Embed Pods Frameworks */,
|
||||
5DF67264ADC5268AC3021CED /* [CP] Copy Pods Resources */,
|
||||
B7A1D5902EE51A0000000003 /* Upload Crashlytics Symbols */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -356,22 +352,6 @@
|
||||
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;
|
||||
};
|
||||
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 */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@ -427,7 +407,6 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||
B7A1D58E2EE51A0000000001 /* GiftMp4VideoPlatformView.swift in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -510,7 +489,7 @@
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Profile;
|
||||
@ -519,13 +498,12 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_APPat_icon_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 102;
|
||||
DEVELOPMENT_TEAM = S9YG87C297;
|
||||
CURRENT_PROJECT_VERSION = 140;
|
||||
DEVELOPMENT_TEAM = S9X2AJ2US9;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
@ -533,7 +511,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
MARKETING_VERSION = 1.4.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -647,7 +625,7 @@
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@ -701,7 +679,7 @@
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
@ -710,13 +688,13 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_APPat_icon_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 102;
|
||||
DEVELOPMENT_TEAM = S9YG87C297;
|
||||
CURRENT_PROJECT_VERSION = 140;
|
||||
DEVELOPMENT_TEAM = F33K8VUZ62;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
@ -724,7 +702,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
MARKETING_VERSION = 1.4.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -739,13 +717,13 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_APPat_icon_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 102;
|
||||
DEVELOPMENT_TEAM = S9YG87C297;
|
||||
CURRENT_PROJECT_VERSION = 140;
|
||||
DEVELOPMENT_TEAM = F33K8VUZ62;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
@ -753,7 +731,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
MARKETING_VERSION = 1.4.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
||||
@ -9,23 +9,11 @@ import UIKit
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
registerGiftMp4VideoView()
|
||||
registerDurableAuthStorageChannel()
|
||||
registerMobileContextChannel()
|
||||
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() {
|
||||
guard let controller = window?.rootViewController as? FlutterViewController else {
|
||||
return
|
||||
|
||||
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 2.1 MiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 67 KiB |
@ -1,23 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 187 KiB |
|
Before Width: | Height: | Size: 811 KiB |
|
Before Width: | Height: | Size: 371 KiB |
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 811 KiB After Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 371 KiB After Width: | Height: | Size: 68 B |
@ -1,23 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 108 KiB |
@ -16,23 +16,13 @@
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleAspectFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="BGD-k0-Ey4">
|
||||
</imageView>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleAspectFit" image="LaunchLogo" translatesAutoresizingMaskIntoConstraints="NO" id="LOG-k0-Ey4">
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="107" id="LOG-width-107"/>
|
||||
<constraint firstAttribute="height" constant="97" id="LOG-height-97"/>
|
||||
</constraints>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0" green="0" blue="0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" 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"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
@ -42,7 +32,6 @@
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchBackground" width="375" height="812"/>
|
||||
<image name="LaunchLogo" width="107" height="97"/>
|
||||
<image name="LaunchImage" width="168" height="185"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@ -38,22 +38,8 @@
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>com.googleusercontent.apps.52796773508-dk441c9e7tdmqv47mbdt2vpbn2clrii8</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>com.googleusercontent.apps.52796773508-dk441c9e7tdmqv47mbdt2vpbn2clrii8</string>
|
||||
</array>
|
||||
</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>
|
||||
@ -64,8 +50,6 @@
|
||||
<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>
|
||||
<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>NSAppleMusicUsageDescription</key>
|
||||
<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>
|
||||
@ -93,5 +77,9 @@
|
||||
<array>
|
||||
<string>audio</string>
|
||||
</array>
|
||||
<key>com.apple.developer.applesignin</key>
|
||||
<array>
|
||||
<string>Default</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -6,9 +6,5 @@
|
||||
<array>
|
||||
<string>Default</string>
|
||||
</array>
|
||||
<key>com.apple.developer.associated-domains</key>
|
||||
<array>
|
||||
<string>applinks:h5.yumi.com</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -6,9 +6,5 @@
|
||||
<array>
|
||||
<string>Default</string>
|
||||
</array>
|
||||
<key>com.apple.developer.associated-domains</key>
|
||||
<array>
|
||||
<string>applinks:h5.yumi.com</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -16,8 +16,8 @@ class SCVariant1Config implements AppConfig {
|
||||
@override
|
||||
String get apiHost => const String.fromEnvironment(
|
||||
'API_HOST',
|
||||
defaultValue: 'http://192.168.110.64:1100/',
|
||||
); // 默认连本地接口,本地调试可通过 --dart-define=API_HOST 覆盖,线上“https://jvapi.haiyihy.com/”
|
||||
defaultValue: 'http://192.168.110.66:1100/',
|
||||
); // 默认连线上环境,本地调试可通过 --dart-define=API_HOST 覆盖 本地“http://192.168.110.66:1100/”,线上“https://jvapi.haiyihy.com/”
|
||||
|
||||
@override
|
||||
String get imgHost => 'https://img.atuchat.com/'; // 测试图片服务器,上架前需替换为正式域名
|
||||
|
||||
@ -168,7 +168,8 @@ class SCGlobalConfig {
|
||||
}
|
||||
|
||||
static String clampFloatingBroadcastScope(String value) {
|
||||
return normalizeFloatingBroadcastScope(value);
|
||||
final scope = normalizeFloatingBroadcastScope(value);
|
||||
return _isLowPerformanceDevice ? floatingBroadcastScopeOff : scope;
|
||||
}
|
||||
|
||||
static String floatingBroadcastScopeStorageKey(String? account) {
|
||||
@ -187,7 +188,8 @@ class SCGlobalConfig {
|
||||
|
||||
static bool get isLowPerformanceDevice => _isLowPerformanceDevice;
|
||||
|
||||
static bool get allowsHighCostAnimations => true;
|
||||
static bool get allowsHighCostAnimations =>
|
||||
!_isLowPerformanceDevice && sdkInt > maxSdkNoAnim;
|
||||
|
||||
static int get recommendedImageCacheBytes =>
|
||||
_isLowPerformanceDevice ? 40 * 1024 * 1024 : 100 * 1024 * 1024;
|
||||
@ -209,17 +211,19 @@ class SCGlobalConfig {
|
||||
isLowRamDevice ||
|
||||
processorCount <= 6 ||
|
||||
matchesKnownLowPerformanceModel;
|
||||
resetVisualEffectSwitchesToRecommendedDefaults();
|
||||
}
|
||||
|
||||
static bool clampVisualEffectPreference(bool enabled) {
|
||||
return enabled;
|
||||
return !_isLowPerformanceDevice && enabled;
|
||||
}
|
||||
|
||||
static void resetVisualEffectSwitchesToRecommendedDefaults() {
|
||||
isEntryVehicleAnimation = true;
|
||||
isGiftSpecialEffects = true;
|
||||
isFloatingAnimationInGlobal = true;
|
||||
isLuckGiftSpecialEffects = true;
|
||||
final enabled = !_isLowPerformanceDevice;
|
||||
isEntryVehicleAnimation = enabled;
|
||||
isGiftSpecialEffects = enabled;
|
||||
isFloatingAnimationInGlobal = enabled;
|
||||
isLuckGiftSpecialEffects = enabled;
|
||||
}
|
||||
|
||||
/// 获取当前业务逻辑策略
|
||||
|
||||
@ -3,7 +3,6 @@ class SCRoomMsgType {
|
||||
static const String image = "IMAGE";
|
||||
static const String joinRoom = "JOIN_ROOM";
|
||||
static const String systemTips = "SYSTEM_TIPS";
|
||||
static const String cpSystemNotice = "CP_SYSTEM_NOTICE";
|
||||
|
||||
///表情包
|
||||
static const String emoticons = "EMOTICONS";
|
||||
|
||||
@ -890,8 +890,6 @@ class SCAppLocalizations {
|
||||
|
||||
String get bio => translate('bio');
|
||||
|
||||
String get introduction => translate('introduction');
|
||||
|
||||
String get hobby => translate('hobby');
|
||||
|
||||
String get search => translate('search');
|
||||
@ -997,47 +995,6 @@ class SCAppLocalizations {
|
||||
String get 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 chats => translate('chats');
|
||||
@ -1595,9 +1552,6 @@ class SCAppLocalizations {
|
||||
|
||||
String get kickRoomTips => translate('kickRoomTips');
|
||||
|
||||
String get roomKickedEntryBlockedTips =>
|
||||
translate('roomKickedEntryBlockedTips');
|
||||
|
||||
String get joinRoomTips => translate('joinRoomTips');
|
||||
|
||||
String get youAreCurrentlyCPRelationshipPleaseDissolve =>
|
||||
|
||||
@ -1,75 +0,0 @@
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,173 +0,0 @@
|
||||
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() {}
|
||||
}
|
||||
@ -1,164 +0,0 @@
|
||||
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(
|
||||
daysValue: '999',
|
||||
daysLabel: 'Days',
|
||||
levelText: 'Lv.0',
|
||||
levelText: 'Lv.1 Simple Love',
|
||||
currentValue: 1,
|
||||
targetValue: 1,
|
||||
expAwayText: '1,000',
|
||||
|
||||
@ -1,113 +0,0 @@
|
||||
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"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,103 +0,0 @@
|
||||
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,7 +6,6 @@ import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
||||
import 'package:fluro/fluro.dart' as fluro;
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@ -45,6 +44,8 @@ import 'services/shop/shop_manager.dart';
|
||||
import 'services/theme/theme_manager.dart';
|
||||
import 'services/auth/user_profile_manager.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;
|
||||
|
||||
@ -277,8 +278,6 @@ class RootAppWithProviders extends StatelessWidget {
|
||||
|
||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
|
||||
final RouteObserver<ModalRoute<dynamic>> modalRouteObserver =
|
||||
RouteObserver<ModalRoute<dynamic>>();
|
||||
|
||||
class YumiApplication extends StatefulWidget {
|
||||
const YumiApplication({super.key});
|
||||
@ -300,7 +299,6 @@ class _YumiApplicationState extends State<YumiApplication>
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_initRouter();
|
||||
unawaited(_initLink());
|
||||
unawaited(_reconcilePendingGooglePurchases(force: true));
|
||||
});
|
||||
}
|
||||
|
||||
@ -327,24 +325,6 @@ class _YumiApplicationState extends State<YumiApplication>
|
||||
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
|
||||
@ -447,11 +427,46 @@ class _YumiApplicationState extends State<YumiApplication>
|
||||
builder: (context, child) {
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [child ?? const SizedBox.shrink()],
|
||||
children: [
|
||||
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",
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
navigatorObservers: [routeObserver, modalRouteObserver],
|
||||
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],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -106,7 +106,7 @@ class _SCEditProfilePageState extends State<SCEditProfilePage> {
|
||||
if (success) {
|
||||
userProvider?.updateUserAvatar(url);
|
||||
}
|
||||
}, allowGif: true);
|
||||
});
|
||||
},
|
||||
),
|
||||
SizedBox(height: 15.w),
|
||||
@ -559,6 +559,24 @@ class _SCEditProfilePageState extends State<SCEditProfilePage> {
|
||||
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) {
|
||||
if (primary != null && primary != 0) {
|
||||
return primary;
|
||||
@ -643,6 +661,10 @@ class _SCEditProfilePageState extends State<SCEditProfilePage> {
|
||||
if (submittedProfile != null) {
|
||||
final mergedProfile = (user.userProfile ?? SocialChatUserProfile())
|
||||
.copyWith(
|
||||
userAvatar: _preferUsableAvatar(
|
||||
user.userProfile?.userAvatar,
|
||||
submittedProfile.userAvatar,
|
||||
),
|
||||
userNickname: _preferNonEmpty(
|
||||
user.userProfile?.userNickname,
|
||||
submittedProfile.userNickname,
|
||||
|
||||
@ -16,26 +16,25 @@ 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/widgets/msg/message_conversation_list_page.dart';
|
||||
|
||||
import '../../index/main_route.dart';
|
||||
import '../chat_route.dart';
|
||||
|
||||
///消息
|
||||
class SCMessagePage extends StatefulWidget {
|
||||
final bool isFromRoom;
|
||||
bool isFromRoom = false;
|
||||
|
||||
const SCMessagePage({super.key, this.isFromRoom = false});
|
||||
SCMessagePage({this.isFromRoom = false});
|
||||
|
||||
@override
|
||||
State<SCMessagePage> createState() => _MessagePageState();
|
||||
_MessagePageState createState() => _MessagePageState(isFromRoom);
|
||||
}
|
||||
|
||||
class _MessagePageState extends State<SCMessagePage> {
|
||||
BusinessLogicStrategy get _strategy => SCGlobalConfig.businessLogicStrategy;
|
||||
bool isFromRoom = false;
|
||||
bool showSystemAnnouncementTips = true;
|
||||
final TextEditingController _messageSearchController =
|
||||
TextEditingController();
|
||||
final FocusNode _messageSearchFocusNode = FocusNode();
|
||||
bool _isSearchingMessages = false;
|
||||
String _messageSearchText = "";
|
||||
|
||||
_MessagePageState(this.isFromRoom);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -85,8 +84,6 @@ class _MessagePageState extends State<SCMessagePage> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_messageSearchController.dispose();
|
||||
_messageSearchFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@ -108,18 +105,12 @@ class _MessagePageState extends State<SCMessagePage> {
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
leading: null,
|
||||
title:
|
||||
_isSearchingMessages
|
||||
? _messageSearchField()
|
||||
: TabBar(
|
||||
title: TabBar(
|
||||
tabs:
|
||||
[SCAppLocalizations.of(context)!.message]
|
||||
.map(
|
||||
(e) => Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: 3.w,
|
||||
top: 4.w,
|
||||
),
|
||||
padding: EdgeInsets.only(bottom: 3.w, top: 4.w),
|
||||
child: Text(e),
|
||||
),
|
||||
)
|
||||
@ -145,22 +136,15 @@ class _MessagePageState extends State<SCMessagePage> {
|
||||
actions: [
|
||||
SCDebounceWidget(
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.only(start: 10.w, end: 10.w),
|
||||
child:
|
||||
_isSearchingMessages
|
||||
? Icon(Icons.close, size: 22.w, color: Colors.white)
|
||||
: Image.asset(
|
||||
padding: EdgeInsetsDirectional.only(end: 10.w),
|
||||
child: Image.asset(
|
||||
"sc_images/index/sc_icon_serach.png",
|
||||
width: 20.w,
|
||||
height: 20.w,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (_isSearchingMessages) {
|
||||
_stopMessageSearch();
|
||||
} else {
|
||||
_startMessageSearch();
|
||||
}
|
||||
SCNavigatorUtils.push(context, SCMainRoute.mainSearch);
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -181,7 +165,7 @@ class _MessagePageState extends State<SCMessagePage> {
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
if (!_isSearchingMessages) _headMenu(provider),
|
||||
_headMenu(provider),
|
||||
Expanded(
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(
|
||||
@ -189,10 +173,7 @@ class _MessagePageState extends State<SCMessagePage> {
|
||||
left: 8.w,
|
||||
right: 8.w,
|
||||
),
|
||||
child: MessageConversationListPage(
|
||||
false,
|
||||
searchQuery: _messageSearchText,
|
||||
),
|
||||
child: MessageConversationListPage(false),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -208,76 +189,9 @@ 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) {
|
||||
return Container(
|
||||
margin: EdgeInsetsDirectional.symmetric(
|
||||
horizontal: 20.w,
|
||||
).copyWith(top: 10.w),
|
||||
margin: EdgeInsetsDirectional.symmetric(horizontal: 25.w,).copyWith(top: 10.w),
|
||||
child: Column(
|
||||
spacing: 15.w,
|
||||
children: [
|
||||
@ -351,7 +265,9 @@ class _MessagePageState extends State<SCMessagePage> {
|
||||
},
|
||||
),
|
||||
SCDebounceWidget(
|
||||
child: Row(
|
||||
child: Stack(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(height: 3.w),
|
||||
Image.asset(
|
||||
@ -359,9 +275,8 @@ class _MessagePageState extends State<SCMessagePage> {
|
||||
width: 45.w,
|
||||
height: 45.w,
|
||||
),
|
||||
SizedBox(width: 6.w),
|
||||
Expanded(
|
||||
child: Column(
|
||||
SizedBox(width: 5.w),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
text(
|
||||
@ -371,21 +286,41 @@ class _MessagePageState extends State<SCMessagePage> {
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
SizedBox(height: 3.w),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: text(
|
||||
text(
|
||||
"[${SCAppLocalizations.of(context)!.newMessage}]",
|
||||
fontSize: 12.sp,
|
||||
textColor: Colors.white54,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5.w),
|
||||
_unreadBadge(provider.systemUnReadCount),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
PositionedDirectional(
|
||||
bottom: 0,
|
||||
end: 10.w,
|
||||
child: Visibility(
|
||||
visible: provider.systemUnReadCount > 0,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6.w),
|
||||
alignment: Alignment.center,
|
||||
height: 16.w,
|
||||
decoration: BoxDecoration(
|
||||
//gradient: LinearGradient(colors: [Color(0xffA447FF), Color(0xff623CE9)]),
|
||||
color: Colors.red,
|
||||
shape: BoxShape.circle,
|
||||
//borderRadius: BorderRadius.all(Radius.circular(7.w)),
|
||||
),
|
||||
child: Text(
|
||||
"${provider.systemUnReadCount > 99 ? '99+' : provider.systemUnReadCount}",
|
||||
style: TextStyle(
|
||||
fontSize: 10.sp,
|
||||
color: Color(0xffffffff),
|
||||
fontWeight: FontWeight.w400,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -402,6 +337,10 @@ class _MessagePageState extends State<SCMessagePage> {
|
||||
},
|
||||
),
|
||||
SCDebounceWidget(
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(height: 3.w),
|
||||
@ -410,9 +349,8 @@ class _MessagePageState extends State<SCMessagePage> {
|
||||
width: 45.w,
|
||||
height: 45.w,
|
||||
),
|
||||
SizedBox(width: 6.w),
|
||||
Expanded(
|
||||
child: Column(
|
||||
SizedBox(width: 5.w),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
text(
|
||||
@ -422,20 +360,41 @@ class _MessagePageState extends State<SCMessagePage> {
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
SizedBox(height: 3.w),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: text(
|
||||
text(
|
||||
"[${SCAppLocalizations.of(context)!.newMessage}]",
|
||||
fontSize: 12.sp,
|
||||
textColor: Colors.white54,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5.w),
|
||||
_unreadBadge(provider.notifcationUnReadCount),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
PositionedDirectional(
|
||||
bottom: 0,
|
||||
end: 10.w,
|
||||
child: Visibility(
|
||||
visible: provider.notifcationUnReadCount > 0,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6.w),
|
||||
alignment: Alignment.center,
|
||||
height: 16.w,
|
||||
decoration: BoxDecoration(
|
||||
//gradient: LinearGradient(colors: [Color(0xffA447FF), Color(0xff623CE9)]),
|
||||
color: Colors.red,
|
||||
shape: BoxShape.circle,
|
||||
//borderRadius: BorderRadius.all(Radius.circular(7.w)),
|
||||
),
|
||||
child: Text(
|
||||
"${provider.notifcationUnReadCount > 99 ? '99+' : provider.notifcationUnReadCount}",
|
||||
style: TextStyle(
|
||||
fontSize: 10.sp,
|
||||
color: Color(0xffffffff),
|
||||
fontWeight: FontWeight.w400,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -453,25 +412,4 @@ 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,928 +0,0 @@
|
||||
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/repositories/sc_room_repository_imp.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:provider/provider.dart';
|
||||
import 'package:yumi/app/constants/sc_room_msg_type.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/data_sources/sources/local/floating_screen_manager.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/rtm_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/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/cp_rights/cp_rights_guide_page.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/message/sc_floating_message.dart';
|
||||
import '../../shared/business_logic/usecases/sc_fixed_width_tabIndicator.dart';
|
||||
@ -52,18 +52,14 @@ class _GiftPageTabItem {
|
||||
|
||||
class GiftPage extends StatefulWidget {
|
||||
final SocialChatUserProfile? toUser;
|
||||
final String? initialTabType;
|
||||
|
||||
const GiftPage({super.key, this.toUser, this.initialTabType});
|
||||
const GiftPage({super.key, this.toUser});
|
||||
|
||||
@override
|
||||
State<GiftPage> createState() => _GiftPageState();
|
||||
}
|
||||
|
||||
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 Duration _comboSendBatchWindow = Duration(milliseconds: 200);
|
||||
|
||||
@ -79,7 +75,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
|
||||
TabController? _tabController;
|
||||
List<String> _tabTypes = <String>[];
|
||||
bool _didApplyInitialTab = false;
|
||||
|
||||
/// 业务逻辑策略访问器
|
||||
BusinessLogicStrategy get _strategy => SCGlobalConfig.businessLogicStrategy;
|
||||
@ -208,9 +203,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
number = 1;
|
||||
noShowNumber = false;
|
||||
giftType = _giftTypeFromTab(gift?.giftTab);
|
||||
if (_isCpGift(gift)) {
|
||||
_normalizeCpRecipientSelection();
|
||||
}
|
||||
if (notify) {
|
||||
setState(() {});
|
||||
}
|
||||
@ -228,7 +220,7 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
Provider.of<SCAppGeneralManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).giftList(includeCustomized: true);
|
||||
).giftList(includeCustomized: false);
|
||||
Provider.of<SCAppGeneralManager>(context, listen: false).giftActivityList();
|
||||
unawaited(
|
||||
Provider.of<SCAppGeneralManager>(
|
||||
@ -268,53 +260,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
}
|
||||
final ref = Provider.of<SCAppGeneralManager>(context, listen: false);
|
||||
_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(
|
||||
@ -325,6 +270,7 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
final availableTypes =
|
||||
ref.giftByTab.entries
|
||||
.where((entry) => entry.value.isNotEmpty)
|
||||
.where((entry) => entry.key != "CUSTOMIZED")
|
||||
.where((entry) => entry.key != _backpackGiftTab)
|
||||
.map((entry) => entry.key)
|
||||
.toList();
|
||||
@ -366,7 +312,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
) {
|
||||
final nextTypes = tabs.map((tab) => tab.type).toList();
|
||||
if (_tabController != null && listEquals(_tabTypes, nextTypes)) {
|
||||
_applyInitialTabIfNeeded(nextTypes);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -376,20 +321,7 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
_tabController!.index < _tabTypes.length)
|
||||
? _tabTypes[_tabController!.index]
|
||||
: null;
|
||||
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;
|
||||
}
|
||||
final nextIndex = currentType != null ? nextTypes.indexOf(currentType) : 0;
|
||||
_tabController?.removeListener(_handleTabChanged);
|
||||
_tabController?.dispose();
|
||||
_tabTypes = nextTypes;
|
||||
@ -405,27 +337,6 @@ 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(
|
||||
SCAppGeneralManager ref,
|
||||
String tabType, {
|
||||
@ -440,17 +351,11 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
void _ensureCurrentTabSelection(SCAppGeneralManager ref, String tabType) {
|
||||
final gifts = ref.giftByTab[tabType] ?? const <SocialChatGiftRes>[];
|
||||
if (_matchesCurrentGift(tabType, gifts)) {
|
||||
if (_isCpGiftTab(tabType)) {
|
||||
_normalizeCpRecipientSelection();
|
||||
}
|
||||
return;
|
||||
}
|
||||
final gift = _resolveSelectedGift(tabType, gifts);
|
||||
_selectedGiftByTab[tabType] = gift;
|
||||
_applyGiftSelection(gift, notify: false);
|
||||
if (_isCpGiftTab(tabType)) {
|
||||
_normalizeCpRecipientSelection();
|
||||
}
|
||||
}
|
||||
|
||||
SocialChatGiftRes? _resolveSelectedGift(
|
||||
@ -637,19 +542,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
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) {
|
||||
final values = <String>[];
|
||||
void addValue(dynamic value) {
|
||||
@ -692,34 +584,89 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
if (tabController == null) {
|
||||
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);
|
||||
final isCpRecipientSingleMode = isCpTab || _isCpGift(checkedGift);
|
||||
return SafeArea(
|
||||
top: false,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
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(),
|
||||
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(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(12.0),
|
||||
@ -740,14 +687,15 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
widget.toUser == null
|
||||
? Builder(
|
||||
builder: (ct) {
|
||||
return Opacity(
|
||||
opacity:
|
||||
isCpRecipientSingleMode ? 0.45 : 1,
|
||||
child: GestureDetector(
|
||||
onTap:
|
||||
isCpRecipientSingleMode
|
||||
? null
|
||||
: () {
|
||||
return GestureDetector(
|
||||
child: Image.asset(
|
||||
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;
|
||||
@ -755,14 +703,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
@ -867,7 +807,14 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
children: [
|
||||
SizedBox(width: 15.w),
|
||||
GestureDetector(
|
||||
onTap: _openRoomRechargeBottomSheet,
|
||||
onTap: () {
|
||||
SmartDialog.dismiss(tag: "showGiftControl");
|
||||
SCNavigatorUtils.push(
|
||||
navigatorKey.currentState!.context,
|
||||
WalletRoute.recharge,
|
||||
replace: false,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 8.w,
|
||||
@ -1095,17 +1042,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
// 是否选中
|
||||
return GestureDetector(
|
||||
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;
|
||||
for (var mai in listMai) {
|
||||
if (shead.mic?.user?.id == mai.mic?.user?.id) {
|
||||
@ -1207,40 +1143,18 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
|
||||
///选中所有在座位上的用户
|
||||
void selecteAllMicUsers() {
|
||||
if (_isCpRecipientSingleMode) {
|
||||
return;
|
||||
}
|
||||
for (var mai in listMai) {
|
||||
mai.isSelect = true;
|
||||
}
|
||||
}
|
||||
|
||||
_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<MicRes> acceptUsers = [];
|
||||
|
||||
if (widget.toUser != null) {
|
||||
acceptUserIds.add(widget.toUser?.id ?? "");
|
||||
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 {
|
||||
///所有在线
|
||||
if (giveType == 2) {
|
||||
@ -1269,6 +1183,15 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
SCTts.show(SCAppLocalizations.of(context)!.pleaseSelectTheRecipient);
|
||||
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 roomId = rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "";
|
||||
final roomAccount =
|
||||
@ -1549,7 +1472,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
'roomId=${request.roomId} '
|
||||
'elapsedMs=${stopwatch.elapsedMilliseconds}',
|
||||
);
|
||||
_showLocalCpInviteWaitingAfterGift(request, sentUsers);
|
||||
if (sentUsers.isNotEmpty) {
|
||||
final giftBatchId = _createGiftBatchId(request);
|
||||
sendGiftMsg(
|
||||
@ -1561,7 +1483,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
);
|
||||
}
|
||||
await _refreshGiftBackpack();
|
||||
_refreshCpStateAfterGift(request, profileManager);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1573,7 +1494,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
request.quantity,
|
||||
false,
|
||||
roomId: request.roomId,
|
||||
relationType: request.cpRelationType,
|
||||
)
|
||||
: await repository.giveGift(
|
||||
request.acceptUserIds,
|
||||
@ -1581,7 +1501,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
request.quantity,
|
||||
false,
|
||||
roomId: request.roomId,
|
||||
relationType: request.cpRelationType,
|
||||
);
|
||||
_giftFxLog(
|
||||
'$requestName success trigger=$trigger '
|
||||
@ -1598,7 +1517,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
'balance=$result '
|
||||
'elapsedMs=${stopwatch.elapsedMilliseconds}',
|
||||
);
|
||||
_showLocalCpInviteWaitingAfterGift(request, request.acceptUsers);
|
||||
if (request.isLuckyGiftRequest) {
|
||||
final giftBatchId = _createGiftBatchId(request);
|
||||
_showLocalLuckyGiftFeedback(
|
||||
@ -1626,7 +1544,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
);
|
||||
}
|
||||
profileManager?.updateBalance(result);
|
||||
_refreshCpStateAfterGift(request, profileManager);
|
||||
} catch (e) {
|
||||
final errorMessage =
|
||||
request.isBackpackGiftRequest && _isBackpackQuantityError(e)
|
||||
@ -1647,83 +1564,10 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
if (request.isBackpackGiftRequest) {
|
||||
unawaited(_refreshGiftBackpack());
|
||||
}
|
||||
if (!request.isBackpackGiftRequest &&
|
||||
_isInsufficientMoneyError(e, errorMessage)) {
|
||||
RoomGiftComboSendController().hide();
|
||||
RoomRechargeBottomSheet.show(
|
||||
navigatorKey.currentState?.context ?? context,
|
||||
);
|
||||
return;
|
||||
}
|
||||
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(
|
||||
SCChatRoomRepository repository,
|
||||
_GiftSendRequest request,
|
||||
@ -1745,7 +1589,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
roomId: request.roomId,
|
||||
seatIndex:
|
||||
acceptUser?.micIndex ?? _resolveSeatIndexForUserId(acceptUserId),
|
||||
relationType: request.cpRelationType,
|
||||
);
|
||||
if (acceptUser != null) {
|
||||
sentUsers.add(acceptUser);
|
||||
@ -1856,7 +1699,7 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
);
|
||||
}
|
||||
num coins = (gift.giftCandy ?? 0) * quantity;
|
||||
if (coins > 9999 && !_isCpGift(gift)) {
|
||||
if (coins > 9999) {
|
||||
var fMsg = SCFloatingMessage(
|
||||
type: 1,
|
||||
userId: AccountStorage().getCurrentUser()?.userProfile?.id ?? "",
|
||||
@ -1870,7 +1713,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
toUserAvatarUrl: u.user?.userAvatar ?? "",
|
||||
giftUrl: gift.giftPhoto ?? "",
|
||||
giftId: gift.id,
|
||||
giftTab: _floatingGiftTabFor(gift),
|
||||
roomId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||||
coins: coins,
|
||||
number: quantity,
|
||||
@ -1899,6 +1741,14 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
'animationCount=$animationCount',
|
||||
);
|
||||
SCGiftVapSvgaManager().play(gift.giftSourceUrl!);
|
||||
} else if (SCGlobalConfig.isLowPerformanceDevice &&
|
||||
(rtcProvider?.shouldShowRoomVisualEffects ?? false)) {
|
||||
_enqueueLowPerformanceGiftCompensation(
|
||||
gift: gift,
|
||||
receiver: u.user,
|
||||
quantity: quantity,
|
||||
coins: coins,
|
||||
);
|
||||
} else {
|
||||
_giftFxLog(
|
||||
'skip local play because visual effects disabled '
|
||||
@ -1925,10 +1775,51 @@ 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(
|
||||
SCFloatingMessage message, {
|
||||
String? dedupKey,
|
||||
}) {
|
||||
if (SCGlobalConfig.isLowPerformanceDevice) {
|
||||
OverlayManager().addLowPerformanceCompensationMessage(
|
||||
message,
|
||||
dedupKey: dedupKey,
|
||||
);
|
||||
return;
|
||||
}
|
||||
OverlayManager().addMessage(message);
|
||||
}
|
||||
|
||||
@ -1949,13 +1840,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
].join("|");
|
||||
}
|
||||
|
||||
String _floatingGiftTabFor(SocialChatGiftRes gift) {
|
||||
if (_isCpGift(gift)) {
|
||||
return scCpRelationTypeForGift(gift);
|
||||
}
|
||||
return gift.giftTab ?? "";
|
||||
}
|
||||
|
||||
bool _supportsComboFeedback(SocialChatGiftRes gift) {
|
||||
final giftTab = (gift.giftTab ?? '').trim();
|
||||
return giftTab == "LUCK" ||
|
||||
@ -1964,76 +1848,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
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() {
|
||||
return SCGiftComboSendButton(
|
||||
label: SCAppLocalizations.of(context)!.send,
|
||||
@ -2296,44 +2110,6 @@ class _GiftSendRequest {
|
||||
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 {
|
||||
final sortedAcceptUserIds = List<String>.from(acceptUserIds)..sort();
|
||||
final type =
|
||||
|
||||
@ -13,7 +13,6 @@ import 'package:provider/provider.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/app/config/business_logic_strategy.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 }
|
||||
|
||||
@ -448,15 +447,12 @@ class _GiftTabPageState extends State<GiftTabPage>
|
||||
)
|
||||
: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.w),
|
||||
border: Border.all(
|
||||
color:
|
||||
checkedIndex ==
|
||||
ref.giftByTab[widget.type]!.indexOf(gift)
|
||||
checkedIndex == ref.giftByTab[widget.type]!.indexOf(gift)
|
||||
? SocialChatTheme.primaryLight
|
||||
: Colors.transparent,
|
||||
width: 1.w,
|
||||
@ -519,14 +515,6 @@ class _GiftTabPageState extends State<GiftTabPage>
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_shouldShowCpRelationBadge(gift))
|
||||
PositionedDirectional(
|
||||
top: 0,
|
||||
end: 0,
|
||||
child: _buildCpRelationBadge(gift),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
checkedIndex = ref.giftByTab[widget.type]!.indexOf(gift);
|
||||
@ -536,68 +524,6 @@ 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;
|
||||
|
||||
String _formatBackpackQuantity(SocialChatGiftRes gift) {
|
||||
@ -631,19 +557,3 @@ class _GiftTabPageState extends State<GiftTabPage>
|
||||
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,9 +15,7 @@ import 'package:provider/provider.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.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/user_manager.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/auth/user_profile_manager.dart';
|
||||
import 'package:yumi/ui_kit/widgets/svga/sc_svga_asset_widget.dart';
|
||||
@ -46,13 +44,11 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
final List<Widget> _pages = [];
|
||||
final Set<int> _builtPageIndexes = <int>{0};
|
||||
final List<BottomNavigationBarItem> _bottomItems = [];
|
||||
final ValueNotifier<int> _currentIndexNotifier = ValueNotifier<int>(0);
|
||||
final ValueNotifier<int> _taskClaimableCount = ValueNotifier<int>(0);
|
||||
SCAppGeneralManager? generalProvider;
|
||||
Locale? _lastLocale;
|
||||
bool _hasShownEntryDialogs = false;
|
||||
bool _hasShownRegisterRewardDialog = false;
|
||||
bool _hasRequestedFirstRechargeDialog = false;
|
||||
bool _isShowingDailySignInDialog = false;
|
||||
bool _showRegisterRewardAfterDailySignIn = false;
|
||||
bool _isOpeningFirstRegisterRoomGame = false;
|
||||
@ -97,7 +93,6 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
routeObserver.unsubscribe(this);
|
||||
OverlayManager().setHomeRootTabsVisible(false);
|
||||
_registerRewardSubscription?.cancel();
|
||||
_currentIndexNotifier.dispose();
|
||||
_taskClaimableCount.dispose();
|
||||
WakelockPlus.disable();
|
||||
super.dispose();
|
||||
@ -275,7 +270,7 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
),
|
||||
);
|
||||
_pages.add(SCMessagePage());
|
||||
_pages.add(MePage2(currentIndexListenable: _currentIndexNotifier));
|
||||
_pages.add(MePage2());
|
||||
}
|
||||
|
||||
Widget _buildCachedBody() {
|
||||
@ -302,7 +297,6 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
}
|
||||
setState(() {
|
||||
_currentIndex = index;
|
||||
_currentIndexNotifier.value = index;
|
||||
_builtPageIndexes.add(index);
|
||||
});
|
||||
}
|
||||
@ -441,27 +435,14 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
return;
|
||||
}
|
||||
|
||||
await _showDailySignInDialogIfNeeded();
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
await _showEntryPopupIfNeeded();
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
await _showFirstRechargeDialogIfNeeded();
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
await _openFirstRegisterRoomGameIfNeeded();
|
||||
}
|
||||
|
||||
Future<void> _showDailySignInDialogIfNeeded() async {
|
||||
final dialogData = await _loadDailySignInDialogData();
|
||||
if (!mounted || dialogData == null) {
|
||||
await _openFirstRegisterRoomGameIfNeeded();
|
||||
return;
|
||||
}
|
||||
_isShowingDailySignInDialog = true;
|
||||
@ -477,6 +458,7 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
await _openFirstRegisterRoomGameIfNeeded();
|
||||
}
|
||||
|
||||
Future<void> _showEntryPopupIfNeeded() async {
|
||||
@ -489,18 +471,6 @@ 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 {
|
||||
if (!DataPersistence.getAwaitRegisterRewardSocket() ||
|
||||
DataPersistence.getPendingRegisterRewardDialog()) {
|
||||
|
||||
@ -29,13 +29,8 @@ import '../voice_room_route.dart';
|
||||
///编辑房间信息
|
||||
class RoomEditPage extends StatefulWidget {
|
||||
final String needRestCurrentRoomInfo;
|
||||
final RoomEntryPreviewData? setupPreviewData;
|
||||
|
||||
const RoomEditPage({
|
||||
super.key,
|
||||
this.needRestCurrentRoomInfo = "false",
|
||||
this.setupPreviewData,
|
||||
});
|
||||
const RoomEditPage({super.key, this.needRestCurrentRoomInfo = "false"});
|
||||
|
||||
@override
|
||||
State<RoomEditPage> createState() => _RoomEditPageState();
|
||||
@ -50,7 +45,6 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
||||
|
||||
RtcProvider? rtcProvider;
|
||||
bool isEdit = false;
|
||||
bool _isBasicInfoEditing = false;
|
||||
final TextEditingController _roomNameController = TextEditingController();
|
||||
final TextEditingController _roomAnnouncementController =
|
||||
TextEditingController();
|
||||
@ -59,7 +53,7 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
||||
final int _roomNameMaxLength = 24;
|
||||
int _roomNameCurrentLength = 0;
|
||||
String roomCover = "";
|
||||
final int _roomAnnouncementMaxLength = 200;
|
||||
final int _roomAnnouncementMaxLength = 24;
|
||||
int _roomAnnouncementCurrentLength = 0;
|
||||
bool _initialRoomLocked = false;
|
||||
bool _isRoomPrivate = false;
|
||||
@ -82,25 +76,17 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
||||
);
|
||||
});
|
||||
});
|
||||
final setupPreviewData = widget.setupPreviewData;
|
||||
_roomNameController.text =
|
||||
setupPreviewData?.roomName ??
|
||||
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomName ??
|
||||
"";
|
||||
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomName ?? "";
|
||||
_roomNameCurrentLength = _getActualCharacterCount(_roomNameController.text);
|
||||
_roomAnnouncementController.text =
|
||||
setupPreviewData?.roomDesc ??
|
||||
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomDesc ??
|
||||
"";
|
||||
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomDesc ?? "";
|
||||
_roomAnnouncementCurrentLength = _getActualCharacterCount(
|
||||
_roomAnnouncementController.text,
|
||||
);
|
||||
roomCover =
|
||||
setupPreviewData?.roomCover ??
|
||||
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomCover ??
|
||||
"";
|
||||
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomCover ?? "";
|
||||
final roomPassword =
|
||||
setupPreviewData?.roomSetting?.password?.trim() ??
|
||||
rtcProvider?.currenRoom?.roomProfile?.roomSetting?.password?.trim() ??
|
||||
"";
|
||||
_initialRoomLocked = roomPassword.isNotEmpty;
|
||||
@ -147,11 +133,22 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
||||
SizedBox(height: mediaQuery.viewPadding.top),
|
||||
_buildNavigationBar(context),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
||||
padding: EdgeInsets.only(bottom: bottomPadding + 24.w),
|
||||
child: Stack(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
keyboardDismissBehavior:
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
@ -198,50 +195,11 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
||||
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) {
|
||||
return Column(
|
||||
children: [
|
||||
@ -253,12 +211,11 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
||||
height: 40.w,
|
||||
hintText: "Please enter a name",
|
||||
counterText: '$_roomNameCurrentLength/$_roomNameMaxLength',
|
||||
readOnly: !_isBasicInfoEditing,
|
||||
inputFormatters: [
|
||||
SCAccurateLengthLimitingTextInputFormatter(_roomNameMaxLength),
|
||||
],
|
||||
onChanged: (_) {
|
||||
_markEdited();
|
||||
isEdit = true;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 16.w),
|
||||
@ -274,22 +231,17 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
||||
hintText: "Please enter a name",
|
||||
maxLines: null,
|
||||
expands: true,
|
||||
readOnly: !_isBasicInfoEditing,
|
||||
inputFormatters: [
|
||||
SCAccurateLengthLimitingTextInputFormatter(
|
||||
_roomAnnouncementMaxLength,
|
||||
),
|
||||
],
|
||||
onChanged: (_) {
|
||||
_markEdited();
|
||||
isEdit = true;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 16.w),
|
||||
_buildLockHeader(),
|
||||
Visibility(
|
||||
visible: _isRoomPrivate,
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 8.w),
|
||||
_buildTextInput(
|
||||
controller: _roomLockPasswordController,
|
||||
@ -298,14 +250,11 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
obscureText: true,
|
||||
onChanged: (_) {
|
||||
_markEdited();
|
||||
isEdit = true;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 8.w),
|
||||
_buildLockTips(),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.needRestCurrentRoomInfo != "true") ...[
|
||||
SizedBox(height: 16.w),
|
||||
_buildSettingRow(
|
||||
@ -391,7 +340,6 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
||||
TextInputType? keyboardType,
|
||||
List<TextInputFormatter>? inputFormatters,
|
||||
ValueChanged<String>? onChanged,
|
||||
bool readOnly = false,
|
||||
}) {
|
||||
final isMultiline = maxLines == null || expands;
|
||||
return Container(
|
||||
@ -417,9 +365,6 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
||||
cursorColor: SocialChatTheme.primaryColor,
|
||||
keyboardType: keyboardType,
|
||||
obscureText: obscureText,
|
||||
readOnly: readOnly,
|
||||
enableInteractiveSelection: !readOnly,
|
||||
showCursor: !readOnly,
|
||||
inputFormatters: inputFormatters,
|
||||
maxLines: maxLines,
|
||||
expands: expands,
|
||||
@ -480,15 +425,6 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
||||
);
|
||||
}
|
||||
|
||||
void _markEdited() {
|
||||
if (isEdit) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
isEdit = true;
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildSectionHeader({required String title, String? counterText}) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 10.w),
|
||||
@ -691,9 +627,34 @@ 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() {
|
||||
if (widget.needRestCurrentRoomInfo == "true" &&
|
||||
widget.setupPreviewData == null) {
|
||||
if (widget.needRestCurrentRoomInfo == "true") {
|
||||
Provider.of<RtcProvider>(context, listen: false).currenRoom = null;
|
||||
}
|
||||
Navigator.pop(context);
|
||||
@ -739,35 +700,11 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
||||
);
|
||||
final currentRtcProvider = Provider.of<RtcProvider>(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 submittedRoomDesc = _roomAnnouncementController.text.trim();
|
||||
final submittedRoomCover = roomCover.trim();
|
||||
final submittedRoomPassword = _roomLockPasswordController.text.trim();
|
||||
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;
|
||||
}
|
||||
final roomId = rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "";
|
||||
if (submittedRoomName.isEmpty) {
|
||||
SCTts.show("Room name not empty!");
|
||||
return;
|
||||
@ -796,9 +733,6 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
||||
return;
|
||||
}
|
||||
final mergedRoomInfo = roomInfo.copyWith(
|
||||
id: _preferNonEmpty(roomInfo.id, roomId),
|
||||
roomAccount: _preferNonEmpty(roomInfo.roomAccount, roomAccount),
|
||||
userId: roomInfo.userId ?? roomUserId,
|
||||
roomCover: _preferNonEmpty(roomInfo.roomCover, submittedRoomCover),
|
||||
roomName: _preferNonEmpty(roomInfo.roomName, submittedRoomName),
|
||||
roomDesc: _preferNonEmpty(roomInfo.roomDesc, submittedRoomDesc),
|
||||
@ -827,7 +761,7 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
||||
if (widget.needRestCurrentRoomInfo == "true") {
|
||||
///需要创建群组
|
||||
var c = await currentRtmProvider.createRoomGroup(
|
||||
roomAccount,
|
||||
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "",
|
||||
mergedRoomInfo.roomName ?? submittedRoomName,
|
||||
);
|
||||
if (!context.mounted) {
|
||||
@ -836,25 +770,31 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
||||
}
|
||||
if (_roomGroupReadyCodes.contains(c.code)) {
|
||||
SCLoadingManager.hide();
|
||||
SCTts.show(SCAppLocalizations.of(context)!.createRoomSuccsess);
|
||||
SCNavigatorUtils.goBack(context);
|
||||
currentRtcProvider.joinVoiceRoomSession(
|
||||
context,
|
||||
mergedRoomInfo.id ?? roomId,
|
||||
clearRoomData: setupPreviewData == null,
|
||||
mergedRoomInfo.id ??
|
||||
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ??
|
||||
"",
|
||||
clearRoomData: true,
|
||||
previewData: RoomEntryPreviewData(
|
||||
roomId: mergedRoomInfo.id ?? roomId,
|
||||
roomAccount: mergedRoomInfo.roomAccount ?? roomAccount,
|
||||
userId: mergedRoomInfo.userId ?? roomUserId,
|
||||
roomId:
|
||||
mergedRoomInfo.id ??
|
||||
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id,
|
||||
roomAccount:
|
||||
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount,
|
||||
userId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.userId,
|
||||
roomCover: mergedRoomInfo.roomCover,
|
||||
roomName: mergedRoomInfo.roomName ?? submittedRoomName,
|
||||
roomDesc: mergedRoomInfo.roomDesc ?? submittedRoomDesc,
|
||||
roomSetting: roomSetting,
|
||||
roomSetting: rtcProvider?.currenRoom?.roomProfile?.roomSetting,
|
||||
existsPassword: _isRoomPrivate,
|
||||
ownerProfile: ownerProfile,
|
||||
roomAdminCount: roomAdminCount,
|
||||
roomMemberCount: roomMemberCount,
|
||||
entrantsRole: entrantsRole,
|
||||
ownerProfile: rtcProvider?.currenRoom?.roomProfile?.userProfile,
|
||||
roomAdminCount:
|
||||
rtcProvider?.currenRoom?.roomProfile?.roomCounter?.adminCount,
|
||||
roomMemberCount:
|
||||
rtcProvider?.currenRoom?.roomProfile?.roomCounter?.memberCount,
|
||||
entrantsRole: rtcProvider?.currenRoom?.entrants?.roles,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
|
||||
@ -558,14 +558,9 @@ class _SwipeDeleteTileState extends State<_SwipeDeleteTile> {
|
||||
duration: const Duration(milliseconds: 160),
|
||||
curve: Curves.easeOutCubic,
|
||||
transform: Matrix4.translationValues(_dragOffset, 0, 0),
|
||||
child: SizedBox(
|
||||
width: constraints.maxWidth + actionWidth,
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: constraints.maxWidth,
|
||||
child: widget.child,
|
||||
),
|
||||
SizedBox(width: constraints.maxWidth, child: widget.child),
|
||||
SizedBox(
|
||||
width: actionWidth,
|
||||
height: 56.w,
|
||||
@ -592,7 +587,6 @@ class _SwipeDeleteTileState extends State<_SwipeDeleteTile> {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@ -13,6 +13,7 @@ import '../../../ui_kit/components/sc_page_list.dart';
|
||||
import '../../../ui_kit/components/sc_tts.dart';
|
||||
import '../../../ui_kit/components/text/sc_text.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/room/room_user_info_card.dart';
|
||||
|
||||
@ -43,7 +44,7 @@ class _RoomOnlinePageState
|
||||
if (dialogContext == null) {
|
||||
return;
|
||||
}
|
||||
RoomUserInfoCard.showPreloaded(dialogContext, userId: userId);
|
||||
showBottomInBottomDialog(dialogContext, RoomUserInfoCard(userId: userId));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ 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/text/sc_text.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/ui_kit/widgets/id/sc_special_id_badge.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/room_user_info_card.dart';
|
||||
@ -97,11 +98,11 @@ class _RoomGiftRankTabPageState
|
||||
// headdress: userInfo.userProfile?.getHeaddress()?.sourceUrl,
|
||||
),
|
||||
onTap: () {
|
||||
SmartDialog.dismiss(tag: "showRoomGiftRankPage");
|
||||
RoomUserInfoCard.showPreloaded(
|
||||
showBottomInBottomDialog(
|
||||
navigatorKey.currentState!.context,
|
||||
userId: userProfile?.id,
|
||||
RoomUserInfoCard(userId: userProfile?.id),
|
||||
);
|
||||
SmartDialog.dismiss(tag: "showRoomGiftRankPage");
|
||||
},
|
||||
),
|
||||
SizedBox(width: 3.w),
|
||||
|
||||
@ -225,7 +225,7 @@ class _SCSeatItemState extends State<SCSeatItem> with TickerProviderStateMixin {
|
||||
height: _seatLabelRowHeight.w,
|
||||
child: Center(
|
||||
child: text(
|
||||
"NO.${widget.index.toInt() + 1}",
|
||||
"NO.${widget.index}",
|
||||
fontSize: 10.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
|
||||
@ -21,19 +21,15 @@ import 'package:yumi/services/music/room_music_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_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_path_utils.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/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_entrance_screen.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/vapp_svga_layer_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_online_user_widget.dart';
|
||||
@ -81,16 +77,18 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
int _shownRoomStartupFailureToken = 0;
|
||||
RtcProvider? _rtcProvider;
|
||||
bool _roomProviderRebuildScheduled = false;
|
||||
bool _roomRouteVisibleScheduled = false;
|
||||
bool _roomVisualEffectsEnsureScheduled = false;
|
||||
PageRoute<dynamic>? _routeObserverRoute;
|
||||
SCRoomTopLayerGuardLease? _coveredRouteGuardLease;
|
||||
bool _roomRouteVisible = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: _chatTabCount, vsync: this);
|
||||
Provider.of<RtcProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).setVoiceRoomRouteVisible(true);
|
||||
_enableRoomVisualEffects();
|
||||
_tabController.addListener(_handleTabChange);
|
||||
_subscription = eventBus.on<SCGiveRoomLuckPageDisposeEvent>().listen((
|
||||
event,
|
||||
@ -126,20 +124,15 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
}
|
||||
_routeObserverRoute = route;
|
||||
routeObserver.subscribe(this, route);
|
||||
if (route.isCurrent) {
|
||||
_scheduleRoomRouteVisible();
|
||||
} else {
|
||||
_handleRoomRouteCovered();
|
||||
_setRoomRouteVisible(route.isCurrent);
|
||||
}
|
||||
}
|
||||
_scheduleEnsureRoomVisualEffectsEnabled();
|
||||
_ensureRoomVisualEffectsEnabled();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
routeObserver.unsubscribe(this);
|
||||
_routeObserverRoute = null;
|
||||
_releaseCoveredRouteGuard();
|
||||
_setRoomRouteVisible(false);
|
||||
_rtcProvider?.removeListener(_handleRoomProviderChanged);
|
||||
_rtcProvider = null;
|
||||
@ -153,60 +146,22 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
|
||||
@override
|
||||
void didPush() {
|
||||
_handleRoomRouteVisible();
|
||||
_setRoomRouteVisible(true);
|
||||
}
|
||||
|
||||
@override
|
||||
void didPopNext() {
|
||||
_handleRoomRouteVisible();
|
||||
_setRoomRouteVisible(true);
|
||||
}
|
||||
|
||||
@override
|
||||
void didPushNext() {
|
||||
_handleRoomRouteCovered();
|
||||
_setRoomRouteVisible(false);
|
||||
}
|
||||
|
||||
@override
|
||||
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);
|
||||
_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) {
|
||||
@ -261,43 +216,6 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
_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() {
|
||||
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
||||
final currentRoomId =
|
||||
@ -322,12 +240,10 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
OverlayManager().removeRoom(roomId: currentRoomId);
|
||||
SCRoomEffectScheduler().clearDeferredTasks(reason: 'voice_room_suspend');
|
||||
SCGiftVapSvgaManager().stopPlayback();
|
||||
if (!SCFloatIchart().isShow()) {
|
||||
unawaited(
|
||||
Provider.of<RoomMusicManager>(context, listen: false).stopForRoomExit(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildRoomStartupLayer() {
|
||||
return Selector<RtcProvider, _RoomStartupSnapshot>(
|
||||
@ -389,8 +305,6 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
return SCAppLocalizations.of(context)!.enterRoomFailedRetry;
|
||||
case RoomStartupFailureType.rtc:
|
||||
return SCAppLocalizations.of(context)!.voiceConnectionFailedRetry;
|
||||
case RoomStartupFailureType.kicked:
|
||||
return SCAppLocalizations.of(context)!.roomKickedEntryBlockedTips;
|
||||
case RoomStartupFailureType.entry:
|
||||
case RoomStartupFailureType.none:
|
||||
return SCAppLocalizations.of(context)!.operationFail;
|
||||
@ -504,8 +418,6 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
// _buildPlayViews(),
|
||||
///幸运礼物中奖动画
|
||||
LuckGiftNomorAnimWidget(),
|
||||
_buildRoomGiftSvgaLayer(),
|
||||
_buildRoomGiftSeatFlightLayer(),
|
||||
const RoomGiftComboFloatingLayer(),
|
||||
],
|
||||
),
|
||||
@ -514,43 +426,6 @@ 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) {
|
||||
return Expanded(
|
||||
@ -732,7 +607,6 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
}
|
||||
|
||||
final giftPhoto = (msg.gift?.giftPhoto ?? "").trim();
|
||||
_enqueueCpGiftFloatingIfNeeded(msg);
|
||||
if (_supportsComboMilestoneEffects(msg)) {
|
||||
_handleComboMilestoneVisuals(msg, targetGroupKey);
|
||||
}
|
||||
@ -754,48 +628,6 @@ 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) {
|
||||
if (!Provider.of<RtcProvider>(
|
||||
context,
|
||||
|
||||
@ -159,14 +159,12 @@ class VoiceRoomRoute implements SCIRouterProvider {
|
||||
static Future<T?> openRoomEdit<T>(
|
||||
BuildContext context, {
|
||||
required bool needRestCurrentRoomInfo,
|
||||
RoomEntryPreviewData? setupPreviewData,
|
||||
bool rootNavigator = true,
|
||||
}) {
|
||||
return Navigator.of(context, rootNavigator: rootNavigator).push<T>(
|
||||
_buildRoute<T>(
|
||||
RoomEditPage(
|
||||
needRestCurrentRoomInfo: needRestCurrentRoomInfo ? "true" : "false",
|
||||
setupPreviewData: setupPreviewData,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -1,303 +0,0 @@
|
||||
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,10 +127,6 @@ class RoomGameListItemModel {
|
||||
bool get isLeader =>
|
||||
provider.toUpperCase() == 'LEADER' || gameType.toUpperCase() == 'LEADER';
|
||||
|
||||
bool get isHotgame =>
|
||||
provider.toUpperCase() == 'HOTGAME' ||
|
||||
gameType.toUpperCase() == 'HOTGAME';
|
||||
|
||||
factory RoomGameListItemModel.fromJson(Map<String, dynamic> json) {
|
||||
return RoomGameListItemModel(
|
||||
gameId: _asString(json['gameId']),
|
||||
|
||||
@ -6,24 +6,24 @@ class BaishunLoadingView extends StatelessWidget {
|
||||
const BaishunLoadingView({
|
||||
super.key,
|
||||
this.message = 'Loading game...',
|
||||
this.backgroundColor = const Color(0xFF081915),
|
||||
});
|
||||
|
||||
final String message;
|
||||
final Color backgroundColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Positioned.fill(
|
||||
child: Container(
|
||||
color: backgroundColor,
|
||||
color: Colors.black.withValues(alpha: 0.42),
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 22.w, vertical: 18.w),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF102D28).withValues(alpha: 0.92),
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
border: Border.all(color: Colors.white.withValues(alpha: 0.1)),
|
||||
border: Border.all(
|
||||
color: Colors.white.withValues(alpha: 0.1),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
||||
@ -1,640 +0,0 @@
|
||||
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,11 +9,9 @@ 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_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/services/audio/rtc_manager.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/sc_tts.dart';
|
||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||
@ -33,6 +31,7 @@ class RoomGameListSheet extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _RoomGameListSheetState extends State<RoomGameListSheet> {
|
||||
static const String _logPrefix = '[RoomGameLaunch]';
|
||||
static const int _itemsPerRow = 4;
|
||||
static const String _sheetFrameAsset =
|
||||
'sc_images/room/sc_room_game_sheet_frame.png';
|
||||
@ -91,9 +90,7 @@ class _RoomGameListSheetState extends State<RoomGameListSheet> {
|
||||
String targetGameId,
|
||||
) {
|
||||
final supportedItems =
|
||||
items
|
||||
.where((item) => item.isBaishun || item.isLeader || item.isHotgame)
|
||||
.toList();
|
||||
items.where((item) => item.isBaishun || item.isLeader).toList();
|
||||
final candidates = supportedItems.isNotEmpty ? supportedItems : items;
|
||||
if (targetGameId == '0') {
|
||||
return candidates[math.Random().nextInt(candidates.length)];
|
||||
@ -120,7 +117,7 @@ class _RoomGameListSheetState extends State<RoomGameListSheet> {
|
||||
SCTts.show('roomId is empty');
|
||||
return;
|
||||
}
|
||||
if (!game.isBaishun && !game.isLeader && !game.isHotgame) {
|
||||
if (!game.isBaishun && !game.isLeader) {
|
||||
SCTts.show('This game provider is not wired yet');
|
||||
return;
|
||||
}
|
||||
@ -129,7 +126,6 @@ class _RoomGameListSheetState extends State<RoomGameListSheet> {
|
||||
_launchingGameId = game.gameId;
|
||||
});
|
||||
|
||||
SCRoomTopLayerGuardLease? gameDialogGuard;
|
||||
try {
|
||||
_log(
|
||||
'launch_request roomId=$_roomId gameId=${game.gameId} '
|
||||
@ -151,38 +147,23 @@ class _RoomGameListSheetState extends State<RoomGameListSheet> {
|
||||
return;
|
||||
}
|
||||
|
||||
gameDialogGuard = SCRoomTopLayerGuard().acquire(reason: 'room_game_page');
|
||||
Navigator.of(context).pop();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 180));
|
||||
if (!widget.roomContext.mounted) {
|
||||
gameDialogGuard.release();
|
||||
gameDialogGuard = null;
|
||||
return;
|
||||
}
|
||||
final gamePage = _buildGamePage(game, launchModel);
|
||||
if (gamePage == null) {
|
||||
gameDialogGuard.release();
|
||||
gameDialogGuard = null;
|
||||
SCTts.show('This game provider is not wired yet');
|
||||
return;
|
||||
}
|
||||
final activeGameDialogGuard = gameDialogGuard;
|
||||
gameDialogGuard = null;
|
||||
try {
|
||||
unawaited(
|
||||
showBottomInBottomDialog(
|
||||
widget.roomContext,
|
||||
gamePage,
|
||||
barrierColor: Colors.black54,
|
||||
barrierDismissible: true,
|
||||
).whenComplete(activeGameDialogGuard.release),
|
||||
);
|
||||
} catch (_) {
|
||||
activeGameDialogGuard.release();
|
||||
rethrow;
|
||||
}
|
||||
} catch (error) {
|
||||
gameDialogGuard?.release();
|
||||
_log('launch_error error=${_clip(error.toString(), 400)}');
|
||||
SCTts.show('Launch failed');
|
||||
} finally {
|
||||
@ -270,13 +251,6 @@ class _RoomGameListSheetState extends State<RoomGameListSheet> {
|
||||
launchModel: launchModel,
|
||||
);
|
||||
}
|
||||
if (game.isHotgame) {
|
||||
return HotgameGamePage(
|
||||
roomId: _roomId,
|
||||
game: game,
|
||||
launchModel: launchModel,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@ -178,8 +178,7 @@ class _SplashPageState extends State<SplashPage> {
|
||||
Image.asset(
|
||||
SCGlobalConfig.businessLogicStrategy.getSplashPageIcon(),
|
||||
width: 107.w,
|
||||
height: 97.w,
|
||||
fit: BoxFit.contain,
|
||||
height: 159.w,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@ -8,7 +8,6 @@ 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/repositories/sc_user_repository_impl.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:yumi/ui_kit/components/appbar/socialchat_appbar.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
@ -18,11 +17,8 @@ import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
import 'package:yumi/shared/tools/sc_lk_dialog_util.dart';
|
||||
import 'package:yumi/services/audio/rtc_manager.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/business_logic/models/res/country_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';
|
||||
|
||||
class EditUserInfoPage2 extends StatefulWidget {
|
||||
@ -41,9 +37,6 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
String? bornMonth;
|
||||
String? autograph;
|
||||
String? hobby;
|
||||
String? countryId;
|
||||
String? countryCode;
|
||||
String? countryName;
|
||||
String? bornDay;
|
||||
String? bornYear;
|
||||
String nickName = "";
|
||||
@ -60,32 +53,12 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
nickName = currentProfile?.userNickname ?? "";
|
||||
autograph = currentProfile?.autograph ?? "";
|
||||
hobby = currentProfile?.hobby ?? "";
|
||||
countryId = currentProfile?.countryId ?? "";
|
||||
countryCode = currentProfile?.countryCode ?? "";
|
||||
countryName = currentProfile?.countryName ?? "";
|
||||
birthdayDate =
|
||||
_birthdayFromProfile(currentProfile) ??
|
||||
_birthdayFromAge(currentProfile?.age);
|
||||
bornMonth = _formatTwoDigits(
|
||||
currentProfile?.bornMonth ?? birthdayDate?.month,
|
||||
);
|
||||
bornDay = _formatTwoDigits(currentProfile?.bornDay ?? birthdayDate?.day);
|
||||
bornYear = _formatYear(currentProfile?.bornYear ?? birthdayDate?.year);
|
||||
birthdayDate = _birthdayFromProfile(currentProfile);
|
||||
bornMonth = _formatTwoDigits(currentProfile?.bornMonth);
|
||||
bornDay = _formatTwoDigits(currentProfile?.bornDay);
|
||||
bornYear = _formatYear(currentProfile?.bornYear);
|
||||
age = currentProfile?.age ?? _ageFromBirthday(birthdayDate);
|
||||
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) {
|
||||
@ -98,6 +71,21 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
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]) {
|
||||
final targetProfile =
|
||||
profile ?? AccountStorage().getCurrentUser()?.userProfile;
|
||||
@ -137,15 +125,6 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
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() {
|
||||
if ((bornYear ?? "").isEmpty ||
|
||||
(bornMonth ?? "").isEmpty ||
|
||||
@ -165,50 +144,16 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
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) {
|
||||
userCover = profile.userAvatar;
|
||||
userCover = _preferUsableAvatar(profile.userAvatar, userCover);
|
||||
nickName = profile.userNickname ?? nickName;
|
||||
autograph = profile.autograph ?? autograph;
|
||||
hobby = profile.hobby ?? hobby;
|
||||
countryId = profile.countryId ?? countryId;
|
||||
countryCode = profile.countryCode ?? countryCode;
|
||||
countryName = profile.countryName ?? countryName;
|
||||
sex = profile.userSex ?? sex;
|
||||
birthdayDate =
|
||||
_birthdayFromProfile(profile) ??
|
||||
_birthdayFromAge(profile.age) ??
|
||||
birthdayDate;
|
||||
bornYear = _formatYear(profile.bornYear ?? birthdayDate?.year);
|
||||
bornMonth = _formatTwoDigits(profile.bornMonth ?? birthdayDate?.month);
|
||||
bornDay = _formatTwoDigits(profile.bornDay ?? birthdayDate?.day);
|
||||
birthdayDate = _birthdayFromProfile(profile) ?? birthdayDate;
|
||||
bornYear = _formatYear(profile.bornYear);
|
||||
bornMonth = _formatTwoDigits(profile.bornMonth);
|
||||
bornDay = _formatTwoDigits(profile.bornDay);
|
||||
age = profile.age ?? _ageFromBirthday(birthdayDate) ?? age;
|
||||
}
|
||||
|
||||
@ -278,9 +223,11 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
String url,
|
||||
) {
|
||||
if (success) {
|
||||
submit(userAvatarValue: url);
|
||||
userCover = url;
|
||||
setState(() {});
|
||||
submitAvatarOnly();
|
||||
}
|
||||
}, allowGif: true);
|
||||
});
|
||||
},
|
||||
),
|
||||
SizedBox(height: 3.w),
|
||||
@ -296,7 +243,9 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
sex == 1
|
||||
? SCAppLocalizations.of(context)!.man
|
||||
: SCAppLocalizations.of(context)!.woman,
|
||||
null,
|
||||
() {
|
||||
_showSexDialog();
|
||||
},
|
||||
),
|
||||
|
||||
_buildItem(
|
||||
@ -321,12 +270,6 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
_showInputBioHobby(hobby ?? "", 2);
|
||||
},
|
||||
),
|
||||
if (_canChangeCountry(ref.userIdentity))
|
||||
_buildItem(
|
||||
"${SCAppLocalizations.of(context)!.countryRegion}:",
|
||||
_countryDisplayText(),
|
||||
_openCountryPicker,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
@ -447,6 +390,13 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
return eighteenYearsAgo;
|
||||
}
|
||||
|
||||
void submitAvatarOnly() {
|
||||
if ((userCover ?? "").trim().isEmpty) {
|
||||
return;
|
||||
}
|
||||
submit(userAvatarValue: userCover);
|
||||
}
|
||||
|
||||
void submit({
|
||||
Object? userAvatarValue = _noChange,
|
||||
Object? userNicknameValue = _noChange,
|
||||
@ -457,7 +407,6 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
Object? bornDayValue = _noChange,
|
||||
Object? hobbyValue = _noChange,
|
||||
Object? autographValue = _noChange,
|
||||
Object? countryValue = _noChange,
|
||||
}) async {
|
||||
if (!_hasChanges([
|
||||
userAvatarValue,
|
||||
@ -469,7 +418,6 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
bornDayValue,
|
||||
hobbyValue,
|
||||
autographValue,
|
||||
countryValue,
|
||||
])) {
|
||||
return;
|
||||
}
|
||||
@ -484,8 +432,6 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
_isSubmitting = true;
|
||||
SCLoadingManager.show();
|
||||
try {
|
||||
final selectedCountry =
|
||||
identical(countryValue, _noChange) ? null : countryValue as Country?;
|
||||
final updatedProfile = await SCAccountRepository().updateUserInfo(
|
||||
userAvatar:
|
||||
identical(userAvatarValue, _noChange)
|
||||
@ -511,9 +457,14 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
identical(autographValue, _noChange)
|
||||
? null
|
||||
: autographValue as String?,
|
||||
countryId: selectedCountry?.id,
|
||||
);
|
||||
final mergedProfile = updatedProfile.copyWith(
|
||||
userAvatar: _preferUsableAvatar(
|
||||
updatedProfile.userAvatar,
|
||||
identical(userAvatarValue, _noChange)
|
||||
? userCover
|
||||
: userAvatarValue as String?,
|
||||
),
|
||||
userNickname: _preferNonEmpty(
|
||||
updatedProfile.userNickname,
|
||||
identical(userNicknameValue, _noChange)
|
||||
@ -551,21 +502,6 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
(identical(bornYearValue, _noChange)
|
||||
? birthdayDate?.year
|
||||
: 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);
|
||||
if (!mounted) {
|
||||
@ -579,50 +515,13 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
true;
|
||||
setState(() {});
|
||||
SCTts.show(SCAppLocalizations.of(context)!.operationSuccessful);
|
||||
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.
|
||||
} catch (e) {
|
||||
} finally {
|
||||
_isSubmitting = false;
|
||||
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() {
|
||||
showCenterDialog(
|
||||
context,
|
||||
@ -704,7 +603,7 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
);
|
||||
}
|
||||
|
||||
_buildItem(String title, String value, Function()? func) {
|
||||
_buildItem(String title, String value, Function() func) {
|
||||
return SCDebounceWidget(
|
||||
child: Column(
|
||||
children: [
|
||||
@ -723,7 +622,6 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
child: text(value, textColor: Colors.white, fontSize: 15.sp),
|
||||
),
|
||||
),
|
||||
if (func != null)
|
||||
Icon(
|
||||
Icons.keyboard_arrow_right,
|
||||
size: 20.w,
|
||||
@ -742,7 +640,7 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
func?.call();
|
||||
func.call();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
@ -19,26 +17,19 @@ 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/repositories/sc_user_repository_impl.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_debounce_widget.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';
|
||||
|
||||
class MePage2 extends StatefulWidget {
|
||||
const MePage2({super.key, this.currentIndexListenable, this.tabIndex = 3});
|
||||
|
||||
final ValueListenable<int>? currentIndexListenable;
|
||||
final int tabIndex;
|
||||
const MePage2({super.key});
|
||||
|
||||
@override
|
||||
State<MePage2> createState() => _MePage2State();
|
||||
}
|
||||
|
||||
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';
|
||||
class _MePage2State extends State<MePage2> {
|
||||
static const String _inviteActivityUrl =
|
||||
'https://h5.haiyihy.com/app-invite/index.html';
|
||||
static const double _inviteActivityHorizontalScale = 750 / 662;
|
||||
@ -46,52 +37,15 @@ class _MePage2State extends State<MePage2> with WidgetsBindingObserver {
|
||||
Map<String, SCUserCounterRes> _counterMap = {};
|
||||
SCVipStatusRes? _vipStatus;
|
||||
bool _isVipStatusLoading = false;
|
||||
bool _isCounterLoading = false;
|
||||
bool _counterRefreshQueued = false;
|
||||
bool _counterReloadAfterCurrent = false;
|
||||
DateTime? _lastCounterLoadedAt;
|
||||
StreamSubscription<SCUserRelationChangedEvent>? _relationChangedSubscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
widget.currentIndexListenable?.addListener(_handleTabVisibilityChanged);
|
||||
_relationChangedSubscription = eventBus
|
||||
.on<SCUserRelationChangedEvent>()
|
||||
.listen(_handleUserRelationChanged);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_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() {
|
||||
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
@ -104,89 +58,10 @@ class _MePage2State extends State<MePage2> with WidgetsBindingObserver {
|
||||
_loadVipStatus();
|
||||
}
|
||||
|
||||
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;
|
||||
Future<void> _loadCounter() async {
|
||||
final userId = AccountStorage().getCurrentUser()?.userProfile?.id ?? "";
|
||||
if (userId.isEmpty) return;
|
||||
|
||||
_isCounterLoading = true;
|
||||
try {
|
||||
final userCounterList = await SCAccountRepository().userCounter(userId);
|
||||
if (!mounted) return;
|
||||
@ -196,17 +71,8 @@ class _MePage2State extends State<MePage2> with WidgetsBindingObserver {
|
||||
(counter) => MapEntry(counter.counterType ?? "", counter),
|
||||
),
|
||||
);
|
||||
_lastCounterLoadedAt = DateTime.now();
|
||||
_counterRefreshQueued = false;
|
||||
});
|
||||
} catch (_) {
|
||||
} finally {
|
||||
_isCounterLoading = false;
|
||||
if (_counterReloadAfterCurrent && mounted) {
|
||||
_counterReloadAfterCurrent = false;
|
||||
unawaited(_loadCounter(force: true));
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
Future<void> _loadVipStatus() async {
|
||||
@ -301,21 +167,6 @@ class _MePage2State extends State<MePage2> with WidgetsBindingObserver {
|
||||
);
|
||||
}
|
||||
|
||||
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) {
|
||||
final token = AccountStorage().getToken();
|
||||
if (token.isEmpty) return url;
|
||||
@ -497,14 +348,6 @@ class _MePage2State extends State<MePage2> with WidgetsBindingObserver {
|
||||
iconPath: 'sc_images/index/sc_icon_bag.png',
|
||||
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,10 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.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_tts.dart';
|
||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
@ -13,6 +15,7 @@ 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/login_res.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/modules/store/store_route.dart';
|
||||
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
||||
@ -152,15 +155,53 @@ class _BagsChatboxPageState
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
final selected = selecteChatbox;
|
||||
if (selected == null) {
|
||||
return;
|
||||
}
|
||||
_use(
|
||||
selected,
|
||||
myChatbox?.id == selected.propsResources?.id,
|
||||
if (myChatbox?.id !=
|
||||
selecteChatbox?.propsResources?.id) {
|
||||
///佩戴
|
||||
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);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -235,18 +276,33 @@ 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: () async {
|
||||
onTap: () {
|
||||
selecteChatbox = res;
|
||||
setState(() {});
|
||||
if (myChatbox?.id != res.propsResources?.id) {
|
||||
final success = await _use(res, false);
|
||||
if (!success || !mounted) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
showStoreBagFullScreenResourcePreview(
|
||||
context,
|
||||
resource: res.propsResources,
|
||||
@ -282,42 +338,46 @@ class _BagsChatboxPageState
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _use(BagsListRes res, bool unload) async {
|
||||
SCLoadingManager.show(context: context);
|
||||
final localizations = SCAppLocalizations.of(context)!;
|
||||
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
void _showDetail(BagsListRes res) {
|
||||
SmartDialog.show(
|
||||
tag: "showPropsDetail",
|
||||
alignment: Alignment.center,
|
||||
animationType: SmartAnimationType.fade,
|
||||
builder: (_) {
|
||||
return PropsBagChatboxDetailDialog(res);
|
||||
},
|
||||
onDismiss: () {
|
||||
myChatbox = AccountStorage().getChatbox();
|
||||
setState(() {});
|
||||
},
|
||||
);
|
||||
try {
|
||||
await SCStoreRepositoryImp().switchPropsUse(
|
||||
}
|
||||
|
||||
void _use(BagsListRes res, bool unload) {
|
||||
SCLoadingManager.show(context: context);
|
||||
SCStoreRepositoryImp()
|
||||
.switchPropsUse(
|
||||
SCPropsType.CHAT_BUBBLE.name,
|
||||
res.propsResources?.id ?? "",
|
||||
unload,
|
||||
);
|
||||
try {
|
||||
await profileManager.fetchUserProfileData(loadGuardCount: false);
|
||||
} catch (_) {}
|
||||
if (!mounted) {
|
||||
return false;
|
||||
}
|
||||
)
|
||||
.then((value) async {
|
||||
setState(() {});
|
||||
if (!unload) {
|
||||
myChatbox = res.propsResources;
|
||||
SCTts.show(localizations.successfulWear);
|
||||
SCTts.show(SCAppLocalizations.of(context)!.successfulWear);
|
||||
} else {
|
||||
myChatbox = null;
|
||||
SCTts.show(localizations.successfullyUnloaded);
|
||||
SCTts.show(SCAppLocalizations.of(context)!.successfullyUnloaded);
|
||||
}
|
||||
profileManager.syncCurrentUserChatBubble(
|
||||
unload ? null : res.propsResources,
|
||||
expireTime: res.expireTime?.toString(),
|
||||
);
|
||||
setState(() {});
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
} finally {
|
||||
Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).fetchUserProfileData(loadGuardCount: false);
|
||||
SCLoadingManager.hide();
|
||||
}
|
||||
})
|
||||
.catchError((e) {
|
||||
SCLoadingManager.hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
@ -11,12 +12,14 @@ 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/repositories/sc_store_repository_imp.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_debounce_widget.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/text/sc_text.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/store/store_bag_page_helpers.dart';
|
||||
|
||||
@ -164,11 +167,49 @@ class BagsDataCardPageState
|
||||
);
|
||||
return;
|
||||
}
|
||||
final selected = selectedDataCard;
|
||||
if (selected == null) {
|
||||
if (!selectedInUse) {
|
||||
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(selectedDataCard!, false);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
_use(selected, selectedInUse);
|
||||
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(selectedDataCard!, true);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -247,29 +288,33 @@ 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: () async {
|
||||
onTap: () {
|
||||
selectedDataCard = res;
|
||||
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(
|
||||
context,
|
||||
resource: res.propsResources,
|
||||
@ -303,22 +348,40 @@ class BagsDataCardPageState
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _use(BagsListRes res, bool unload) async {
|
||||
void _showDetail(BagsListRes res) {
|
||||
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);
|
||||
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
);
|
||||
final localizations = SCAppLocalizations.of(context)!;
|
||||
try {
|
||||
await SCStoreRepositoryImp().switchPropsUse(
|
||||
SCStoreRepositoryImp()
|
||||
.switchPropsUse(
|
||||
SCPropsType.DATA_CARD.name,
|
||||
res.propsResources?.id ?? "",
|
||||
unload,
|
||||
);
|
||||
)
|
||||
.then((value) async {
|
||||
if (!mounted) {
|
||||
return false;
|
||||
SCLoadingManager.hide();
|
||||
return;
|
||||
}
|
||||
setState(() {});
|
||||
if (!unload) {
|
||||
myDataCard = res.propsResources;
|
||||
SCTts.show(localizations.successfulWear);
|
||||
@ -326,13 +389,11 @@ class BagsDataCardPageState
|
||||
myDataCard = null;
|
||||
SCTts.show(localizations.successfullyUnloaded);
|
||||
}
|
||||
setState(() {});
|
||||
profileManager.fetchUserProfileData(loadGuardCount: false);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
} finally {
|
||||
SCLoadingManager.hide();
|
||||
}
|
||||
})
|
||||
.catchError((e) {
|
||||
SCLoadingManager.hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
||||
@ -10,11 +11,13 @@ import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
||||
import 'package:yumi/modules/store/store_route.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/dialog/dialog_base.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/text/sc_text.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_tts.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/store/store_bag_page_helpers.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@ -152,15 +155,53 @@ class _BagsHeaddressPageState
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
final selected = selecteHeaddress;
|
||||
if (selected == null) {
|
||||
return;
|
||||
}
|
||||
_use(
|
||||
selected,
|
||||
myHeaddress?.id == selected.propsResources?.id,
|
||||
if (myHeaddress?.id !=
|
||||
selecteHeaddress?.propsResources?.id) {
|
||||
///佩戴
|
||||
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);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -235,18 +276,33 @@ 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: () async {
|
||||
onTap: () {
|
||||
selecteHeaddress = res;
|
||||
setState(() {});
|
||||
if (myHeaddress?.id != res.propsResources?.id) {
|
||||
final success = await _use(res, false);
|
||||
if (!success || !mounted) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
showStoreBagFullScreenResourcePreview(
|
||||
context,
|
||||
resource: res.propsResources,
|
||||
@ -283,36 +339,46 @@ class _BagsHeaddressPageState
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _use(BagsListRes res, bool unload) async {
|
||||
SCLoadingManager.show(context: context);
|
||||
final localizations = SCAppLocalizations.of(context)!;
|
||||
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
void _showDetail(BagsListRes res) {
|
||||
SmartDialog.show(
|
||||
tag: "showPropsDetail",
|
||||
alignment: Alignment.center,
|
||||
animationType: SmartAnimationType.fade,
|
||||
builder: (_) {
|
||||
return PropsBagHeaddressDetailDialog(res);
|
||||
},
|
||||
onDismiss: () {
|
||||
myHeaddress = AccountStorage().getHeaddress();
|
||||
setState(() {});
|
||||
},
|
||||
);
|
||||
try {
|
||||
await SCStoreRepositoryImp().switchPropsUse(
|
||||
}
|
||||
|
||||
void _use(BagsListRes res, bool unload) {
|
||||
SCLoadingManager.show(context: context);
|
||||
SCStoreRepositoryImp()
|
||||
.switchPropsUse(
|
||||
SCPropsType.AVATAR_FRAME.name,
|
||||
res.propsResources?.id ?? "",
|
||||
unload,
|
||||
);
|
||||
if (!mounted) {
|
||||
return false;
|
||||
}
|
||||
)
|
||||
.then((value) async {
|
||||
setState(() {});
|
||||
if (!unload) {
|
||||
myHeaddress = res.propsResources;
|
||||
SCTts.show(localizations.successfulWear);
|
||||
SCTts.show(SCAppLocalizations.of(context)!.successfulWear);
|
||||
} else {
|
||||
myHeaddress = null;
|
||||
SCTts.show(localizations.successfullyUnloaded);
|
||||
SCTts.show(SCAppLocalizations.of(context)!.successfullyUnloaded);
|
||||
}
|
||||
setState(() {});
|
||||
profileManager.fetchUserProfileData(loadGuardCount: false);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
} finally {
|
||||
Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).fetchUserProfileData(loadGuardCount: false);
|
||||
SCLoadingManager.hide();
|
||||
}
|
||||
})
|
||||
.catchError((e) {
|
||||
SCLoadingManager.hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/bag/props_bag_mountains_detail_dialog.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
@ -19,6 +21,7 @@ import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
||||
|
||||
import '../../../../shared/data_sources/models/enum/sc_props_type.dart';
|
||||
import '../../../../ui_kit/components/sc_debounce_widget.dart';
|
||||
import '../../../../ui_kit/components/dialog/dialog_base.dart';
|
||||
import '../../../../ui_kit/theme/socialchat_theme.dart';
|
||||
|
||||
///坐骑
|
||||
@ -158,15 +161,53 @@ class _BagsMountainsPageState
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
final selected = selecteMountains;
|
||||
if (selected == null) {
|
||||
return;
|
||||
}
|
||||
_use(
|
||||
selected,
|
||||
myMountains?.id == selected.propsResources?.id,
|
||||
if (myMountains?.id !=
|
||||
selecteMountains?.propsResources?.id) {
|
||||
///佩戴
|
||||
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);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -244,15 +285,9 @@ class _BagsMountainsPageState
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
onTap: () {
|
||||
selecteMountains = res;
|
||||
setState(() {});
|
||||
if (myMountains?.id != res.propsResources?.id) {
|
||||
final success = await _use(res, false);
|
||||
if (!success || !mounted) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
showStoreBagFullScreenResourcePreview(
|
||||
context,
|
||||
resource: res.propsResources,
|
||||
@ -288,36 +323,46 @@ class _BagsMountainsPageState
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _use(BagsListRes res, bool unload) async {
|
||||
SCLoadingManager.show(context: context);
|
||||
final localizations = SCAppLocalizations.of(context)!;
|
||||
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
void _showDetail(BagsListRes res) {
|
||||
SmartDialog.show(
|
||||
tag: "showPropsDetail",
|
||||
alignment: Alignment.center,
|
||||
animationType: SmartAnimationType.fade,
|
||||
builder: (_) {
|
||||
return PropsBagMountainsDetailDialog(res);
|
||||
},
|
||||
onDismiss: () {
|
||||
myMountains = AccountStorage().getMountains();
|
||||
setState(() {});
|
||||
},
|
||||
);
|
||||
try {
|
||||
await SCStoreRepositoryImp().switchPropsUse(
|
||||
}
|
||||
|
||||
void _use(BagsListRes res, bool unload) {
|
||||
SCLoadingManager.show(context: context);
|
||||
SCStoreRepositoryImp()
|
||||
.switchPropsUse(
|
||||
SCPropsType.RIDE.name,
|
||||
res.propsResources?.id ?? "",
|
||||
unload,
|
||||
);
|
||||
if (!mounted) {
|
||||
return false;
|
||||
}
|
||||
)
|
||||
.then((value) async {
|
||||
setState(() {});
|
||||
if (!unload) {
|
||||
myMountains = res.propsResources;
|
||||
SCTts.show(localizations.successfulWear);
|
||||
SCTts.show(SCAppLocalizations.of(context)!.successfulWear);
|
||||
} else {
|
||||
myMountains = null;
|
||||
SCTts.show(localizations.successfullyUnloaded);
|
||||
SCTts.show(SCAppLocalizations.of(context)!.successfullyUnloaded);
|
||||
}
|
||||
setState(() {});
|
||||
profileManager.fetchUserProfileData(loadGuardCount: false);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
} finally {
|
||||
Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).fetchUserProfileData(loadGuardCount: false);
|
||||
SCLoadingManager.hide();
|
||||
}
|
||||
})
|
||||
.catchError((e) {
|
||||
SCLoadingManager.hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_page_list.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/ui_kit/widgets/bag/props_bag_theme_detail_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/countdown_timer.dart';
|
||||
import 'package:yumi/modules/store/store_route.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@ -19,6 +20,7 @@ import '../../../../shared/business_logic/models/res/sc_room_theme_list_res.dart
|
||||
import '../../../../services/audio/rtc_manager.dart';
|
||||
import '../../../../services/audio/rtm_manager.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/widgets/room/room_msg_item.dart';
|
||||
|
||||
@ -147,11 +149,51 @@ class _BagsThemePageState
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
final selected = selecteTheme;
|
||||
if (selected == null) {
|
||||
return;
|
||||
if (!(selecteTheme?.useTheme ?? false)) {
|
||||
///佩戴
|
||||
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(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);
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -230,15 +272,9 @@ class _BagsThemePageState
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
onTap: () {
|
||||
selecteTheme = res;
|
||||
setState(() {});
|
||||
if (!(res.useTheme ?? false)) {
|
||||
final success = await _use(res, false);
|
||||
if (!success || !mounted) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
showStoreBagFullScreenResourcePreview(
|
||||
context,
|
||||
sourceUrl: res.themeBack,
|
||||
@ -272,46 +308,62 @@ class _BagsThemePageState
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _use(SCRoomThemeListRes res, bool unload) async {
|
||||
void _use(SCRoomThemeListRes res, bool unload) {
|
||||
SCLoadingManager.show();
|
||||
final localizations = SCAppLocalizations.of(context)!;
|
||||
final rtcManager = Provider.of<RealTimeCommunicationManager>(
|
||||
context,
|
||||
listen: false,
|
||||
);
|
||||
final rtmProvider = Provider.of<RtmProvider>(context, listen: false);
|
||||
try {
|
||||
await SCStoreRepositoryImp().themeSwitchUse(res.id ?? "", unload);
|
||||
if (!mounted) {
|
||||
return false;
|
||||
}
|
||||
SCStoreRepositoryImp()
|
||||
.themeSwitchUse(res.id ?? "", unload)
|
||||
.then((value) async {
|
||||
SCLoadingManager.hide();
|
||||
SmartDialog.dismiss(tag: "showPropsDetail");
|
||||
if (!unload) {
|
||||
SCTts.show(localizations.successfulWear);
|
||||
rtcManager.updateRoomBG(res);
|
||||
SCTts.show(SCAppLocalizations.of(context)!.successfulWear);
|
||||
Provider.of<RealTimeCommunicationManager>(
|
||||
context!,
|
||||
listen: false,
|
||||
).updateRoomBG(res);
|
||||
} else {
|
||||
rtcManager.updateRoomBG(SCRoomThemeListRes());
|
||||
Provider.of<RealTimeCommunicationManager>(
|
||||
context!,
|
||||
listen: false,
|
||||
).updateRoomBG(SCRoomThemeListRes());
|
||||
selecteTheme = null;
|
||||
SCTts.show(localizations.successfullyUnloaded);
|
||||
SCTts.show(SCAppLocalizations.of(context)!.successfullyUnloaded);
|
||||
}
|
||||
loadData(1);
|
||||
|
||||
///发送一条消息更新远端房间信息
|
||||
String? groupId =
|
||||
rtcManager.currenRoom?.roomProfile?.roomProfile?.roomAccount;
|
||||
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,
|
||||
);
|
||||
rtmProvider.dispatchMessage(msg, addLocal: false);
|
||||
Provider.of<RtmProvider>(
|
||||
context!,
|
||||
listen: false,
|
||||
).dispatchMessage(msg, addLocal: false);
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
} finally {
|
||||
})
|
||||
.catchError((e) {
|
||||
SCLoadingManager.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _showDetail(SCRoomThemeListRes res) {
|
||||
SmartDialog.show(
|
||||
tag: "showPropsDetail",
|
||||
alignment: Alignment.center,
|
||||
animationType: SmartAnimationType.fade,
|
||||
builder: (_) {
|
||||
return PropsBagThemeDetailDialog(res, () {
|
||||
loadData(1);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,7 +14,6 @@ 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_tts.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 {
|
||||
const VipDetailPage({super.key});
|
||||
@ -471,7 +470,8 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
|
||||
Widget _buildLevelCard() {
|
||||
final assetLevel = _assetLevel;
|
||||
final effectImage = _selectedEffectImage;
|
||||
final backgroundCard = _selectedBackgroundCard;
|
||||
final effectImage = _selectedEffectImage ?? _selectedLongBadge;
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: 351.w,
|
||||
@ -482,7 +482,10 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
Positioned.fill(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8.w),
|
||||
child: _buildVipCardBackground(assetLevel: assetLevel),
|
||||
child: _buildVipCardBackground(
|
||||
resource: backgroundCard,
|
||||
assetLevel: assetLevel,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
@ -522,7 +525,6 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_hasResourceUrl(effectImage))
|
||||
PositionedDirectional(
|
||||
end: 24.w,
|
||||
top: -36.w,
|
||||
@ -539,13 +541,25 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildVipCardBackground({required int assetLevel}) {
|
||||
return Image.asset(
|
||||
Widget _buildVipCardBackground({
|
||||
required SCVipResourceRes? resource,
|
||||
required int assetLevel,
|
||||
}) {
|
||||
final fallback = Image.asset(
|
||||
'sc_images/vip/sc_vip_card_bg_$assetLevel.png',
|
||||
fit: BoxFit.fill,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
);
|
||||
if (!_hasResourceUrl(resource)) {
|
||||
return fallback;
|
||||
}
|
||||
return SCDataCardResourceView(
|
||||
sourceUrl: resource?.sourceResourceUrl,
|
||||
coverUrl: resource?.previewUrl,
|
||||
fit: BoxFit.fill,
|
||||
fallback: fallback,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLevelDivider() {
|
||||
@ -618,28 +632,20 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
}
|
||||
|
||||
Widget _buildDecorationGrid(List<_VipFeatureData> items) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final spacing = 10.w;
|
||||
final maxWidth =
|
||||
constraints.maxWidth.isFinite ? constraints.maxWidth : 314.w;
|
||||
final itemWidth = (maxWidth - spacing * 2) / 3;
|
||||
return Wrap(
|
||||
spacing: spacing,
|
||||
runSpacing: spacing,
|
||||
spacing: 10.w,
|
||||
runSpacing: 10.w,
|
||||
children:
|
||||
items
|
||||
.map(
|
||||
(item) => _buildFeatureTile(
|
||||
item,
|
||||
width: itemWidth,
|
||||
width: item.wide ? 206.w : 98.w,
|
||||
height: 100.w,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPrivilegeGrid(List<_VipFeatureData> items) {
|
||||
@ -766,7 +772,6 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
resource: _selectedAvatarFrame,
|
||||
fallbackTitle: 'Avatar Frame',
|
||||
fallbackIcon: Icons.person_pin_circle_outlined,
|
||||
previewKind: SCStoreItemPreviewKind.avatarFrame,
|
||||
);
|
||||
_addResourceItem(
|
||||
items,
|
||||
@ -785,14 +790,14 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
resource: _selectedEntryEffect,
|
||||
fallbackTitle: 'Entry Effect',
|
||||
fallbackIcon: Icons.airline_seat_recline_extra,
|
||||
wide: true,
|
||||
);
|
||||
_addResourceItem(
|
||||
items,
|
||||
resource: _selectedBackgroundCard,
|
||||
fallbackTitle: 'Profile Card',
|
||||
fallbackIcon: Icons.badge_outlined,
|
||||
previewKind: SCStoreItemPreviewKind.dataCard,
|
||||
previewFit: BoxFit.cover,
|
||||
wide: true,
|
||||
);
|
||||
return items;
|
||||
}
|
||||
@ -810,9 +815,6 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
required String fallbackTitle,
|
||||
IconData? fallbackIcon,
|
||||
bool wide = false,
|
||||
SCStoreItemPreviewKind previewKind = SCStoreItemPreviewKind.resource,
|
||||
BoxFit previewFit = BoxFit.contain,
|
||||
BoxFit tileFit = BoxFit.contain,
|
||||
}) {
|
||||
if (!_hasResource(resource)) {
|
||||
return;
|
||||
@ -822,12 +824,8 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
_resourceTitle(resource, fallbackTitle),
|
||||
null,
|
||||
icon: fallbackIcon,
|
||||
sourceUrl: resource?.sourceResourceUrl,
|
||||
coverUrl: _resourceCoverUrl(resource),
|
||||
resourceUrl: resource?.previewUrl,
|
||||
wide: wide,
|
||||
previewKind: previewKind,
|
||||
previewFit: previewFit,
|
||||
tileFit: tileFit,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -848,18 +846,6 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
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({
|
||||
required SCVipResourceRes? resource,
|
||||
required double width,
|
||||
@ -960,7 +946,7 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
}
|
||||
|
||||
Widget _buildFeatureIcon(_VipFeatureData item, {required double size}) {
|
||||
final url = item.coverUrl?.trim();
|
||||
final url = item.resourceUrl?.trim();
|
||||
if (url != null && url.isNotEmpty) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(6.w),
|
||||
@ -987,15 +973,15 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
}
|
||||
|
||||
Widget _buildFeatureVisual(_VipFeatureData item, {required bool isWide}) {
|
||||
final coverUrl = item.coverUrl?.trim();
|
||||
if (coverUrl != null && coverUrl.isNotEmpty) {
|
||||
final url = item.resourceUrl?.trim();
|
||||
if (url != null && url.isNotEmpty) {
|
||||
return netImage(
|
||||
url: coverUrl,
|
||||
fit: item.tileFit,
|
||||
url: url,
|
||||
fit: BoxFit.contain,
|
||||
noDefaultImg: true,
|
||||
errorWidget:
|
||||
item.assetPath != null
|
||||
? Image.asset(item.assetPath!, fit: item.tileFit)
|
||||
? Image.asset(item.assetPath!, fit: BoxFit.contain)
|
||||
: isWide
|
||||
? Center(child: _buildWideRewardPlaceholder())
|
||||
: _buildRewardPlaceholder(item.icon ?? Icons.auto_awesome),
|
||||
@ -1076,22 +1062,7 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
required double height,
|
||||
}) {
|
||||
final isWide = item.wide;
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap:
|
||||
item.canPreview
|
||||
? () => showStoreBagFullScreenResourcePreview(
|
||||
context,
|
||||
sourceUrl: item.sourceUrl,
|
||||
coverUrl: item.coverUrl,
|
||||
previewKind: item.previewKind,
|
||||
fit: item.previewFit,
|
||||
)
|
||||
: null,
|
||||
child: Semantics(
|
||||
label: item.title,
|
||||
button: item.canPreview,
|
||||
child: Container(
|
||||
return Container(
|
||||
width: width,
|
||||
height: height,
|
||||
decoration: BoxDecoration(
|
||||
@ -1100,9 +1071,24 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
color: Colors.black.withValues(alpha: 0.38),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 8.w),
|
||||
child: Center(child: _buildFeatureVisual(item, isWide: isWide)),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(child: _buildFeatureVisual(item, isWide: isWide)),
|
||||
SizedBox(height: 5.w),
|
||||
Text(
|
||||
item.title,
|
||||
maxLines: 2,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -1434,27 +1420,15 @@ class _VipFeatureData {
|
||||
this.title,
|
||||
this.assetPath, {
|
||||
this.icon,
|
||||
this.sourceUrl,
|
||||
this.coverUrl,
|
||||
this.resourceUrl,
|
||||
this.wide = false,
|
||||
this.previewKind = SCStoreItemPreviewKind.resource,
|
||||
this.previewFit = BoxFit.contain,
|
||||
this.tileFit = BoxFit.contain,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String? assetPath;
|
||||
final IconData? icon;
|
||||
final String? sourceUrl;
|
||||
final String? coverUrl;
|
||||
final String? resourceUrl;
|
||||
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 {
|
||||
|
||||
@ -37,6 +37,11 @@ class _RechargePageState extends State<RechargePage> {
|
||||
context,
|
||||
listen: false,
|
||||
).initializePaymentProcessor(context);
|
||||
} else if (Platform.isIOS) {
|
||||
Provider.of<IOSPaymentProcessor>(
|
||||
context,
|
||||
listen: false,
|
||||
).initializePaymentProcessor(context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,7 +60,7 @@ class _RechargePageState extends State<RechargePage> {
|
||||
.getRechargePageScaffoldBackgroundColor(),
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: SocialChatStandardAppBar(
|
||||
title: SCAppLocalizations.of(context)!.wallet,
|
||||
title: SCAppLocalizations.of(context)!.recharge,
|
||||
actions: [
|
||||
GestureDetector(
|
||||
onTap: _showRecordDialog,
|
||||
@ -78,10 +83,7 @@ class _RechargePageState extends State<RechargePage> {
|
||||
_buildWalletSection(),
|
||||
SizedBox(height: 36.w),
|
||||
Expanded(
|
||||
child:
|
||||
Platform.isIOS
|
||||
? const SizedBox.shrink()
|
||||
: Padding(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(12.w, 8.w, 12.w, 0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -89,10 +91,24 @@ class _RechargePageState extends State<RechargePage> {
|
||||
_buildMethodSection(),
|
||||
SizedBox(height: 14.w),
|
||||
Expanded(child: _buildNativeProductList()),
|
||||
if (Platform.isIOS) ...[
|
||||
SizedBox(height: 12.w),
|
||||
_buildFooterButton(
|
||||
title:
|
||||
SCAppLocalizations.of(context)!.recharge,
|
||||
SCAppLocalizations.of(
|
||||
context,
|
||||
)!.restorePurchases,
|
||||
onTap: () {
|
||||
Provider.of<IOSPaymentProcessor>(
|
||||
context,
|
||||
listen: false,
|
||||
).recoverTransactions();
|
||||
},
|
||||
),
|
||||
],
|
||||
SizedBox(height: 12.w),
|
||||
_buildFooterButton(
|
||||
title: SCAppLocalizations.of(context)!.recharge,
|
||||
onTap: _processNativePurchase,
|
||||
),
|
||||
SizedBox(height: 14.w),
|
||||
|
||||
@ -14,7 +14,6 @@ import 'package:yumi/app/constants/sc_screen.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_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_url_launcher_utils.dart';
|
||||
import 'package:yumi/main.dart';
|
||||
@ -41,7 +40,6 @@ class WebViewPage extends StatefulWidget {
|
||||
|
||||
class _WebViewPageState extends State<WebViewPage> {
|
||||
late WebViewController _controller;
|
||||
late final SCRoomTopLayerGuardLease _topLayerGuardLease;
|
||||
String _title = "";
|
||||
double _progress = 0.0;
|
||||
bool _isLoading = true;
|
||||
@ -50,7 +48,6 @@ class _WebViewPageState extends State<WebViewPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_topLayerGuardLease = SCRoomTopLayerGuard().acquire(reason: 'webview_page');
|
||||
urlLink = SCH5UrlUtils.appendAppParams(widget.url);
|
||||
if (kDebugMode) {
|
||||
debugPrint('[WebViewPage] load url=${_maskSensitiveUrl(urlLink)}');
|
||||
@ -180,7 +177,6 @@ class _WebViewPageState extends State<WebViewPage> {
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.loadRequest(Uri.parse('about:blank'));
|
||||
_topLayerGuardLease.release();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
@ -20,11 +20,9 @@ class RoomRtcEnterRoomConfig {
|
||||
}
|
||||
|
||||
class RoomRtcAnchorConfig {
|
||||
const RoomRtcAnchorConfig({required this.muted, bool? publishMuted})
|
||||
: publishMuted = publishMuted ?? muted;
|
||||
const RoomRtcAnchorConfig({required this.muted});
|
||||
|
||||
final bool muted;
|
||||
final bool publishMuted;
|
||||
}
|
||||
|
||||
class RoomRtcAudioMixingConfig {
|
||||
|
||||
@ -34,7 +34,6 @@ abstract class RoomTrtcAudioEffectManager {
|
||||
void resumePlayMusic(int id);
|
||||
void setMusicPublishVolume(int id, int volume);
|
||||
void setMusicPlayoutVolume(int id, int volume);
|
||||
void setVoiceCaptureVolume(int volume);
|
||||
int getMusicCurrentPosInMS(int id);
|
||||
int getMusicDurationInMS(String path);
|
||||
void seekMusicToPosInTime(int id, int pts);
|
||||
@ -157,11 +156,6 @@ class TencentRoomTrtcAudioEffectManager implements RoomTrtcAudioEffectManager {
|
||||
_manager.setMusicPlayoutVolume(id, volume);
|
||||
}
|
||||
|
||||
@override
|
||||
void setVoiceCaptureVolume(int volume) {
|
||||
_manager.setVoiceCaptureVolume(volume);
|
||||
}
|
||||
|
||||
@override
|
||||
int getMusicCurrentPosInMS(int id) {
|
||||
return _manager.getMusicCurrentPosInMS(id);
|
||||
@ -370,8 +364,7 @@ class TrtcRoomRtcEngineAdapter implements RoomRtcEngineAdapter {
|
||||
try {
|
||||
cloud.switchRole(TRTCRoleType.anchor);
|
||||
cloud.startLocalAudio(TRTCAudioQuality.defaultMode);
|
||||
_musicManager?.setVoiceCaptureVolume(config.muted ? 0 : 100);
|
||||
cloud.muteLocalAudio(config.publishMuted);
|
||||
cloud.muteLocalAudio(config.muted);
|
||||
_isLocalAudioStarted = true;
|
||||
return true;
|
||||
} catch (_) {
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
@ -14,17 +12,8 @@ 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/sc_user_identity_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 {
|
||||
static const List<String> _cpProfileRelationTypes = [
|
||||
'CP',
|
||||
'BROTHER',
|
||||
'SISTERS',
|
||||
];
|
||||
|
||||
SCUserProfileCmd? editUser;
|
||||
|
||||
void updateUserAvatar(String avatar) {
|
||||
@ -87,38 +76,9 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
||||
currentUser.setUserProfile(profile);
|
||||
AccountStorage().setCurrentUser(currentUser);
|
||||
}
|
||||
if ((userProfile?.id ?? "").isNotEmpty && userProfile?.id == profile.id) {
|
||||
userProfile = profile;
|
||||
}
|
||||
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({
|
||||
bool loadGuardCount = true,
|
||||
bool refreshFamilyData = false,
|
||||
@ -130,7 +90,6 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
||||
}
|
||||
var userInfo = await SCAccountRepository().loadUserInfo(userId);
|
||||
userInfo = await _withCurrentVipLevel(userInfo, userId);
|
||||
userInfo = await _withCpCabinProfiles(userInfo, userId);
|
||||
syncCurrentUserProfile(userInfo);
|
||||
}
|
||||
|
||||
@ -161,208 +120,25 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
||||
|
||||
/// 当前正在查看的个人详情资料,不用于承载登录用户自己的 Me 页数据。
|
||||
SocialChatUserProfile? userProfile;
|
||||
Object? userProfileLoadError;
|
||||
bool isUserProfileLoading = false;
|
||||
int _userInfoRequestSerial = 0;
|
||||
|
||||
Future<void> getUserInfoById(String userId) {
|
||||
return _loadUserInfoById(userId, clearBeforeLoad: true);
|
||||
}
|
||||
|
||||
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) {
|
||||
void getUserInfoById(String userId) async {
|
||||
userProfile = null;
|
||||
}
|
||||
userProfileLoadError = null;
|
||||
isUserProfileLoading = true;
|
||||
notifyListeners();
|
||||
final loadedProfile = await SCAccountRepository().loadUserInfo(userId);
|
||||
userProfile = await _withCurrentVipLevel(loadedProfile, userId);
|
||||
|
||||
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;
|
||||
Object? userCardLoadError;
|
||||
bool isUserCardLoading = false;
|
||||
bool isUserCardRelationLoading = false;
|
||||
int _userCardRequestSerial = 0;
|
||||
int _userCardSessionSerial = 0;
|
||||
int? _activeUserCardSessionSerial;
|
||||
|
||||
// List<UserCountGuardRes>? userCountGuardResList;
|
||||
|
||||
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();
|
||||
void roomUserCard(String roomId, String userId) async {
|
||||
userCardInfo = null;
|
||||
userCardLoadError = null;
|
||||
isUserCardLoading = true;
|
||||
isUserCardRelationLoading = false;
|
||||
userCardInfo = await SCChatRoomRepository().roomUserCard(roomId, userId);
|
||||
userCardInfo = await _withCurrentVipLevelForCard(userCardInfo, userId);
|
||||
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(
|
||||
@ -373,373 +149,13 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
||||
if (card == null || profile == null) {
|
||||
return card;
|
||||
}
|
||||
var mergedProfile = await _withCurrentVipLevel(profile, userId);
|
||||
mergedProfile = await _withCpCabinProfiles(mergedProfile, userId);
|
||||
final mergedProfile = await _withCurrentVipLevel(profile, userId);
|
||||
if (mergedProfile == profile) {
|
||||
return card;
|
||||
}
|
||||
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(
|
||||
SocialChatUserProfile profile,
|
||||
String userId,
|
||||
@ -777,12 +193,10 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
||||
}
|
||||
|
||||
bool _isCurrentUserId(String userId) {
|
||||
final currentUserId =
|
||||
AccountStorage().getCurrentUser()?.userProfile?.id?.trim();
|
||||
final normalizedUserId = userId.trim();
|
||||
final currentUserId = AccountStorage().getCurrentUser()?.userProfile?.id;
|
||||
return currentUserId != null &&
|
||||
currentUserId.isNotEmpty &&
|
||||
currentUserId == normalizedUserId;
|
||||
currentUserId == userId;
|
||||
}
|
||||
|
||||
Future<SCVipStatusRes?> _loadCurrentVipStatus() async {
|
||||
@ -904,7 +318,7 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
Future<bool> cpRlationshipProcessApply(
|
||||
void cpRlationshipProcessApply(
|
||||
BuildContext ct,
|
||||
String id,
|
||||
bool status,
|
||||
@ -912,183 +326,8 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
||||
final successMessage = SCAppLocalizations.of(ct)!.operationSuccessful;
|
||||
try {
|
||||
await SCAccountRepository().cpRelationshipProcessApply(id, status);
|
||||
await fetchUserProfileData(loadGuardCount: false);
|
||||
await refreshLoadedCpProfiles();
|
||||
fetchUserProfileData(loadGuardCount: false);
|
||||
SCTts.show(successMessage);
|
||||
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();
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@ 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/gift_backpack_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_room_emoji_res.dart';
|
||||
|
||||
@ -37,9 +36,6 @@ class SCAppGeneralManager extends ChangeNotifier {
|
||||
bool _hasGiftListLoaded = false;
|
||||
bool _isFetchingGiftBackpack = false;
|
||||
bool _hasGiftBackpackLoaded = false;
|
||||
bool _isFetchingFirstRechargeRewardHome = false;
|
||||
bool _hasFirstRechargeRewardHomeLoaded = false;
|
||||
SCFirstRechargeRewardHomeRes? _firstRechargeRewardHome;
|
||||
|
||||
Map<String, List<SocialChatGiftRes>> giftByTab = {};
|
||||
Map<String, List<SocialChatGiftRes>> activityGiftByTab = {};
|
||||
@ -63,40 +59,10 @@ class SCAppGeneralManager extends ChangeNotifier {
|
||||
bool get isGiftBackpackLoading =>
|
||||
_isFetchingGiftBackpack && !_hasGiftBackpackLoaded;
|
||||
|
||||
bool get shouldShowFirstRechargeReward =>
|
||||
_firstRechargeRewardHome?.shouldShowRewardDialog ?? false;
|
||||
|
||||
SCFirstRechargeRewardHomeRes? get firstRechargeRewardHome =>
|
||||
_firstRechargeRewardHome;
|
||||
|
||||
bool isGiftTabLoading(String type) {
|
||||
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 {
|
||||
if (_isFetchingCountryList) {
|
||||
@ -213,28 +179,24 @@ class SCAppGeneralManager extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
String _displayGiftTab(String? giftTab) {
|
||||
return giftTab == "EXCLUSIVE" ? "CUSTOMIZED" : (giftTab ?? "");
|
||||
}
|
||||
|
||||
void _rebuildGiftTabs({required bool includeCustomized}) {
|
||||
giftByTab.clear();
|
||||
_giftByIdMap.clear();
|
||||
_giftByStandardIdMap.clear();
|
||||
for (var gift in giftResList) {
|
||||
final tab = _displayGiftTab(gift.giftTab);
|
||||
var gmap = giftByTab[tab];
|
||||
giftByTab[gift.giftTab];
|
||||
var gmap = giftByTab[gift.giftTab];
|
||||
gmap ??= [];
|
||||
gmap.add(gift);
|
||||
giftByTab[tab] = gmap;
|
||||
giftByTab[gift.giftTab!] = gmap;
|
||||
var gAllMap = giftByTab["ALL"];
|
||||
gAllMap ??= [];
|
||||
if (tab != "NSCIONAL_FLAG" &&
|
||||
tab != "ACTIVITY" &&
|
||||
tab != "LUCKY_GIFT" &&
|
||||
tab != "CP" &&
|
||||
tab != "CUSTOMIZED" &&
|
||||
tab != "MAGIC") {
|
||||
if (gift.giftTab != "NSCIONAL_FLAG" &&
|
||||
gift.giftTab != "ACTIVITY" &&
|
||||
gift.giftTab != "LUCKY_GIFT" &&
|
||||
gift.giftTab != "CP" &&
|
||||
gift.giftTab != "CUSTOMIZED" &&
|
||||
gift.giftTab != "MAGIC") {
|
||||
gAllMap.add(gift);
|
||||
}
|
||||
giftByTab["ALL"] = gAllMap;
|
||||
|
||||
@ -1,11 +1,7 @@
|
||||
import 'dart:convert';
|
||||
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: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_mode.dart';
|
||||
import 'package:yumi/shared/tools/sc_permission_utils.dart';
|
||||
@ -19,10 +15,6 @@ class LocalMusicScanner {
|
||||
Future<List<SCMusicFolderMode>> scanMp3Folders({
|
||||
List<SCMusicMode> addedSongs = const [],
|
||||
}) async {
|
||||
if (Platform.isIOS) {
|
||||
return _pickIosMp3Files(addedSongs: addedSongs);
|
||||
}
|
||||
|
||||
final hasPermission = await SCPermissionUtils.checkAudioPermission();
|
||||
if (!hasPermission) {
|
||||
return const <SCMusicFolderMode>[];
|
||||
@ -104,98 +96,6 @@ class LocalMusicScanner {
|
||||
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) {
|
||||
final extension = _songString(
|
||||
song,
|
||||
@ -208,72 +108,6 @@ class LocalMusicScanner {
|
||||
_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) {
|
||||
try {
|
||||
return File(path).existsSync();
|
||||
|
||||