Compare commits

..

No commits in common. "main" and "test" have entirely different histories.
main ... test

295 changed files with 8333 additions and 36570 deletions

View File

@ -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": "تفاصيل الغرفة",

View File

@ -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": "রুম বিবরণ",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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ı",

View File

@ -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>

View File

@ -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. 禁用 蓝牙 (如果你没用到蓝牙,建议也关掉)

View File

@ -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

View File

@ -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 = "";

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 67 KiB

View File

@ -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
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 811 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 811 KiB

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 KiB

After

Width:  |  Height:  |  Size: 68 B

View File

@ -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
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

View File

@ -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>

File diff suppressed because it is too large Load Diff

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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/'; //

View File

@ -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;
}
///

View File

@ -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";

View File

@ -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 =>

View File

@ -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,
),
);
}
}

View File

@ -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() {}
}

View File

@ -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)),
),
);
}
}

View File

@ -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',

View File

@ -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"),
),
],
),
),
],
),
);
}
}

View File

@ -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);
}
}

View File

@ -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],
),
),
);

View File

@ -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,

View File

@ -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,
),
),
),
);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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;
}

View File

@ -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 =

View File

@ -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;
}

View File

@ -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()) {

View File

@ -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 {

View File

@ -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> {
),
),
),
),
);
},
);

View File

@ -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));
});
}

View File

@ -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),

View File

@ -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,
),

View File

@ -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,

View File

@ -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,
),
),
);

View File

@ -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 })
);
}
})();
''';
}
}

View File

@ -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']),

View File

@ -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,

View File

@ -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'),
),
],
),
],
),
),
);
}
}

View File

@ -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;
}

View File

@ -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,
),
],
);

View File

@ -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();
},
);
}

View File

@ -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,
),
],
);
}

View File

@ -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();
});
}
}

View File

@ -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();
});
}
}

View File

@ -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();
});
}
}

View File

@ -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();
});
}
}

View File

@ -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);
});
},
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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 {

View File

@ -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),

View File

@ -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();
}

View File

@ -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 {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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 (_) {

View File

@ -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) {}
}
}

View File

@ -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;

View File

@ -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();

Some files were not shown because too many files have changed in this diff Show More