5 Commits e6d880d0de ... 24c720e0bc

Author SHA1 Message Date
  DoggyZhang 24c720e0bc 翻译,进房退上一个房 1 day ago
  DoggyZhang 2a2f440ca2 媒体冲突 1 day ago
  DoggyZhang c30bc3da11 ui fix 1 day ago
  DoggyZhang 0b0436161c 开放金豆入口 1 day ago
  DoggyZhang 0170674046 送礼 1 day ago
97 changed files with 1726 additions and 944 deletions
  1. 1 1
      app/dependencies/releaseRuntimeClasspath.txt
  2. 2 0
      app/src/main/java/com/adealink/weparty/commonui/imageview/AvatarView.kt
  3. 34 2
      app/src/main/java/com/adealink/weparty/commonui/widget/NumberIncreaseView.kt
  4. 1 1
      app/src/main/java/com/adealink/weparty/media/IMediaManager.kt
  5. 105 0
      app/src/main/java/com/adealink/weparty/media/MediaConflictDialog.kt
  6. 5 3
      app/src/main/java/com/adealink/weparty/media/MediaManager.kt
  7. 49 0
      app/src/main/java/com/adealink/weparty/media/datasource/MediaLocalService.kt
  8. 1 1
      app/src/main/java/com/adealink/weparty/module/call/CallModule.kt
  9. 9 0
      app/src/main/java/com/adealink/weparty/module/gift/GiftModule.kt
  10. 3 0
      app/src/main/java/com/adealink/weparty/module/gift/IGiftService.kt
  11. 2 1
      app/src/main/java/com/adealink/weparty/module/gift/viewmodel/IGiftViewModel.kt
  12. 1 1
      app/src/main/java/com/adealink/weparty/module/room/RoomModule.kt
  13. 28 0
      app/src/main/java/com/adealink/weparty/module/room/chat/data/Message.kt
  14. 37 0
      app/src/main/java/com/adealink/weparty/module/room/chat/data/Notification.kt
  15. 2 2
      app/src/main/java/com/adealink/weparty/module/room/data/RoomData.kt
  16. 3 1
      app/src/main/java/com/adealink/weparty/storage/Constants.kt
  17. BIN
      app/src/main/res/drawable-xhdpi/common_go_white_bold_ic.png
  18. 134 0
      app/src/main/res/layout/dialog_media_conflict.xml
  19. 5 5
      app/src/main/res/layout/layout_common_dialog.xml
  20. 3 2
      app/src/main/res/values-in/strings.xml
  21. 2 1
      app/src/main/res/values-zh/strings.xml
  22. 4 0
      app/src/main/res/values/attrs.xml
  23. 4 1
      app/src/main/res/values/strings.xml
  24. 2 2
      gradle/libs.versions.toml
  25. 35 43
      module/call/src/main/java/com/adealink/weparty/call/CallServiceImpl.kt
  26. 0 2
      module/call/src/main/java/com/adealink/weparty/call/datasource/local/CallLocalService.kt
  27. 5 0
      module/gift/src/main/java/com/adealink/weparty/gift/GiftServiceImpl.kt
  28. 4 0
      module/gift/src/main/java/com/adealink/weparty/gift/manager/GiftManager.kt
  29. 3 0
      module/gift/src/main/java/com/adealink/weparty/gift/manager/IGiftManager.kt
  30. 3 4
      module/gift/src/main/java/com/adealink/weparty/gift/viewmodel/GiftViewModel.kt
  31. 0 2
      module/playmate/src/main/res/layout/fragment_find_partner_category.xml
  32. 4 4
      module/playmate/src/main/res/layout/item_playmate_home_list.xml
  33. 1 0
      module/profile/src/main/res/layout/layout_user_profile_header.xml
  34. 33 30
      module/room/src/main/java/com/adealink/weparty/room/RoomServiceImpl.kt
  35. 2 0
      module/room/src/main/java/com/adealink/weparty/room/applymic/ApplyMicFilterDialog.kt
  36. 6 0
      module/room/src/main/java/com/adealink/weparty/room/chat/ChatMessageFragment.kt
  37. 95 0
      module/room/src/main/java/com/adealink/weparty/room/chat/adapter/SendGiftMessageViewBinder.kt
  38. 5 5
      module/room/src/main/java/com/adealink/weparty/room/chat/viewmodel/ChatMessageViewModel.kt
  39. 20 12
      module/room/src/main/java/com/adealink/weparty/room/chatroom/page/dispatchcenter/DispatchRoomEventViewModel.kt
  40. 11 0
      module/room/src/main/java/com/adealink/weparty/room/datasource/local/RoomLocalService.kt
  41. 176 92
      module/room/src/main/java/com/adealink/weparty/room/gift/RoomGiftPanelDialog.kt
  42. 46 7
      module/room/src/main/java/com/adealink/weparty/room/gift/adapter/RoomGiftItemViewBinder.kt
  43. 29 0
      module/room/src/main/java/com/adealink/weparty/room/gift/adapter/RoomGiftTargetUserItemViewBinder.kt
  44. 14 6
      module/room/src/main/java/com/adealink/weparty/room/gift/adapter/RoomGiftUserItemViewBinder.kt
  45. 17 0
      module/room/src/main/java/com/adealink/weparty/room/gift/data/GiftData.kt
  46. 129 0
      module/room/src/main/java/com/adealink/weparty/room/gift/dialog/DiamondRechargeDialog.kt
  47. 74 0
      module/room/src/main/java/com/adealink/weparty/room/gift/viewmodel/RoomGiftViewModel.kt
  48. 2 0
      module/room/src/main/java/com/adealink/weparty/room/interceptor/EnterRoomUriInterceptor.kt
  49. 2 2
      module/room/src/main/java/com/adealink/weparty/room/invite/InviteMicDialog.kt
  50. 3 2
      module/room/src/main/java/com/adealink/weparty/room/sdk/controller/IMessageController.kt
  51. 4 1
      module/room/src/main/java/com/adealink/weparty/room/sdk/controller/impl/JoinController.kt
  52. 38 30
      module/room/src/main/java/com/adealink/weparty/room/sdk/controller/impl/MessageController.kt
  53. 4 0
      module/room/src/main/java/com/adealink/weparty/room/viewmodel/RoomViewModelFactory.kt
  54. BIN
      module/room/src/main/res/drawable-xhdpi/room_gift_selected_bg.png
  55. BIN
      module/room/src/main/res/drawable-xhdpi/room_number_add_ic.png
  56. BIN
      module/room/src/main/res/drawable-xhdpi/room_number_minus_disable_ic.png
  57. BIN
      module/room/src/main/res/drawable-xhdpi/room_number_minus_ic.png
  58. 6 0
      module/room/src/main/res/drawable/room_bottom_send_gift_button.xml
  59. 0 0
      module/room/src/main/res/drawable/room_gift_user_selected_bg.xml
  60. 0 0
      module/room/src/main/res/drawable/room_gift_user_tag_bg.xml
  61. 0 0
      module/room/src/main/res/drawable/room_gift_user_tag_bg_selected.xml
  62. 0 5
      module/room/src/main/res/drawable/shape_room_gift_btn_send_bg.xml
  63. 0 5
      module/room/src/main/res/drawable/shape_room_gift_btn_send_bg_selected.xml
  64. 0 5
      module/room/src/main/res/drawable/shape_room_gift_btn_send_bg_unselected.xml
  65. 0 5
      module/room/src/main/res/drawable/shape_room_gift_item_bg.xml
  66. 0 6
      module/room/src/main/res/drawable/shape_room_gift_item_bg_selected.xml
  67. 0 5
      module/room/src/main/res/drawable/shape_room_gift_panel_bg.xml
  68. 0 5
      module/room/src/main/res/drawable/shape_room_gift_quantity_bg.xml
  69. 132 0
      module/room/src/main/res/layout/dialog_diamond_recharge.xml
  70. 0 2
      module/room/src/main/res/layout/dialog_room_apply_mic_host_list.xml
  71. 128 99
      module/room/src/main/res/layout/dialog_room_gift_panel.xml
  72. 9 5
      module/room/src/main/res/layout/dialog_room_invite_mic.xml
  73. 1 1
      module/room/src/main/res/layout/dialog_room_member.xml
  74. 1 1
      module/room/src/main/res/layout/fragment_dispatch_room_bottom_operate.xml
  75. 8 3
      module/room/src/main/res/layout/fragment_room_apply_mic_list.xml
  76. 1 0
      module/room/src/main/res/layout/item_message_enter_room.xml
  77. 69 0
      module/room/src/main/res/layout/item_message_send_gift.xml
  78. 1 0
      module/room/src/main/res/layout/item_message_text.xml
  79. 37 34
      module/room/src/main/res/layout/item_room_gift.xml
  80. 42 0
      module/room/src/main/res/layout/item_room_gift_target_user.xml
  81. 23 15
      module/room/src/main/res/layout/item_room_gift_user.xml
  82. 0 1
      module/room/src/main/res/layout/layout_room_member_playmate_category_item.xml
  83. 1 1
      module/room/src/main/res/layout/layout_room_playmate_category_filter_item.xml
  84. 10 0
      module/room/src/main/res/values-in/strings.xml
  85. 10 0
      module/room/src/main/res/values-zh/strings.xml
  86. 1 1
      module/room/src/main/res/values/dimens.xml
  87. 10 0
      module/room/src/main/res/values/strings.xml
  88. 0 2
      module/share/src/main/res/layout/dialog_qr_code_share.xml
  89. 0 5
      module/wallet/src/main/java/com/adealink/weparty/wallet/comp/MyIncomeComp.kt
  90. 25 3
      module/wallet/src/main/java/com/adealink/weparty/wallet/pay/PayManager.kt
  91. 2 2
      module/wallet/src/main/java/com/adealink/weparty/wallet/recharge/dialog/RechargeDialog.kt
  92. 7 2
      module/wallet/src/main/java/com/adealink/weparty/wallet/recharge/fragment/BeanExchangedFragment.kt
  93. 0 1
      module/wallet/src/main/res/values-in/strings.xml
  94. 0 1
      module/wallet/src/main/res/values-zh/strings.xml
  95. 0 1
      module/wallet/src/main/res/values/strings.xml
  96. 0 255
      pull_and_fill_translations.py
  97. 0 207
      push_translations.py

+ 1 - 1
app/dependencies/releaseRuntimeClasspath.txt

@@ -250,7 +250,7 @@ com.wenext.android:frame-guide:6.0.2
 com.wenext.android:frame-image:6.0.10
 com.wenext.android:frame-locale:6.0.8
 com.wenext.android:frame-log:6.0.6
-com.wenext.android:frame-media:6.0.5
+com.wenext.android:frame-media:6.0.6-beta-1
 com.wenext.android:frame-mvvm:6.0.0
 com.wenext.android:frame-network:6.1.4
 com.wenext.android:frame-oss:6.0.5

+ 2 - 0
app/src/main/java/com/adealink/weparty/commonui/imageview/AvatarView.kt

@@ -12,6 +12,8 @@ class AvatarView @JvmOverloads constructor(
         setIsAsCircle(true)
         setBackgroundResource(R.drawable.common_avatar_bg)
         setDefaultImageResId(R.drawable.common_default_avatar_ic)
+        hierarchy.setPlaceholderImage(R.drawable.common_fail_img_ic, ScalingUtils.ScaleType.CENTER_CROP)
+        hierarchy.setFailureImage(R.drawable.common_fail_img_ic, ScalingUtils.ScaleType.CENTER_CROP)
         hierarchy.actualImageScaleType = ScalingUtils.ScaleType.CENTER_CROP
     }
 }

+ 34 - 2
app/src/main/java/com/adealink/weparty/commonui/widget/NumberIncreaseView.kt

@@ -6,6 +6,7 @@ import android.view.LayoutInflater
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.core.content.withStyledAttributes
 import com.adealink.weparty.R
+import com.adealink.weparty.commonui.widget.util.getColorX
 import com.adealink.weparty.databinding.LayoutNumberIncreaseViewBinding
 
 class NumberIncreaseView @JvmOverloads constructor(
@@ -20,6 +21,12 @@ class NumberIncreaseView @JvmOverloads constructor(
     private val binding =
         LayoutNumberIncreaseViewBinding.inflate(LayoutInflater.from(context), this)
 
+    private var numberTextColor = getColorX(R.color.color_FF1D2129)
+
+    private var numberAddIc = R.drawable.common_number_add_ic
+    private var numberMinusIc = R.drawable.common_number_minus_ic
+    private var numberMinusDisableIc = R.drawable.common_number_minus_diable_ic
+
     private var number = 0
 
     private var listener: OnNumberChangedListener? = null
@@ -33,12 +40,37 @@ class NumberIncreaseView @JvmOverloads constructor(
                 R.styleable.NumberIncreaseView_default_number,
                 0
             ).coerceIn(MIN_NUMBER, MAX_NUMBER)
+
+            numberTextColor = getColor(
+                R.styleable.NumberIncreaseView_number_text_color,
+                numberTextColor
+            )
+
+            numberAddIc = getResourceId(
+                R.styleable.NumberIncreaseView_number_add_src,
+                numberAddIc
+            )
+
+            numberMinusIc = getResourceId(
+                R.styleable.NumberIncreaseView_number_minus_src,
+                numberMinusIc
+            )
+
+            numberMinusDisableIc = getResourceId(
+                R.styleable.NumberIncreaseView_number_minus_disable_src,
+                numberMinusDisableIc
+            )
         }
 
         binding.tvNumber.text = number.toString()
+        binding.tvNumber.setTextColor(numberTextColor)
+
+        binding.ivMinus.setImageResource(numberMinusIc)
         binding.ivMinus.setOnClickListener {
             clickMinus()
         }
+
+        binding.ivAdd.setImageResource(numberAddIc)
         binding.ivAdd.setOnClickListener {
             clickAdd()
         }
@@ -64,9 +96,9 @@ class NumberIncreaseView @JvmOverloads constructor(
         this.number = nextNumber
         binding.tvNumber.text = number.toString()
         if (this.number <= MIN_NUMBER) {
-            binding.ivMinus.setImageResource(R.drawable.common_number_minus_diable_ic)
+            binding.ivMinus.setImageResource(numberMinusDisableIc)
         } else {
-            binding.ivMinus.setImageResource(R.drawable.common_number_minus_ic)
+            binding.ivMinus.setImageResource(numberMinusIc)
         }
         listener?.onNumberChanged(nextNumber)
     }

+ 1 - 1
app/src/main/java/com/adealink/weparty/media/IMediaManager.kt

@@ -7,7 +7,7 @@ interface IMediaManager {
 
     suspend fun conflictHandle(mediaInfo: MediaInfo): Rlt<Any>
 
-    fun getJoinedMediaRoomId(): Long?
+    fun getJoinedMediaRoomId(): String?
 
     suspend fun rejoinRoom()
 

+ 105 - 0
app/src/main/java/com/adealink/weparty/media/MediaConflictDialog.kt

@@ -0,0 +1,105 @@
+package com.adealink.weparty.media
+
+import android.content.DialogInterface
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.view.Gravity
+import android.view.Window
+import android.view.WindowManager
+import androidx.fragment.app.FragmentManager
+import com.adealink.frame.aab.util.getCompatString
+import com.adealink.frame.media.MediaInfo
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.util.onClick
+import com.adealink.weparty.R
+import com.adealink.weparty.commonui.dialogfragment.BaseDialogFragment
+import com.adealink.weparty.commonui.ext.dp
+import com.adealink.weparty.databinding.DialogMediaConflictBinding
+import com.adealink.weparty.media.datasource.MediaLocalService
+
+class MediaConflictDialog : BaseDialogFragment(R.layout.dialog_media_conflict) {
+
+    companion object {
+        @JvmStatic
+        fun show(
+            exitMediaInfo: MediaInfo,
+            enterMediaInfo: MediaInfo,
+            fm: FragmentManager,
+            onPositive: (() -> Unit)? = null,
+            onNegative: (() -> Unit)? = null,
+            onDismiss: (() -> Unit)? = null,
+        ) {
+            MediaConflictDialog().apply {
+                this.exitMediaInfo = exitMediaInfo
+                this.enterMediaInfo = enterMediaInfo
+                this.onPositive = onPositive
+                this.onNegative = onNegative
+                this.onDismiss = onDismiss
+            }.show(fm, "CallMediaConflictDialog")
+        }
+    }
+
+    private val binding by viewBinding(DialogMediaConflictBinding::bind)
+
+    private var exitMediaInfo: MediaInfo? = null
+    private var enterMediaInfo: MediaInfo? = null
+    private var onPositive: (() -> Unit)? = null
+    private var onNegative: (() -> Unit)? = null
+    private var onDismiss: (() -> Unit)? = null
+
+    private var remind = false
+
+    override fun initViews() {
+        super.initViews()
+        binding.tvMessage.text = getCompatString(
+            R.string.room_media_conflict_tips,
+            exitMediaInfo?.mediaName ?: "",
+            enterMediaInfo?.mediaName ?: ""
+        )
+        binding.clNeverRemind.setOnClickListener {
+            remind = !remind
+            MediaLocalService.updateConflictRemind(exitMediaInfo, enterMediaInfo, remind)
+            updateDontRemind()
+        }
+        updateDontRemind()
+        binding.topCloseBtn.onClick {
+            onPositive?.invoke()
+            dismiss()
+        }
+        binding.btnCancel.setOnClickListener {
+            onNegative?.invoke()
+            dismiss()
+        }
+        binding.btnConfirm.setOnClickListener {
+            onPositive?.invoke()
+            dismiss()
+        }
+    }
+
+    private fun updateDontRemind() {
+        if (remind) {
+            binding.ivNeverRemind.setImageResource(R.drawable.common_check_selected_ic)
+        } else {
+            binding.ivNeverRemind.setImageResource(R.drawable.common_check_unselected_ic)
+        }
+    }
+
+    override fun onDismiss(dialog: DialogInterface) {
+        super.onDismiss(dialog)
+        onDismiss?.invoke()
+    }
+
+    override fun resetWindowAttributes(window: Window) {
+        super.resetWindowAttributes(window)
+        window.setLayout(
+            300.dp(),
+            WindowManager.LayoutParams.WRAP_CONTENT
+        )
+        window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+        val attr = window.attributes
+        attr.gravity = Gravity.CENTER
+        attr.dimAmount = 0.7f
+        window.attributes = attr
+    }
+
+}

+ 5 - 3
app/src/main/java/com/adealink/weparty/media/MediaManager.kt

@@ -6,6 +6,8 @@ import com.adealink.frame.media.IMediaOperatorGet
 import com.adealink.frame.media.MediaInfo
 import com.adealink.frame.media.constant.TAG_MEDIA
 import com.adealink.frame.media.data.RtcType
+import com.adealink.weparty.module.call.CallModule
+import com.adealink.weparty.module.room.RoomModule
 
 enum class MediaType(val type: Int) {
     UNKNOWN(0), //未知
@@ -16,8 +18,8 @@ enum class MediaType(val type: Int) {
 class MediaManager : IMediaManager {
 
     private val mediaOperatorGets = arrayListOf<IMediaOperatorGet>(
-//        RoomModule,
-//        CallModule
+        RoomModule,
+        CallModule
     )
 
     override suspend fun conflictHandle(mediaInfo: MediaInfo): Rlt<Any> {
@@ -54,7 +56,7 @@ class MediaManager : IMediaManager {
         return Rlt.Success(Any())
     }
 
-    override fun getJoinedMediaRoomId(): Long? {
+    override fun getJoinedMediaRoomId(): String? {
         val mediaGet = mediaOperatorGets.find { it.getMediaOperator()?.getJoinedRoomId() != null }
         return mediaGet?.getMediaOperator()?.getJoinedRoomId()
     }

+ 49 - 0
app/src/main/java/com/adealink/weparty/media/datasource/MediaLocalService.kt

@@ -0,0 +1,49 @@
+package com.adealink.weparty.media.datasource
+
+import android.content.Context
+import com.adealink.frame.media.MediaInfo
+import com.adealink.frame.storage.sp.TypeDelegationPrefs
+import com.adealink.frame.util.AppUtil
+import com.adealink.weparty.media.MediaType
+import com.adealink.weparty.module.profile.ProfileModule
+import com.adealink.weparty.storage.PREF_MEDIA
+
+object MediaLocalService : TypeDelegationPrefs(
+    prefs = {
+        AppUtil.appContext.getSharedPreferences(PREF_MEDIA, Context.MODE_PRIVATE)
+    },
+    userId = {
+        ProfileModule.getMyUid()
+    }
+) {
+
+    var room2CallConflictRemind: Boolean by PrefUserKey("key_room_2_call_conflict", true)
+    var call2RoomConflictRemind: Boolean by PrefUserKey("key_room_2_call_conflict", true)
+
+
+    fun showConflictRemind(
+        exitMediaInfo: MediaInfo?,
+        enterMediaInfo: MediaInfo?
+    ): Boolean {
+        return if (exitMediaInfo?.mediaType == MediaType.CHAT_ROOM.type && enterMediaInfo?.mediaType == MediaType.CALL_1V1.type) {
+            room2CallConflictRemind
+        } else if (exitMediaInfo?.mediaType == MediaType.CALL_1V1.type && enterMediaInfo?.mediaType == MediaType.CHAT_ROOM.type) {
+            call2RoomConflictRemind
+        } else {
+            false
+        }
+
+    }
+
+    fun updateConflictRemind(
+        exitMediaInfo: MediaInfo?,
+        enterMediaInfo: MediaInfo?,
+        neverRemind: Boolean
+    ) {
+        if (exitMediaInfo?.mediaType == MediaType.CHAT_ROOM.type && enterMediaInfo?.mediaType == MediaType.CALL_1V1.type) {
+            room2CallConflictRemind = neverRemind
+        } else if (exitMediaInfo?.mediaType == MediaType.CALL_1V1.type && enterMediaInfo?.mediaType == MediaType.CHAT_ROOM.type) {
+            call2RoomConflictRemind = neverRemind
+        }
+    }
+}

+ 1 - 1
app/src/main/java/com/adealink/weparty/module/call/CallModule.kt

@@ -55,7 +55,7 @@ object CallModule : BaseDynamicModule<ICallService>(ICallService::class), ICallS
                         )
                     }
 
-                    override fun getJoinedRoomId(): Long? {
+                    override fun getJoinedRoomId(): String? {
                         return null
                     }
 

+ 9 - 0
app/src/main/java/com/adealink/weparty/module/gift/GiftModule.kt

@@ -6,6 +6,7 @@ import com.adealink.frame.aab.constant.AABModuleNotInitError
 import com.adealink.frame.base.Rlt
 import com.adealink.weparty.R
 import com.adealink.weparty.module.gift.data.GiftInfo
+import com.adealink.weparty.module.gift.data.GiftResource
 import com.adealink.weparty.module.gift.viewmodel.IGiftViewModel
 
 object GiftModule : BaseDynamicModule<IGiftService>(IGiftService::class), IGiftService {
@@ -32,6 +33,10 @@ object GiftModule : BaseDynamicModule<IGiftService>(IGiftService::class), IGiftS
             override fun getGifts(roomId: String, forceNet: Boolean) {
             }
 
+            override fun getGiftResource(resId: String): GiftResource? {
+                return null
+            }
+
             override suspend fun getGift(roomId: String, giftId: String): Rlt<GiftInfo?> {
                 return Rlt.Failed(AABModuleNotInitError())
             }
@@ -57,6 +62,10 @@ object GiftModule : BaseDynamicModule<IGiftService>(IGiftService::class), IGiftS
         getService().getGifts(roomId, forceNet)
     }
 
+    override fun getGiftResource(resId: String): GiftResource? {
+        return getService().getGiftResource(resId)
+    }
+
     override suspend fun getGift(roomId: String, giftId: String): Rlt<GiftInfo?> {
         return getService().getGift(roomId, giftId)
     }

+ 3 - 0
app/src/main/java/com/adealink/weparty/module/gift/IGiftService.kt

@@ -5,6 +5,7 @@ import com.adealink.frame.base.Rlt
 import com.adealink.frame.startup.IAppStartUpTask
 import com.adealink.weparty.aab.IService
 import com.adealink.weparty.module.gift.data.GiftInfo
+import com.adealink.weparty.module.gift.data.GiftResource
 import com.adealink.weparty.module.gift.viewmodel.IGiftViewModel
 
 interface IGiftService : IService<IGiftService>, IAppStartUpTask {
@@ -15,6 +16,8 @@ interface IGiftService : IService<IGiftService>, IAppStartUpTask {
 
     fun getGifts(roomId: String, forceNet: Boolean)
 
+    fun getGiftResource(resId: String): GiftResource?
+
     suspend fun getGift(roomId: String, giftId: String): Rlt<GiftInfo?>
 
     suspend fun getGifts(roomId: String, giftIds: Set<String>): Rlt<List<GiftInfo>>

+ 2 - 1
app/src/main/java/com/adealink/weparty/module/gift/viewmodel/IGiftViewModel.kt

@@ -18,7 +18,8 @@ interface IGiftViewModel {
 
     /**
      * 送礼
+     * @param useCoinIfLess 如果钻石不足,则使用金币结算
      */
-    fun sendGift(toUids: Set<String>, gift: GiftInfo, count: Int): LiveData<Rlt<Any>>
+    fun sendGift(toUids: Set<String>, gift: GiftInfo, count: Int, useCoinIfLess: Boolean = true): LiveData<Rlt<Any>>
 
 }

+ 1 - 1
app/src/main/java/com/adealink/weparty/module/room/RoomModule.kt

@@ -43,7 +43,7 @@ object RoomModule : BaseDynamicModule<IRoomService>(IRoomService::class), IRoomS
                         )
                     }
 
-                    override fun getJoinedRoomId(): Long? {
+                    override fun getJoinedRoomId(): String? {
                         return null
                     }
 

+ 28 - 0
app/src/main/java/com/adealink/weparty/module/room/chat/data/Message.kt

@@ -1,8 +1,10 @@
 package com.adealink.weparty.module.room.chat.data
 
+import com.adealink.frame.data.json.froJsonErrorNull
 import com.adealink.weparty.commonui.recycleview.diffutil.BaseListItemData
 import com.adealink.weparty.module.profile.data.UserInfo
 import com.adealink.weparty.module.room.chat.livemessage.ILiveMessage
+import com.google.gson.annotations.SerializedName
 import io.trtc.tuikit.atomicxcore.api.barrage.Barrage
 import io.trtc.tuikit.atomicxcore.api.barrage.BarrageType
 
@@ -14,6 +16,9 @@ const val MESSAGE_TYPE_TEXT = "0"
 //房间消息
 const val ROOM_MESSAGE_ENTER_ROOM = "room_message_enter_room"
 
+//送礼消息
+const val ROOM_MESSAGE_SEND_GIFT = "room_message_send_gift"
+
 //fun Barrage.isSupportMessage(): Boolean {
 //    if (messageType == BarrageType.TEXT) {
 //        return true
@@ -21,6 +26,9 @@ const val ROOM_MESSAGE_ENTER_ROOM = "room_message_enter_room"
 //    return false
 //}
 
+/**
+ * 子线程执行, 耗时
+ */
 fun List<Barrage>.toMessages(filterUnSupport: Boolean = true): List<Message> {
     return map { it.toMessage() }
         .filter { it !is UnknownMessage }
@@ -36,6 +44,9 @@ fun Barrage.toMessage(): Message {
         if (businessID == ROOM_MESSAGE_ENTER_ROOM) {
             return EnterRoomMessage(this)
         }
+        if (businessID == ROOM_MESSAGE_SEND_GIFT) {
+            return SendGiftMessage(this, froJsonErrorNull(this.data, SendGiftContent::class.java))
+        }
     }
     return UnknownMessage(this)
 }
@@ -63,4 +74,21 @@ data class TextMessage(override val barrage: Barrage) : Message(barrage) {
     }
 }
 
+data class SendGiftMessage(override val barrage: Barrage, val content: SendGiftContent?) : Message(barrage)
+data class SendGiftContent(
+    @SerializedName("sender") val sender: String,
+    @SerializedName("receiver") val receiver: String,
+    @SerializedName("quantity") val quantity: Long,
+    @SerializedName("resId") val resId: String,
+    @SerializedName("users") val users: Map<String, NotificationUserInfo>
+) {
+    fun getSenderName(): String {
+        return users[sender]?.nickname ?: sender
+    }
+
+    fun getReceiverName(): String {
+        return users[receiver]?.nickname ?: receiver
+    }
+}
+
 data class UnknownMessage(override val barrage: Barrage) : Message(barrage)

+ 37 - 0
app/src/main/java/com/adealink/weparty/module/room/chat/data/Notification.kt

@@ -24,6 +24,12 @@ fun Barrage.toNotification(): Notification {
                 froJsonErrorNull<ClearMicNotificationContent>(data) ?: UnknownNotificationContent()
             }
 
+            NotificationCmd.SEND_GIFT -> {
+                froJsonErrorNull<List<SendGiftNotificationData>>(data)?.let {
+                    SendGiftNotificationContent(it)
+                } ?: UnknownNotificationContent()
+            }
+
             NotificationCmd.UNKNOWN -> {
                 UnknownNotificationContent()
             }
@@ -42,6 +48,7 @@ data class Notification(
 
 enum class NotificationCmd(val cmd: String, val clazz: Class<*>?) {
     CLEAR_MIC_APPLY("mic.clear", ClearMicNotificationContent::class.java),
+    SEND_GIFT("room.gift", SendGiftNotificationContent::class.java),
 
     UNKNOWN("unknown", UnknownNotificationContent::class.java);
 
@@ -78,4 +85,34 @@ data class ClearMicNotificationContent(
     }
 }
 
+data class SendGiftNotificationContent(
+    @SerializedName("list") val list: List<SendGiftNotificationData>
+) : NotificationContent(NotificationCmd.SEND_GIFT)
+
+data class SendGiftNotificationData(
+    @SerializedName("sender") val sender: String, //
+    @SerializedName("receivers") val receivers: List<String>, //
+    @SerializedName("quantity") val quantity: Long,
+    @SerializedName("resId") val resId: String,
+    @SerializedName("users") val users: Map<String, NotificationUserInfo>
+) {
+
+    fun getUserInfo(uid: String): NotificationUserInfo {
+        return users[uid] ?: NotificationUserInfo.emptyUserInfo(uid)
+    }
+}
+
+data class NotificationUserInfo(
+    @SerializedName("id") val uid: String,
+    @SerializedName("nickname") val nickname: String,
+) {
+    companion object {
+        @JvmStatic
+        fun emptyUserInfo(uid: String): NotificationUserInfo {
+            return NotificationUserInfo(uid, uid)
+        }
+    }
+}
+
+
 class UnknownNotificationContent : NotificationContent(NotificationCmd.UNKNOWN)

+ 2 - 2
app/src/main/java/com/adealink/weparty/module/room/data/RoomData.kt

@@ -26,11 +26,11 @@ enum class RoomType(val type: Int) {
 
 
 data class SpeakingRippleConfig(
-    val rippleColor: Int = getCompatColor(R.color.color_66FFFFFF),
+    val rippleColor: Int = getCompatColor(R.color.color_1AFFFFFF),
     val rippleWidth: Int = 1.5f.dp(),
     val rippleSpace: Int = 4.dp(),
     val rippleSpeed: Int = 500,
-    val innerBorderColor: Int = getCompatColor(R.color.color_66FFFFFF),
+    val innerBorderColor: Int = getCompatColor(R.color.color_33FFFFFF),
     val innerBorderWidth: Int = 1.5f.dp(),
     val rippleStartAlphaDuration: Long = 100L,
     val rippleEndAlphaDuration: Long = 500L,

+ 3 - 1
app/src/main/java/com/adealink/weparty/storage/Constants.kt

@@ -24,4 +24,6 @@ const val PREF_COUNTRY = "pref_country"
 
 const val PREF_ROOM = "pref_room"
 
-const val PREF_GIFT = "pref_gift"
+const val PREF_GIFT = "pref_gift"
+
+const val PREF_MEDIA = "pref_media"

BIN
app/src/main/res/drawable-xhdpi/common_go_white_bold_ic.png


+ 134 - 0
app/src/main/res/layout/dialog_media_conflict.xml

@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/content_layout"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@drawable/commonui_dialog_bg"
+    app:layout_constraintTop_toTopOf="parent">
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/top_close_btn"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_marginTop="10dp"
+        android:layout_marginEnd="10dp"
+        android:src="@drawable/common_close_ic"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingStart="24dp"
+        android:paddingTop="24dp"
+        android:paddingEnd="24dp"
+        android:paddingBottom="24dp"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <!--标题-->
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tvTitle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="12dp"
+            android:gravity="center"
+            android:includeFontPadding="false"
+            android:lineSpacingExtra="3dp"
+            android:text="@string/common_tips"
+            android:textColor="@color/color_222222"
+            android:textSize="16sp"
+            android:textStyle="bold"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_goneMarginTop="0dp" />
+
+        <!--内容-->
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_message"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="10dp"
+            android:gravity="center"
+            android:includeFontPadding="false"
+            android:lineSpacingExtra="5dp"
+            android:textColor="@color/color_222222"
+            android:textSize="12sp"
+            app:layout_constraintTop_toBottomOf="@id/tvTitle"
+            tools:text="内容" />
+        <!--确定-->
+        <com.adealink.weparty.commonui.widget.CommonButton
+            android:id="@+id/btn_confirm"
+            android:layout_width="0dp"
+            android:layout_height="36dp"
+            android:layout_marginHorizontal="50dp"
+            android:layout_marginTop="16dp"
+            app:button_radius="22dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/tv_message"
+            app:layout_constraintWidth_default="spread"
+            app:layout_goneMarginTop="20dp"
+            app:text="@string/commonui_confirm"
+            app:textColor="@color/white"
+            app:textSize="14sp" />
+
+        <!--取消-->
+        <com.adealink.weparty.commonui.widget.CommonButton
+            android:id="@+id/btn_cancel"
+            android:layout_width="0dp"
+            android:layout_height="32dp"
+            android:layout_marginHorizontal="50dp"
+            android:layout_marginTop="16dp"
+            app:button_radius="22dp"
+            app:common_button_type="cancel"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/btn_confirm"
+            app:layout_goneMarginTop="20dp"
+            app:text="@string/commonui_cancel"
+            app:textColor="@color/color_FF4E5969"
+            app:textSize="14sp"
+            tools:visibility="visible" />
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/cl_never_remind"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="20dp"
+            android:layout_marginTop="16dp"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/btn_cancel">
+
+            <androidx.appcompat.widget.AppCompatImageView
+                android:id="@+id/iv_never_remind"
+                android:layout_width="14dp"
+                android:layout_height="14dp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintHorizontal_chainStyle="packed"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:srcCompat="@drawable/common_check_unselected_ic" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:id="@+id/tv_never_remind"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="4dp"
+                android:gravity="start"
+                android:includeFontPadding="false"
+                android:text="@string/common_never_remind"
+                android:textColor="@color/color_FF4E5969"
+                android:textSize="12sp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@id/iv_never_remind"
+                app:layout_constraintTop_toTopOf="parent" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 5 - 5
app/src/main/res/layout/layout_common_dialog.xml

@@ -139,11 +139,11 @@
 
     <androidx.appcompat.widget.AppCompatImageView
         android:id="@+id/top_close_btn"
-        android:layout_width="16dp"
-        android:layout_height="16dp"
-        android:layout_marginTop="12dp"
-        android:layout_marginEnd="12dp"
-        android:src="@drawable/commonui_dialog_close_ic"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_marginTop="10dp"
+        android:layout_marginEnd="10dp"
+        android:src="@drawable/common_close_ic"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintTop_toTopOf="parent" />
 

+ 3 - 2
app/src/main/res/values-in/strings.xml

@@ -81,7 +81,7 @@
     <string name="common_size_limited">Ukuran file yang dikirim tidak boleh melebihi %s</string>
     <string name="common_un_lock">Buka blokir</string>
     <string name="common_kickout">Mengeluarkan</string>
-    <string name="common_accept">Menerima</string>
+    <string name="common_accept">Setuju</string>
     <string name="common_must_fill">Silakan isi %s</string>
     <string name="common_next">Langkah berikutnya</string>
     <string name="common_ask_for_gift">Minta gift</string>
@@ -282,7 +282,7 @@
     <string name="order_btn_finish">Selesai</string>
     <string name="order_btn_refund">Pengembalian Dana</string>
     <string name="order_btn_evaluate">Evaluasi</string>
-    <string name="playmate_order_accept">Terima</string>
+    <string name="playmate_order_accept">Setuju</string>
     <string name="playmate_order_start_service">Mulai layanan</string>
     <string name="playmate_order_complete">Selesai</string>
     <string name="common_refuse">Menolak</string>
@@ -402,4 +402,5 @@
     <string name="common_send_gift">Kirim Hadiah</string>
     <string name="common_think_more">Pikirkan lagi</string>
     <string name="common_main_room_tab">Ruangan</string>
+    <string name="wallet_confirm_convert">Konfirmasi Penukaran</string>
 </resources>

+ 2 - 1
app/src/main/res/values-zh/strings.xml

@@ -151,7 +151,7 @@
     <string name="common_go_open_vip">去开通%1$s</string>
     <string name="common_fans_num">%1$s个粉丝</string>
     <string name="common_days">%1$s 天</string>
-    <string name="common_count">x%1$s</string>
+    <string name="common_count">x%s</string>
     <string name="common_x_days">x%1$s天</string>
     <string name="common_received">已领取</string>
     <string name="common_back">返回</string>
@@ -402,4 +402,5 @@
     <string name="common_send_gift">送礼</string>
     <string name="common_think_more">再想一下</string>
     <string name="common_main_room_tab">房间</string>
+    <string name="wallet_confirm_convert">确认兑换</string>
 </resources>

+ 4 - 0
app/src/main/res/values/attrs.xml

@@ -608,6 +608,10 @@
 
     <declare-styleable name="NumberIncreaseView">
         <attr name="default_number" format="integer" />
+        <attr name="number_text_color" format="color" />
+        <attr name="number_add_src" format="reference" />
+        <attr name="number_minus_src" format="reference" />
+        <attr name="number_minus_disable_src" format="reference" />
     </declare-styleable>
 
     <declare-styleable name="PriceView">

+ 4 - 1
app/src/main/res/values/strings.xml

@@ -137,6 +137,7 @@
     <string name="common_update_downloaded">An update has just been downloaded</string>
     <string name="common_restart">Restart</string>
     <string name="common_remind">Remind</string>
+    <string name="common_never_remind">不再提醒</string>
     <string name="common_giving">Give away</string>
     <string name="common_enter_other_id">Enter the other party\'s ID</string>
     <string name="common_click_to_select">Click to select</string>
@@ -155,7 +156,7 @@
     <string name="common_go_open_vip">Buy %1$s</string>
     <string name="common_fans_num">%1$s followers</string>
     <string name="common_days">%1$s days</string>
-    <string name="common_count">x%1$s</string>
+    <string name="common_count">x%s</string>
     <string name="common_x_days">x %1$s days</string>
     <string name="common_received">Received</string>
     <string name="common_back">Back</string>
@@ -409,4 +410,6 @@
     <string name="room_mic_8">Mic8</string>
     <string name="common_send_gift">Send Gifts</string>
     <string name="common_think_more">Think again</string>
+    <string name="wallet_confirm_convert">Confirm Exchange</string>
+    <string name="room_media_conflict_tips">你当前在%1$s,进入%2$s后将自动退出</string>
 </resources>

+ 2 - 2
gradle/libs.versions.toml

@@ -167,7 +167,7 @@ tiktok = "2.3.0"
 
 # frame
 frameBom = "6.2.20"
-
+frameMedia = "6.0.6-beta-1"
 framePush = "6.0.1"
 
 frameRouterCompiler = "6.0.0"
@@ -431,7 +431,7 @@ frame-audio = { group = "com.wenext.android", name = "frame-audio" }
 frame-crash = { group = "com.wenext.android", name = "frame-crash" }
 frame-dot = { group = "com.wenext.android", name = "frame-dot" }
 frame-locale = { group = "com.wenext.android", name = "frame-locale" }
-frame-media = { group = "com.wenext.android", name = "frame-media" }
+frame-media = { group = "com.wenext.android", name = "frame-media", version.ref = "frameMedia" }
 frame-agorartc = { group = "com.wenext.android", name = "frame-agorartc" }
 frame-trtc = { group = "com.wenext.android", name = "frame-trtc" }
 frame-trtc-pro = { group = "com.wenext.android", name = "frame-trtc-pro" }

+ 35 - 43
module/call/src/main/java/com/adealink/weparty/call/CallServiceImpl.kt

@@ -2,21 +2,27 @@ package com.adealink.weparty.call
 
 import android.app.Application
 import com.adealink.frame.aab.util.getCompatString
+import com.adealink.frame.base.IError
 import com.adealink.frame.base.Rlt
+import com.adealink.frame.coroutine.dispatcher.Dispatcher
 import com.adealink.frame.media.IMediaOperator
 import com.adealink.frame.media.MediaConflictConfig
 import com.adealink.frame.media.MediaInfo
 import com.adealink.frame.spi.RegisterService
 import com.adealink.frame.util.AppUtil
-import com.adealink.weparty.call.datasource.local.CallLocalService
 import com.adealink.weparty.call.manager.callManager
 import com.adealink.weparty.call.widget.float.calling.CallingFloatData
 import com.adealink.weparty.call.widget.float.calling.CallingFloatView
 import com.adealink.weparty.commonui.BaseActivity
 import com.adealink.weparty.commonui.widget.floatview.data.IFloatData
+import com.adealink.weparty.media.MediaConflictDialog
 import com.adealink.weparty.module.call.ICallService
 import com.tencent.qcloud.tuikit.tuicallkit.manager.bridge.CallServiceInitializer
+import io.trtc.tuikit.atomicx.callview.core.common.utils.CallUtils
 import io.trtc.tuikit.atomicxcore.api.CompletionHandler
+import io.trtc.tuikit.atomicxcore.api.call.CallStore
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
 
 @RegisterService(ICallService::class)
 class CallServiceImpl : ICallService {
@@ -49,19 +55,10 @@ class CallServiceImpl : ICallService {
         })
     }
 
-//    override fun getIncomingFloatView(data: IFloatData): InComingFloatView {
-//        return InComingFloatView(data as InComingFloatData)
-//    }
-
     override fun getCallingFloatView(data: IFloatData): CallingFloatView {
         return CallingFloatView(data as CallingFloatData)
     }
 
-//    override fun getCallViewModel(owner: ViewModelStoreOwner): ICallViewModel {
-//        return ViewModelProvider(owner, CallViewModelFactory())[CallViewModel::class.java]
-//    }
-
-
     override fun getService(): ICallService {
         return this
     }
@@ -71,12 +68,11 @@ class CallServiceImpl : ICallService {
 
             override suspend fun isMediaIn(): Boolean {
                 //当前正在呼叫
-                return false
-                //return TUICallState.instance.selfUser.get().isCalling()
+                return CallUtils.isSelfInCalling()
             }
 
             override suspend fun leaveMedia(): Rlt<Any> {
-//                EngineManager.instance.hangup(null)
+                CallStore.shared.hangup(null)
                 return Rlt.Success(Any())
             }
 
@@ -84,35 +80,31 @@ class CallServiceImpl : ICallService {
                 exitMediaInfo: MediaInfo,
                 enterMediaInfo: MediaInfo
             ): Rlt<Any> {
-                if (!CallLocalService.mediaConflictRemind) {
-                    return Rlt.Success(Any())
+                val currentActivity =
+                    AppUtil.currentActivity as? BaseActivity ?: return Rlt.Success(Any())
+                return withContext(Dispatcher.UI) {
+                    suspendCancellableCoroutine { continuation ->
+                        var confirm = false
+                        MediaConflictDialog.show(
+                            exitMediaInfo,
+                            enterMediaInfo,
+                            currentActivity.supportFragmentManager,
+                            onPositive = {
+                                confirm = true
+                            },
+                            onNegative = null,
+                            onDismiss = {
+                                if (continuation.isActive) {
+                                    if (confirm) {
+                                        continuation.resume(Rlt.Success(Any()), null)
+                                    } else {
+                                        continuation.resume(Rlt.Failed(IError()), null)
+                                    }
+                                }
+                            }
+                        )
+                    }
                 }
-                return Rlt.Success(Any())
-//                val currentActivity =
-//                    AppUtil.currentActivity as? BaseActivity ?: return Rlt.Success(Any())
-//                return withContext(Dispatcher.UI) {
-//                    suspendCancellableCoroutine { continuation ->
-//                        var confirm = false
-//                        CallMediaConflictDialog.show(
-//                            exitMediaInfo,
-//                            enterMediaInfo,
-//                            currentActivity.supportFragmentManager,
-//                            onPositive = {
-//                                confirm = true
-//                            },
-//                            onNegative = null,
-//                            onDismiss = {
-//                                if (continuation.isActive) {
-//                                    if (confirm) {
-//                                        continuation.resume(Rlt.Success(Any()), null)
-//                                    } else {
-//                                        continuation.resume(Rlt.Failed(IError()), null)
-//                                    }
-//                                }
-//                            }
-//                        )
-//                    }
-//                }
             }
 
             override fun getConflictConfig(): MediaConflictConfig {
@@ -124,8 +116,8 @@ class CallServiceImpl : ICallService {
                 )
             }
 
-            override fun getJoinedRoomId(): Long? {
-                return null
+            override fun getJoinedRoomId(): String? {
+                return CallStore.shared.observerState.activeCall.value.callId
             }
 
             override suspend fun rejoinRoom(): Rlt<Any> {

+ 0 - 2
module/call/src/main/java/com/adealink/weparty/call/datasource/local/CallLocalService.kt

@@ -14,8 +14,6 @@ object CallLocalService : TypeDelegationPrefs(
     }
 ) {
 
-    var mediaConflictRemind: Boolean by PrefUserKey("key_call_media_conflict_remind", true)
-
     var requestOverPlayPermission: Boolean by PrefUserKey("key_request_overlay_permission", true)
 
 }

+ 5 - 0
module/gift/src/main/java/com/adealink/weparty/gift/GiftServiceImpl.kt

@@ -9,6 +9,7 @@ import com.adealink.weparty.gift.viewmodel.GiftViewModel
 import com.adealink.weparty.gift.viewmodel.GiftViewModelFactory
 import com.adealink.weparty.module.gift.IGiftService
 import com.adealink.weparty.module.gift.data.GiftInfo
+import com.adealink.weparty.module.gift.data.GiftResource
 import com.adealink.weparty.module.gift.viewmodel.IGiftViewModel
 
 @RegisterService(value = IGiftService::class)
@@ -25,6 +26,10 @@ class GiftServiceImpl : IGiftService {
         giftManager.getGifts(roomId, forceNet)
     }
 
+    override fun getGiftResource(resId: String): GiftResource? {
+        return giftManager.getGiftResource(resId)
+    }
+
     override suspend fun getGift(roomId: String, giftId: String): Rlt<GiftInfo?> {
         return giftManager.getGift(roomId,giftId)
     }

+ 4 - 0
module/gift/src/main/java/com/adealink/weparty/gift/manager/GiftManager.kt

@@ -140,6 +140,10 @@ class GiftManager : BaseFrame<IGiftListener>(), IGiftManager {
         }
     }
 
+    override fun getGiftResource(resId: String): GiftResource? {
+        return giftResources[resId]
+    }
+
     private suspend fun getRemoteGifts(roomId: String): Rlt<List<GiftInfo>> {
         return withContext(this.coroutineContext) {
             when (val result = giftHttpService.giftList(GiftListReq(roomId))) {

+ 3 - 0
module/gift/src/main/java/com/adealink/weparty/gift/manager/IGiftManager.kt

@@ -3,6 +3,7 @@ package com.adealink.weparty.gift.manager
 import com.adealink.frame.base.Rlt
 import com.adealink.frame.frame.IBaseFrame
 import com.adealink.weparty.module.gift.data.GiftInfo
+import com.adealink.weparty.module.gift.data.GiftResource
 import com.adealink.weparty.module.gift.listener.IGiftListener
 
 interface IGiftManager : IBaseFrame<IGiftListener> {
@@ -11,6 +12,8 @@ interface IGiftManager : IBaseFrame<IGiftListener> {
 
     fun getGifts(roomId: String, forceNet: Boolean)
 
+    fun getGiftResource(resId: String): GiftResource?
+
     suspend fun getGift(roomId: String, giftId: String): Rlt<GiftInfo?>
 
     suspend fun getGifts(roomId: String, giftIds: Set<String>): Rlt<List<GiftInfo>>

+ 3 - 4
module/gift/src/main/java/com/adealink/weparty/gift/viewmodel/GiftViewModel.kt

@@ -7,7 +7,6 @@ import com.adealink.frame.mvvm.livedata.ExtMutableLiveData
 import com.adealink.frame.mvvm.livedata.OnceMutableLiveData
 import com.adealink.frame.mvvm.viewmodel.BaseViewModel
 import com.adealink.weparty.App
-import com.adealink.weparty.commonui.toast.util.showToast
 import com.adealink.weparty.gift.data.SendGiftReq
 import com.adealink.weparty.gift.datasource.remote.GiftHttpService
 import com.adealink.weparty.gift.manager.giftManager
@@ -40,17 +39,17 @@ class GiftViewModel : BaseViewModel(), IGiftViewModel, IGiftListener {
         }
     }
 
-    override fun sendGift(toUids: Set<String>, gift: GiftInfo, count: Int): LiveData<Rlt<Any>> {
+    override fun sendGift(toUids: Set<String>, gift: GiftInfo, count: Int, useCoinIfLess: Boolean): LiveData<Rlt<Any>> {
         val liveData = OnceMutableLiveData<Rlt<Any>>()
         viewModelScope.launch {
             val roomId = RoomModule.getJoinedRoomId() ?: return@launch
             val rlt = giftHttpService.sendGift(
                 SendGiftReq(
                     roomId,
-                    "69c1397085bfc551a8cd8af1",
+                    gift.id,
                     toUids.toList(),
                     count,
-                    true
+                    useCoinIfLess
                 )
             )
             liveData.send(rlt)

+ 0 - 2
module/playmate/src/main/res/layout/fragment_find_partner_category.xml

@@ -18,10 +18,8 @@
         app:tabIndicatorHeight="0dp"
         app:tabMaxWidth="0dp"
         app:tabMode="fixed"
-        app:tabPaddingBottom="4dp"
         app:tabPaddingEnd="4dp"
         app:tabPaddingStart="4dp"
-        app:tabPaddingTop="4dp"
         app:tabRippleColor="@null" />
 
     <androidx.core.widget.NestedScrollView

+ 4 - 4
module/playmate/src/main/res/layout/item_playmate_home_list.xml

@@ -209,19 +209,19 @@
         android:id="@+id/cl_category"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:layout_marginStart="12dp"
         android:layout_marginTop="3dp"
         android:background="@drawable/playmate_list_item_category_bg"
         android:paddingVertical="2dp"
-        android:paddingStart="12dp"
         android:paddingEnd="6dp"
         app:layout_constraintStart_toEndOf="@id/v_barrier_start"
         app:layout_constraintTop_toBottomOf="@id/cl_new">
 
         <com.adealink.frame.image.view.NetworkImageView
             android:id="@+id/iv_category"
+            style="@style/CommonNetworkImage"
             android:layout_width="16dp"
             android:layout_height="16dp"
-            style="@style/CommonNetworkImage"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toTopOf="parent"
@@ -287,11 +287,11 @@
 
         <com.adealink.frame.image.view.NetworkImageView
             android:id="@+id/iv_photo_1"
+            style="@style/CommonNetworkImage"
             android:layout_width="0dp"
             android:layout_height="0dp"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintDimensionRatio="1:1"
-            style="@style/CommonNetworkImage"
             app:layout_constraintEnd_toStartOf="@id/iv_photo_2"
             app:layout_constraintHorizontal_weight="1"
             app:layout_constraintStart_toStartOf="parent"
@@ -300,10 +300,10 @@
 
         <com.adealink.frame.image.view.NetworkImageView
             android:id="@+id/iv_photo_2"
+            style="@style/CommonNetworkImage"
             android:layout_width="0dp"
             android:layout_height="0dp"
             android:layout_marginStart="6dp"
-            style="@style/CommonNetworkImage"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintDimensionRatio="1:1"
             app:layout_constraintEnd_toStartOf="@id/iv_photo_3"

+ 1 - 0
module/profile/src/main/res/layout/layout_user_profile_header.xml

@@ -219,6 +219,7 @@
         android:id="@+id/tv_evaluate"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:layout_marginEnd="16dp"
         android:layout_marginBottom="18dp"
         android:fontFamily="@font/poppins_semibold"
         android:includeFontPadding="false"

+ 33 - 30
module/room/src/main/java/com/adealink/weparty/room/RoomServiceImpl.kt

@@ -1,14 +1,18 @@
 package com.adealink.weparty.room
 
 import com.adealink.frame.aab.util.getCompatString
+import com.adealink.frame.base.IError
 import com.adealink.frame.base.Rlt
+import com.adealink.frame.coroutine.dispatcher.Dispatcher
 import com.adealink.frame.media.IMediaOperator
 import com.adealink.frame.media.MediaConflictConfig
 import com.adealink.frame.media.MediaInfo
 import com.adealink.frame.room.data.LeaveRoomReason
 import com.adealink.frame.spi.RegisterService
+import com.adealink.frame.util.AppUtil
 import com.adealink.weparty.commonui.BaseActivity
 import com.adealink.weparty.commonui.widget.floatview.data.IFloatData
+import com.adealink.weparty.media.MediaConflictDialog
 import com.adealink.weparty.media.MediaType
 import com.adealink.weparty.module.room.IRoomService
 import com.adealink.weparty.room.create.CreateRoomDialog
@@ -17,6 +21,8 @@ import com.adealink.weparty.room.minimize.manager.minimizeRoomManager
 import com.adealink.weparty.room.minimize.view.MinimizedRoomFloatData
 import com.adealink.weparty.room.minimize.view.MinimizedRoomFloatView
 import com.adealink.weparty.room.sdk.service.roomService
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
 
 @RegisterService(IRoomService::class)
 class RoomServiceImpl : IRoomService {
@@ -41,32 +47,31 @@ class RoomServiceImpl : IRoomService {
                 exitMediaInfo: MediaInfo,
                 enterMediaInfo: MediaInfo
             ): Rlt<Any> {
-                return Rlt.Success(Unit)
-//                val currentActivity =
-//                    AppUtil.currentActivity as? BaseActivity ?: return Rlt.Success(Any())
-//                return withContext(Dispatcher.UI) {
-//                    suspendCancellableCoroutine { continuation ->
-//                        var confirm = false
-//                        RoomMediaConflictDialog.show(
-//                            exitMediaInfo,
-//                            enterMediaInfo,
-//                            currentActivity.supportFragmentManager,
-//                            onPositive = {
-//                                confirm = true
-//                            },
-//                            onNegative = null,
-//                            onDismiss = {
-//                                if (continuation.isActive) {
-//                                    if (confirm) {
-//                                        continuation.resume(Rlt.Success(Any()), null)
-//                                    } else {
-//                                        continuation.resume(Rlt.Failed(IError()), null)
-//                                    }
-//                                }
-//                            }
-//                        )
-//                    }
-//                }
+                val currentActivity =
+                    AppUtil.currentActivity as? BaseActivity ?: return Rlt.Success(Any())
+                return withContext(Dispatcher.UI) {
+                    suspendCancellableCoroutine { continuation ->
+                        var confirm = false
+                        MediaConflictDialog.show(
+                            exitMediaInfo,
+                            enterMediaInfo,
+                            currentActivity.supportFragmentManager,
+                            onPositive = {
+                                confirm = true
+                            },
+                            onNegative = null,
+                            onDismiss = {
+                                if (continuation.isActive) {
+                                    if (confirm) {
+                                        continuation.resume(Rlt.Success(Any()), null)
+                                    } else {
+                                        continuation.resume(Rlt.Failed(IError()), null)
+                                    }
+                                }
+                            }
+                        )
+                    }
+                }
             }
 
             override fun getConflictConfig(): MediaConflictConfig {
@@ -78,14 +83,12 @@ class RoomServiceImpl : IRoomService {
                 )
             }
 
-            override fun getJoinedRoomId(): Long? {
-                return null
-//                return roomService.joinController.getJoinedRoomId()
+            override fun getJoinedRoomId(): String? {
+                return roomService.joinController.getJoinedRoomId()
             }
 
             override suspend fun rejoinRoom(): Rlt<Any> {
                 return Rlt.Success(Unit)
-//                return roomService.joinController.rejoinRoom()
             }
 
         }

+ 2 - 0
module/room/src/main/java/com/adealink/weparty/room/applymic/ApplyMicFilterDialog.kt

@@ -30,6 +30,8 @@ class ApplyMicFilterDialog : BottomDialogFragment(R.layout.dialog_room_playmate_
 
     private var currentSkillId: String? = null
 
+    override val dimAmount: Float = 0.7f
+
     fun setListener(listener: OnCategorySelectListener?) {
         this.listener = listener
     }

+ 6 - 0
module/room/src/main/java/com/adealink/weparty/room/chat/ChatMessageFragment.kt

@@ -21,10 +21,12 @@ import com.adealink.weparty.module.room.chat.livemessage.LiveMessageRecyclerHelp
 import com.adealink.weparty.module.room.listener.IMemberInfoListener
 import com.adealink.weparty.room.R
 import com.adealink.weparty.room.chat.adapter.EnterRoomMessageViewBinder
+import com.adealink.weparty.room.chat.adapter.SendGiftMessageViewBinder
 import com.adealink.weparty.room.chat.adapter.TextMessageViewBinder
 import com.adealink.weparty.room.chat.listener.IMessageOperaListener
 import com.adealink.weparty.room.chat.viewmodel.ChatMessageViewModel
 import com.adealink.weparty.room.databinding.FragmentChatMessageBinding
+import com.adealink.weparty.room.gift.viewmodel.RoomGiftViewModel
 import com.adealink.weparty.room.member.viewmodel.RoomMemberViewModel
 import com.adealink.weparty.room.viewmodel.RoomViewModelFactory
 import io.trtc.tuikit.atomicxcore.api.barrage.Barrage
@@ -39,6 +41,8 @@ class ChatMessageFragment : BaseFragment(R.layout.fragment_chat_message),
     private lateinit var layoutManager: LinearLayoutManager
 
     private val chatMessageViewModel by activityViewModels<ChatMessageViewModel> { RoomViewModelFactory() }
+
+    private val roomGiftViewModel by activityViewModels<RoomGiftViewModel> { RoomViewModelFactory() }
     private val memberViewModel by activityViewModels<RoomMemberViewModel> { RoomViewModelFactory() }
 
     override fun initViews() {
@@ -59,6 +63,7 @@ class ChatMessageFragment : BaseFragment(R.layout.fragment_chat_message),
             adapter = LiveMessageAdapter(helper, BaseListDiffUtil()).also {
                 it.register(TextMessageViewBinder(this@ChatMessageFragment, this@ChatMessageFragment, this@ChatMessageFragment))
                 it.register(EnterRoomMessageViewBinder(this@ChatMessageFragment, this@ChatMessageFragment))
+                it.register(SendGiftMessageViewBinder(this@ChatMessageFragment, this@ChatMessageFragment))
             }
             liveMessageRecyclerHelper = helper
         }
@@ -67,6 +72,7 @@ class ChatMessageFragment : BaseFragment(R.layout.fragment_chat_message),
 
     override fun observeViewModel() {
         super.observeViewModel()
+        roomGiftViewModel
         chatMessageViewModel.messagesLD.observe(viewLifecycleOwner) { messages ->
             adapter?.submitList(messages)
         }

+ 95 - 0
module/room/src/main/java/com/adealink/weparty/room/chat/adapter/SendGiftMessageViewBinder.kt

@@ -0,0 +1,95 @@
+package com.adealink.weparty.room.chat.adapter
+
+import android.text.SpannableStringBuilder
+import android.text.method.LinkMovementMethod
+import android.text.style.ClickableSpan
+import android.text.style.ForegroundColorSpan
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import com.adealink.frame.aab.util.getCompatColor
+import com.adealink.frame.aab.util.getCompatString
+import com.adealink.frame.ext.findAndSetSpan
+import com.adealink.frame.router.Router
+import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
+import com.adealink.weparty.commonui.recycleview.adapter.multitype.ItemViewBinder
+import com.adealink.weparty.commonui.widget.ColorClickSpan
+import com.adealink.weparty.module.gift.GiftModule
+import com.adealink.weparty.module.room.chat.data.SendGiftMessage
+import com.adealink.weparty.module.webview.Web
+import com.adealink.weparty.room.R
+import com.adealink.weparty.room.chat.listener.IMessageOperaListener
+import com.adealink.weparty.room.databinding.ItemMessageSendGiftBinding
+import com.adealink.weparty.url.UrlConfig
+import com.adealink.weparty.util.formatNumberStr
+import com.adealink.weparty.R as APP_R
+
+/**
+ */
+class SendGiftMessageViewBinder(
+    val listener: IMessageOperaListener,
+    val fragment: Fragment,
+) : ItemViewBinder<SendGiftMessage, SendGiftMessageViewBinder.ViewHolder>() {
+
+    override fun onBindViewHolder(holder: ViewHolder, item: SendGiftMessage) {
+        holder.update(item)
+    }
+
+    override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ViewHolder {
+        return ViewHolder(ItemMessageSendGiftBinding.inflate(inflater, parent, false))
+    }
+
+    inner class ViewHolder(binding: ItemMessageSendGiftBinding) :
+        BindingViewHolder<ItemMessageSendGiftBinding>(binding) {
+
+        private var message: SendGiftMessage? = null
+
+        fun update(message: SendGiftMessage) {
+            this.message = message
+            //消息内容
+            val sendGiftContent = message.content
+            val senderName = sendGiftContent?.getSenderName() ?: ""
+            val receiverName = sendGiftContent?.getReceiverName() ?: ""
+            val text = getCompatString(R.string.room_send_gift_message, senderName, receiverName)
+            binding.tvMsg.movementMethod = LinkMovementMethod.getInstance()
+            binding.tvMsg.text = SpannableStringBuilder(text).apply {
+                findAndSetSpan(
+                    ForegroundColorSpan(getCompatColor(APP_R.color.color_FF35F3F0)),
+                    senderName
+                )
+                findAndSetSpan(
+                    object : ClickableSpan() {
+                        override fun onClick(widget: View) {
+                            sendGiftContent?.sender?.let { uid ->
+                                listener.onAvatarClick(uid)
+                            }
+                        }
+                    },
+                    senderName
+                )
+                findAndSetSpan(
+                    ForegroundColorSpan(getCompatColor(APP_R.color.color_FF35F3F0)),
+                    receiverName
+                )
+                findAndSetSpan(
+                    object : ClickableSpan() {
+                        override fun onClick(widget: View) {
+                            sendGiftContent?.receiver?.let { uid ->
+                                listener.onAvatarClick(uid)
+                            }
+                        }
+                    },
+                    receiverName
+                )
+            }
+            val giftIconUrl = sendGiftContent?.resId?.let {
+                GiftModule.getGiftResource(it)?.icon
+            }
+            binding.ivGift.setImageUrl(giftIconUrl)
+            binding.tvCount.text = getCompatString(APP_R.string.common_count, formatNumberStr(sendGiftContent?.quantity ?: 0))
+        }
+
+    }
+
+}

+ 5 - 5
module/room/src/main/java/com/adealink/weparty/room/chat/viewmodel/ChatMessageViewModel.kt

@@ -3,6 +3,7 @@ package com.adealink.weparty.room.chat.viewmodel
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import com.adealink.frame.base.Rlt
+import com.adealink.frame.coroutine.dispatcher.Dispatcher
 import com.adealink.frame.mvvm.livedata.OnceMutableLiveData
 import com.adealink.frame.mvvm.viewmodel.BaseViewModel
 import com.adealink.weparty.module.room.chat.data.Message
@@ -12,6 +13,7 @@ import com.adealink.weparty.room.sdk.listener.IMessageListener
 import com.adealink.weparty.room.sdk.service.roomService
 import io.trtc.tuikit.atomicxcore.api.barrage.Barrage
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 /**
  */
@@ -21,7 +23,7 @@ class ChatMessageViewModel : BaseViewModel(), IChatMessageViewModel, IMessageLis
         roomService.messageController.addListener(this)
         viewModelScope.launch {
             roomService.messageController.messagesFlow.collect {
-                onBarrageListChanged(it)
+                onMessageListChanged(it)
             }
             roomService.messageController
         }
@@ -48,10 +50,8 @@ class ChatMessageViewModel : BaseViewModel(), IChatMessageViewModel, IMessageLis
     }
 
 
-    private fun onBarrageListChanged(barrages: List<Barrage>) {
-        messagesLD.send(
-            barrages.toMessages()
-        )
+    private fun onMessageListChanged(messages: List<Message>) {
+        messagesLD.send(messages)
     }
 
 }

+ 20 - 12
module/room/src/main/java/com/adealink/weparty/room/chatroom/page/dispatchcenter/DispatchRoomEventViewModel.kt

@@ -2,9 +2,9 @@ package com.adealink.weparty.room.chatroom.page.dispatchcenter
 
 import androidx.lifecycle.MutableLiveData
 import com.adealink.frame.mvvm.viewmodel.BaseViewModel
-import com.adealink.weparty.module.profile.ProfileModule
 import com.adealink.weparty.module.room.chat.data.ClearMicNotificationContent
 import com.adealink.weparty.module.room.chat.data.Notification
+import com.adealink.weparty.module.room.chat.data.SendGiftNotificationContent
 import com.adealink.weparty.module.room.data.MIC_ASSIGN_BY_REMOTE
 import com.adealink.weparty.room.applymic.ApplyMicStore
 import com.adealink.weparty.room.data.applyMicMemberCount
@@ -16,6 +16,8 @@ import io.trtc.tuikit.atomicxcore.api.live.LiveListStore
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.launch
 
@@ -33,15 +35,25 @@ class DispatchRoomEventViewModel : BaseViewModel() {
 
     init {
         viewModelScope.launch {
-            LiveListStore.shared().liveState.currentLive.collect { liveInfo ->
-                if (liveInfo.liveID == roomService.joinController.getJoinedRoomId()) {
-                    onLiveInfoChanged(liveInfo)
+            launch {
+                LiveListStore.shared().liveState.currentLive.collect { liveInfo ->
+                    if (liveInfo.liveID == roomService.joinController.getJoinedRoomId()) {
+                        onLiveInfoChanged(liveInfo)
+                    }
                 }
             }
-        }
-        viewModelScope.launch {
-            roomService.messageController.notificationsFlow.collect { notifications ->
-                notifyMicApplyClear(notifications)
+            launch {
+                roomService.messageController.notificationsFlow.map { notifications ->
+                    notifications.mapNotNull {
+                        if (it.content is SendGiftNotificationContent) {
+                            it
+                        } else {
+                            null
+                        }
+                    }
+                }.distinctUntilChanged().collect { notifications ->
+                    notifyMicApplyClear(notifications)
+                }
             }
         }
     }
@@ -72,10 +84,6 @@ class DispatchRoomEventViewModel : BaseViewModel() {
         _hostListFlow.update { adminList }
     }
 
-    fun isHost(): Boolean {
-        return _hostListFlow.value.contains(ProfileModule.getMyUid())
-    }
-
     private fun notifyMicApplyClear(notifications: List<Notification>) {
         var clearGuestApply = false
         var clearPlaymateApply = false

+ 11 - 0
module/room/src/main/java/com/adealink/weparty/room/datasource/local/RoomLocalService.kt

@@ -5,6 +5,7 @@ import com.adealink.frame.storage.sp.TypeDelegationPrefs
 import com.adealink.frame.util.AppUtil
 import com.adealink.weparty.module.profile.ProfileModule
 import com.adealink.weparty.storage.PREF_ROOM
+import com.adealink.weparty.util.isSameDay
 
 object RoomLocalService : TypeDelegationPrefs(
     prefs = {
@@ -15,5 +16,15 @@ object RoomLocalService : TypeDelegationPrefs(
     }
 ) {
 
+    var lastEnterRoomId: String by PrefKey("key_last_enter_room", "")
+
+    /**
+     * 上一次钻石不足提示提醒的时间
+     */
+    var lastDiamondRemindTs: Long by PrefKey("key_last_diamond_remind_ts", 0)
+
+    fun showDiamondRemind(): Boolean {
+        return !isSameDay(lastDiamondRemindTs, System.currentTimeMillis())
+    }
 
 }

+ 176 - 92
module/room/src/main/java/com/adealink/weparty/room/gift/RoomGiftPanelDialog.kt

@@ -1,29 +1,43 @@
 package com.adealink.weparty.room.gift
 
-import androidx.core.content.ContextCompat
 import androidx.lifecycle.lifecycleScope
 import androidx.recyclerview.widget.GridLayoutManager
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
+import com.adealink.frame.aab.util.getCompatString
 import com.adealink.frame.base.Rlt
 import com.adealink.frame.base.fastLazy
 import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.router.Router
 import com.adealink.frame.util.onClick
+import com.adealink.weparty.commonui.ext.dp
 import com.adealink.weparty.commonui.ext.gone
 import com.adealink.weparty.commonui.ext.show
 import com.adealink.weparty.commonui.recycleview.adapter.MultiTypeListAdapter
+import com.adealink.weparty.commonui.recycleview.diffutil.BaseListDiffUtil
+import com.adealink.weparty.commonui.recycleview.itemdecoration.GridSpacingItemDecoration
 import com.adealink.weparty.commonui.toast.util.showFailedToast
+import com.adealink.weparty.commonui.toast.util.showToast
 import com.adealink.weparty.commonui.widget.BottomDialogFragment
+import com.adealink.weparty.commonui.widget.CommonDialog
+import com.adealink.weparty.commonui.widget.NumberIncreaseView
 import com.adealink.weparty.module.gift.GiftModule
+import com.adealink.weparty.module.gift.data.GiftInfo
 import com.adealink.weparty.module.profile.ProfileModule
 import com.adealink.weparty.module.room.data.isSeatEmpty
+import com.adealink.weparty.module.wallet.Wallet
 import com.adealink.weparty.module.wallet.WalletModule
+import com.adealink.weparty.module.wallet.data.Currency
 import com.adealink.weparty.room.R
 import com.adealink.weparty.room.databinding.DialogRoomGiftPanelBinding
+import com.adealink.weparty.room.datasource.local.RoomLocalService
 import com.adealink.weparty.room.gift.adapter.RoomGiftItemViewBinder
+import com.adealink.weparty.room.gift.adapter.RoomGiftTargetUserItemViewBinder
 import com.adealink.weparty.room.gift.adapter.RoomGiftUserItemViewBinder
 import com.adealink.weparty.room.gift.data.RoomGiftItemData
+import com.adealink.weparty.room.gift.data.RoomGiftTargetUserItemData
 import com.adealink.weparty.room.gift.data.RoomGiftUserItemData
+import com.adealink.weparty.room.gift.dialog.DiamondRechargeDialog
 import com.adealink.weparty.room.micseat.dispatcenter.getMicIndexName
 import com.adealink.weparty.room.sdk.service.roomService
 import com.adealink.weparty.util.formatNumberStr
@@ -34,52 +48,146 @@ class RoomGiftPanelDialog : BottomDialogFragment(R.layout.dialog_room_gift_panel
     private val binding by viewBinding(DialogRoomGiftPanelBinding::bind)
     private val roomGiftViewModel by fastLazy { GiftModule.getGiftViewModel(requireActivity()) }
     private val walletViewModel by fastLazy { WalletModule.getWalletViewModel(requireActivity()) }
-    private val userAdapter by fastLazy { MultiTypeListAdapter<RoomGiftUserItemData>() }
-    private val giftAdapter by fastLazy { MultiTypeListAdapter<RoomGiftItemData>() }
-    private var tagetUid: String? = null
-    private var giftQuantity = 1
+    private val userAdapter by fastLazy { MultiTypeListAdapter(BaseListDiffUtil()) }
+    private var userItemList = mutableListOf<RoomGiftUserItemData>()
+    private val giftAdapter by fastLazy { MultiTypeListAdapter<RoomGiftItemData>(BaseListDiffUtil()) }
+    private var giftItemList = mutableListOf<RoomGiftItemData>()
+    private var targetUid: String? = null
+
+    private var selectGift: GiftInfo? = null
+
+    override val height: Int = 400.dp()
 
     fun setTargetUid(uid: String?) {
-        this.tagetUid = uid
+        this.targetUid = uid
+    }
+
+    private fun sendOnlyOne(): Boolean {
+        return !targetUid.isNullOrEmpty()
     }
 
     override fun initViews() {
         super.initViews()
-        initRecyclerViews()
-        initListeners()
+        binding.tvBalance.onClick {
+            goDiamondExchangePage()
+        }
+        //顶部用户
+        userAdapter.register(RoomGiftTargetUserItemViewBinder())
+        userAdapter.register(RoomGiftUserItemViewBinder { clickedItem ->
+            if (sendOnlyOne()) {
+                return@RoomGiftUserItemViewBinder
+            }
+            userItemList.forEach {
+                if (it.userInfo?.uid == clickedItem.userInfo?.uid) {
+                    it.isSelected = !it.isSelected
+                }
+            }
+            userAdapter.submitList(userItemList)
+            updateBottomPanel()
+        })
+        binding.rvUsers.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
+        binding.rvUsers.adapter = userAdapter
+        //礼物
+        giftAdapter.register(RoomGiftItemViewBinder { clickedItem ->
+            giftItemList.forEach { itemData ->
+                itemData.isSelected = itemData.giftInfo.id == clickedItem.giftInfo.id
+                if (itemData.isSelected) {
+                    this@RoomGiftPanelDialog.selectGift = itemData.giftInfo
+                }
+            }
+            giftAdapter.submitList(giftItemList)
+            binding.vNumber.setNumber(1)
+            updateBottomPanel()
+        })
+        binding.rvGifts.layoutManager = GridLayoutManager(context, 4)
+        binding.rvGifts.addItemDecoration(
+            GridSpacingItemDecoration(4, 0, 10.dp(), false)
+        )
+        binding.rvGifts.adapter = giftAdapter
+
+        binding.vNumber.setListener(object : NumberIncreaseView.OnNumberChangedListener {
+            override fun onNumberChanged(number: Int) {
+                updateBottomPanel()
+            }
+        })
+        binding.btnSend.onClick {
+            val toUids = if (sendOnlyOne()) {
+                targetUid?.let {
+                    listOf(it)
+                }
+            } else {
+                userAdapter.getCurrentList()
+                    .mapNotNull {
+                        if (it is RoomGiftUserItemData && it.isSelected && it.userInfo?.uid != ProfileModule.getMyUid()) {
+                            it.userInfo?.uid
+                        } else {
+                            null
+                        }
+                    }
+            }
+            if (toUids.isNullOrEmpty()) {
+                showToast(R.string.room_send_gift_to_uids_empty)
+                return@onClick
+            }
+            val selectedGift = giftAdapter.getCurrentList().find { it.isSelected }?.giftInfo
+            if (selectedGift == null) {
+                showToast(R.string.room_send_gift_gift_no_select)
+                return@onClick
+            }
+            val giftCount = binding.vNumber.getNumber()
+            if (giftCount <= 0) {
+                showToast(R.string.room_send_gift_count_empty)
+                return@onClick
+            }
+            val totalPrice = selectedGift.price * giftCount
+            val diamond = walletViewModel?.getDiamond() ?: 0.0
+            if (totalPrice > diamond) {
+                //余额不足
+                rechargeDiamond(totalPrice.toDouble(), sendGift = {
+                    sendGiftInner(toUids.toSet(), selectedGift, giftCount, true)
+                })
+                return@onClick
+            }
+            sendGiftInner(toUids.toSet(), selectedGift, giftCount)
+        }
         updateBottomPanel()
     }
 
     override fun observeViewModel() {
         super.observeViewModel()
         roomGiftViewModel?.giftsLD?.observe(viewLifecycleOwner) { gifts ->
-            giftAdapter.submitList(
-                gifts?.map {
-                    RoomGiftItemData(it)
-                } ?: emptyList()
-            )
+            val itemList = gifts?.map {
+                RoomGiftItemData(it)
+            } ?: emptyList()
+            giftItemList.clear()
+            giftItemList.addAll(itemList)
+            giftAdapter.submitList(giftItemList)
         }
         walletViewModel?.diamondLD?.observe(viewLifecycleOwner) {
             binding.tvBalance.text = formatNumberStr(it)
         }
-        val tagetUid = tagetUid
+        val tagetUid = targetUid
         if (tagetUid.isNullOrEmpty()) {
             lifecycleScope.launch {
                 roomService.seatController.micSeatsFlow.collect { seatInfos ->
-                    val seatInfoList = seatInfos.entries.filter { !it.value.isSeatEmpty() }.sortedBy { it.key }
-                    userAdapter.submitList(
-                        seatInfoList.map { seatInfo ->
-                            RoomGiftUserItemData(
-                                ProfileModule.getCacheUserInfo(seatInfo.value.userInfo.userID),
-                                getMicIndexName(seatInfo.key)
-                            )
-                        }
-                    )
+                    val seatInfoList = seatInfos.entries.filter {
+                        !it.value.isSeatEmpty() && it.value.userInfo.userID != ProfileModule.getMyUid()
+                    }.sortedBy { it.key }
+                    val itemList = seatInfoList.map { seatInfo ->
+                        RoomGiftUserItemData(
+                            ProfileModule.getCacheUserInfo(seatInfo.value.userInfo.userID),
+                            getMicIndexName(seatInfo.key),
+                            isSelected = userItemList.find { it.userInfo?.uid == seatInfo.value.userInfo.userID }?.isSelected ?: false
+                        )
+                    }
+                    userItemList.clear()
+                    userItemList.addAll(itemList)
+                    userAdapter.submitList(userItemList)
                 }
             }
         } else {
             userAdapter.submitList(
-                listOf(RoomGiftUserItemData(ProfileModule.getCacheUserInfo(tagetUid), ""))
+                listOf(RoomGiftTargetUserItemData(ProfileModule.getCacheUserInfo(tagetUid)))
             )
         }
     }
@@ -94,84 +202,60 @@ class RoomGiftPanelDialog : BottomDialogFragment(R.layout.dialog_room_gift_panel
         walletViewModel?.refreshWalletData()
     }
 
-    private fun initRecyclerViews() {
-        // Users RecyclerView
-        userAdapter.register(RoomGiftUserItemViewBinder { clickedItem ->
-            val list = userAdapter.getCurrentList().map {
-                if (it.userInfo?.uid == clickedItem.userInfo?.uid) {
-                    it.copy(isSelected = !it.isSelected)
-                } else {
-                    it.copy()
-                }
-            }
-            userAdapter.submitList(list)
-            updateBottomPanel()
-        })
-        binding.rvUsers.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
-        binding.rvUsers.adapter = userAdapter
-
-        // Gifts RecyclerView
-        giftAdapter.register(RoomGiftItemViewBinder { clickedItem ->
-            val list = giftAdapter.getCurrentList().map {
-                it.copy(isSelected = it.giftInfo.id == clickedItem.giftInfo.id)
-            }
-            giftAdapter.submitList(list)
-            giftQuantity = 1 // Reset quantity when a new gift is selected
-            updateBottomPanel()
-        })
-        binding.rvGifts.layoutManager = GridLayoutManager(context, 4)
-        binding.rvGifts.adapter = giftAdapter
+    private fun updateBottomPanel() {
+        if (selectGift != null) {
+            binding.vNumber.show()
+            binding.btnSend.isEnabled = true
+            binding.btnSend.alpha = 1f
+        } else {
+            binding.vNumber.gone()
+            binding.btnSend.isEnabled = false
+            binding.btnSend.alpha = 0.4f
+        }
     }
 
-    private fun initListeners() {
-        binding.btnPlus.onClick {
-            giftQuantity++
-            updateBottomPanel()
-        }
-        binding.btnMinus.onClick {
-            if (giftQuantity > 1) {
-                giftQuantity--
-                updateBottomPanel()
+    private fun rechargeDiamond(totalPrice: Double, sendGift: () -> Unit) {
+        val diamond = walletViewModel?.getDiamond() ?: 0.0
+        val coin = walletViewModel?.getCoin() ?: 0.0
+        val totalDiamond = diamond + coin * 1000
+        if (totalPrice < totalDiamond) {
+            //兑换后能满足送礼金额
+            val showRemind = RoomLocalService.showDiamondRemind()
+            if (showRemind) {
+                DiamondRechargeDialog().apply {
+                    setOnConfirmClick { sendGift.invoke() }
+                }.show(childFragmentManager)
+            } else {
+                sendGift.invoke()
             }
+            return
         }
-        binding.btnSend.onClick {
-            val toUids = userAdapter.getCurrentList()
-                .filter { it.isSelected && it.userInfo?.uid != ProfileModule.getMyUid() }
-                .mapNotNull { it.userInfo?.uid }
-            if (toUids.isNullOrEmpty()) {
-                return@onClick
-            }
-            val selectedGift = giftAdapter.getCurrentList().find { it.isSelected }?.giftInfo
-            if (selectedGift == null) {
-                return@onClick
+        //金币兑换后也不足, 提示充值
+        CommonDialog.Builder()
+            .title(getCompatString(R.string.room_send_gift_balance_not_enough))
+            .setShowDefaultCancel(true)
+            .onPositive {
+                WalletModule.showRechargeDialog(childFragmentManager, Currency.DIAMOND)
             }
-            roomGiftViewModel?.sendGift(toUids.toSet(), selectedGift, giftQuantity)?.observe(viewLifecycleOwner) { rlt ->
-                when (rlt) {
-                    is Rlt.Failed -> {
-                        showFailedToast(rlt)
-                    }
+            .show(childFragmentManager)
+    }
 
-                    is Rlt.Success<*> -> {
-                        walletViewModel?.refreshWalletData()
-                    }
+    private fun sendGiftInner(toUids: Set<String>, gift: GiftInfo, count: Int, useCoinIfLess: Boolean = true) {
+        roomGiftViewModel?.sendGift(toUids, gift, count, useCoinIfLess)?.observe(viewLifecycleOwner) { rlt ->
+            when (rlt) {
+                is Rlt.Failed -> {
+                    showFailedToast(rlt)
+                }
+
+                is Rlt.Success<*> -> {
+                    walletViewModel?.refreshWalletData()
                 }
             }
         }
     }
 
-    private fun updateBottomPanel() {
-        val selectedGift = giftAdapter.getCurrentList().find { it.isSelected }
-        if (selectedGift != null) {
-            binding.llQuantity.show()
-            binding.tvQuantity.text = giftQuantity.toString()
-            binding.btnSend.setBackgroundResource(R.drawable.shape_room_gift_btn_send_bg_selected)
-            val color = ContextCompat.getColor(requireContext(), com.adealink.weparty.R.color.color_FF2C2945)
-            binding.btnSend.setTextColor(color)
-        } else {
-            binding.llQuantity.gone()
-            binding.btnSend.setBackgroundResource(R.drawable.shape_room_gift_btn_send_bg_unselected)
-            val color = ContextCompat.getColor(requireContext(), com.adealink.weparty.R.color.color_80FFFFFF)
-            binding.btnSend.setTextColor(color)
-        }
+    private fun goDiamondExchangePage() {
+        val act = activity ?: return
+        Router.build(act, Wallet.Wallet.PATH).start()
     }
 }

+ 46 - 7
module/room/src/main/java/com/adealink/weparty/room/gift/adapter/RoomGiftItemViewBinder.kt

@@ -1,7 +1,11 @@
 package com.adealink.weparty.room.gift.adapter
 
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
 import android.view.LayoutInflater
+import android.view.View
 import android.view.ViewGroup
+import com.adealink.frame.aab.util.getCompatDrawable
 import com.adealink.frame.util.onClick
 import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
 import com.adealink.weparty.commonui.recycleview.adapter.multitype.ItemViewBinder
@@ -9,6 +13,7 @@ import com.adealink.weparty.room.R
 import com.adealink.weparty.room.databinding.ItemRoomGiftBinding
 import com.adealink.weparty.room.gift.data.RoomGiftItemData
 import com.adealink.weparty.util.formatNumber
+import java.lang.ref.WeakReference
 
 class RoomGiftItemViewBinder(private val listener: (RoomGiftItemData) -> Unit) :
     ItemViewBinder<RoomGiftItemData, RoomGiftItemViewBinder.ViewHolder>() {
@@ -37,26 +42,60 @@ class RoomGiftItemViewBinder(private val listener: (RoomGiftItemData) -> Unit) :
 
         fun initView() {
             binding.root.onClick {
-
+                playClickAnim(binding.ivIcon)
+                item?.let {
+                    listener.invoke(it)
+                }
             }
         }
 
         fun update(item: RoomGiftItemData) {
             this.item = item
-
             binding.ivIcon.setImageUrl(item.giftInfo.resource?.icon)
             binding.tvName.text = item.giftInfo.resource?.getDisplayName()
             binding.tvPrice.text = formatNumber(item.giftInfo.price, false)
-
             if (item.isSelected) {
-                binding.root.setBackgroundResource(R.drawable.shape_room_gift_item_bg_selected)
+                binding.root.background = getCompatDrawable(R.drawable.room_gift_selected_bg)
             } else {
-                binding.root.setBackgroundResource(R.drawable.shape_room_gift_item_bg)
+                binding.root.background = null
             }
+        }
+    }
 
-            binding.root.onClick {
-                listener.invoke(item)
+    private fun playClickAnim(iconView: View) {
+        GiftIconAnimator(WeakReference(iconView)).start()
+    }
+
+    class GiftIconAnimator(val viewRef: WeakReference<View?>) {
+
+        private var animatorSet = AnimatorSet()
+
+        private val view: View?
+            get() {
+                return viewRef.get()
+            }
+
+        init {
+            view?.let {
+                val scaleX = ObjectAnimator.ofFloat(it, View.SCALE_X, 1F, 0.8F, 1F)
+                val scaleY = ObjectAnimator.ofFloat(it, View.SCALE_Y, 1F, 0.8F, 1F)
+                animatorSet.play(scaleX)?.with(scaleY)
+                animatorSet.duration = 200
             }
         }
+
+        fun start() {
+            if (animatorSet.isRunning) {
+                return
+            }
+
+            animatorSet.start()
+        }
+
+        fun cancel() {
+            animatorSet.cancel()
+            view?.scaleX = 1F
+            view?.scaleY = 1f
+        }
     }
 }

+ 29 - 0
module/room/src/main/java/com/adealink/weparty/room/gift/adapter/RoomGiftTargetUserItemViewBinder.kt

@@ -0,0 +1,29 @@
+package com.adealink.weparty.room.gift.adapter
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
+import com.adealink.weparty.commonui.recycleview.adapter.multitype.ItemViewBinder
+import com.adealink.weparty.room.R
+import com.adealink.weparty.room.databinding.ItemRoomGiftTargetUserBinding
+import com.adealink.weparty.room.gift.data.RoomGiftTargetUserItemData
+
+class RoomGiftTargetUserItemViewBinder :
+    ItemViewBinder<RoomGiftTargetUserItemData, BindingViewHolder<ItemRoomGiftTargetUserBinding>>() {
+
+    override fun onCreateViewHolder(
+        inflater: LayoutInflater,
+        parent: ViewGroup
+    ): BindingViewHolder<ItemRoomGiftTargetUserBinding> {
+        return BindingViewHolder(ItemRoomGiftTargetUserBinding.inflate(inflater, parent, false))
+    }
+
+    override fun onBindViewHolder(
+        holder: BindingViewHolder<ItemRoomGiftTargetUserBinding>,
+        item: RoomGiftTargetUserItemData
+    ) {
+        holder.binding.ivAvatar.setImageUrl(item.userInfo?.avatar)
+        holder.binding.ivAvatar.setBackgroundResource(R.drawable.room_gift_user_selected_bg)
+        holder.binding.tvName.text = item.userInfo?.nickName
+    }
+}

+ 14 - 6
module/room/src/main/java/com/adealink/weparty/room/gift/adapter/RoomGiftUserItemViewBinder.kt

@@ -4,6 +4,8 @@ import android.view.LayoutInflater
 import android.view.ViewGroup
 import com.adealink.frame.aab.util.getCompatColor
 import com.adealink.frame.util.onClick
+import com.adealink.weparty.commonui.ext.gone
+import com.adealink.weparty.commonui.ext.show
 import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
 import com.adealink.weparty.commonui.recycleview.adapter.multitype.ItemViewBinder
 import com.adealink.weparty.room.R
@@ -12,7 +14,8 @@ import com.adealink.weparty.room.gift.data.RoomGiftUserItemData
 import com.adealink.weparty.R as APP_R
 
 class RoomGiftUserItemViewBinder(
-    private val listener: (RoomGiftUserItemData) -> Unit) :
+    private val listener: (RoomGiftUserItemData) -> Unit
+) :
     ItemViewBinder<RoomGiftUserItemData, BindingViewHolder<ItemRoomGiftUserBinding>>() {
 
     override fun onCreateViewHolder(
@@ -27,15 +30,20 @@ class RoomGiftUserItemViewBinder(
         item: RoomGiftUserItemData
     ) {
         holder.binding.ivAvatar.setImageUrl(item.userInfo?.avatar)
-        holder.binding.tvTag.text = item.tag
+        if (item.tag.isNullOrEmpty()) {
+            holder.binding.tvTag.gone()
+        } else {
+            holder.binding.tvTag.show()
+            holder.binding.tvTag.text = item.tag
+        }
 
         if (item.isSelected) {
-            holder.binding.ivAvatar.setBackgroundResource(R.drawable.shape_room_gift_user_avatar_border)
-            holder.binding.tvTag.setBackgroundResource(R.drawable.shape_room_gift_user_tag_bg_selected)
+            holder.binding.vSelected.show()
+            holder.binding.tvTag.setBackgroundResource(R.drawable.room_gift_user_tag_bg_selected)
             holder.binding.tvTag.setTextColor(getCompatColor(APP_R.color.white))
         } else {
-            holder.binding.ivAvatar.background = null
-            holder.binding.tvTag.setBackgroundResource(R.drawable.shape_room_gift_user_tag_bg)
+            holder.binding.vSelected.gone()
+            holder.binding.tvTag.setBackgroundResource(R.drawable.room_gift_user_tag_bg)
             holder.binding.tvTag.setTextColor(getCompatColor(APP_R.color.color_FF2C2945))
         }
 

+ 17 - 0
module/room/src/main/java/com/adealink/weparty/room/gift/data/GiftData.kt

@@ -10,6 +10,10 @@ data class RoomGiftUserItemData(
     var isSelected: Boolean = false
 ) : BaseListItemData {
 
+    override fun areItemsTheSame(newItem: Any): Boolean {
+        return false
+    }
+
     override fun areContentsTheSame(newItem: Any): Boolean {
         val other = (newItem as? RoomGiftUserItemData) ?: return false
         return userInfo?.uid == other.userInfo?.uid
@@ -18,11 +22,24 @@ data class RoomGiftUserItemData(
     }
 }
 
+data class RoomGiftTargetUserItemData(
+    val userInfo: UserInfo?,
+) : BaseListItemData {
+    override fun areContentsTheSame(newItem: Any): Boolean {
+        val other = (newItem as? RoomGiftTargetUserItemData) ?: return false
+        return userInfo?.uid == other.userInfo?.uid
+    }
+}
+
 data class RoomGiftItemData(
     val giftInfo: GiftInfo,
     var isSelected: Boolean = false
 ) : BaseListItemData {
 
+    override fun areItemsTheSame(newItem: Any): Boolean {
+        return false
+    }
+
     override fun areContentsTheSame(newItem: Any): Boolean {
         val other = (newItem as? RoomGiftItemData) ?: return false
         return giftInfo.id == other.giftInfo.id

+ 129 - 0
module/room/src/main/java/com/adealink/weparty/room/gift/dialog/DiamondRechargeDialog.kt

@@ -0,0 +1,129 @@
+package com.adealink.weparty.room.gift.dialog
+
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.text.SpannableStringBuilder
+import android.view.Gravity
+import android.view.Window
+import android.view.WindowManager
+import com.adealink.frame.aab.util.getCompatDrawable
+import com.adealink.frame.aab.util.getCompatString
+import com.adealink.frame.base.fastLazy
+import com.adealink.frame.ext.findAndSetSpan
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.util.onClick
+import com.adealink.weparty.commonui.dialogfragment.BaseDialogFragment
+import com.adealink.weparty.commonui.ext.dp
+import com.adealink.weparty.commonui.widget.CenterImageSpan
+import com.adealink.weparty.module.wallet.WalletModule
+import com.adealink.weparty.room.R
+import com.adealink.weparty.room.databinding.DialogDiamondRechargeBinding
+import com.adealink.weparty.room.datasource.local.RoomLocalService
+import com.adealink.weparty.util.formatNumberStr
+import com.adealink.weparty.R as APP_R
+
+class DiamondRechargeDialog : BaseDialogFragment(R.layout.dialog_diamond_recharge) {
+
+    private val binding by viewBinding(DialogDiamondRechargeBinding::bind)
+
+    private val walletViewModel by fastLazy { WalletModule.getWalletViewModel(requireActivity()) }
+
+    private var onConfirmClick: (() -> Unit)? = null
+
+    private var todayNeverRemind = false
+
+    fun setOnConfirmClick(onConfirmClick: (() -> Unit)? = null) {
+        this.onConfirmClick = onConfirmClick
+    }
+
+    override fun resetWindowAttributes(window: Window) {
+        super.resetWindowAttributes(window)
+        window.setLayout(
+            300.dp(),
+            WindowManager.LayoutParams.WRAP_CONTENT
+        )
+        window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+        val attr = window.attributes
+        attr.gravity = Gravity.CENTER
+        attr.dimAmount = 0f
+        window.attributes = attr
+    }
+
+    override fun initViews() {
+        super.initViews()
+        updateCoin(walletViewModel?.getCoin() ?: 0.0)
+        updateExchangeRate()
+        binding.topCloseBtn.onClick {
+            dismiss()
+        }
+        binding.btnConfirm.onClick {
+            if (todayNeverRemind) {
+                RoomLocalService.lastDiamondRemindTs = System.currentTimeMillis()
+            }
+            onConfirmClick?.invoke()
+            dismiss()
+        }
+        binding.btnCancel.onClick {
+            dismiss()
+        }
+        binding.clNeverRemind.onClick {
+            toggleNeverRemind()
+        }
+    }
+
+    private fun updateCoin(coin: Double) {
+        val coinStr = formatNumberStr(coin)
+        val text = getCompatString(R.string.room_gift_coin_available, coinStr)
+        binding.tvContent.text = SpannableStringBuilder(text).apply {
+            findAndSetSpan(
+                CenterImageSpan(
+                    getCompatDrawable(APP_R.drawable.common_wallet_coin_ic).apply {
+                        setBounds(0, 0, 14.dp(), 14.dp())
+                    }
+                ), "[coin]"
+            )
+        }
+    }
+
+    private fun updateExchangeRate() {
+        val text = getCompatString(R.string.room_gift_exchange_rate)
+        binding.tvExchanged.text = SpannableStringBuilder(text).apply {
+            findAndSetSpan(
+                CenterImageSpan(
+                    getCompatDrawable(APP_R.drawable.common_wallet_coin_ic).apply {
+                        setBounds(0, 0, 14.dp(), 14.dp())
+                    }
+                ), "[coin]"
+            )
+
+            findAndSetSpan(
+                CenterImageSpan(
+                    getCompatDrawable(APP_R.drawable.common_wallet_diamond_ic).apply {
+                        setBounds(0, 0, 14.dp(), 14.dp())
+                    }
+                ), "[diamond]"
+            )
+        }
+    }
+
+    private fun toggleNeverRemind() {
+        todayNeverRemind = !todayNeverRemind
+        if (todayNeverRemind) {
+            binding.ivNeverRemind.setImageResource(APP_R.drawable.common_check_selected_ic)
+        } else {
+            binding.ivNeverRemind.setImageResource(APP_R.drawable.common_check_unselected_ic)
+        }
+    }
+
+    override fun observeViewModel() {
+        super.observeViewModel()
+        walletViewModel?.coinLD?.observe(viewLifecycleOwner) {
+            updateCoin(it)
+        }
+    }
+
+    override fun loadData() {
+        super.loadData()
+    }
+
+}

+ 74 - 0
module/room/src/main/java/com/adealink/weparty/room/gift/viewmodel/RoomGiftViewModel.kt

@@ -0,0 +1,74 @@
+package com.adealink.weparty.room.gift.viewmodel
+
+import com.adealink.frame.data.json.toJsonErrorNull
+import com.adealink.frame.log.Log
+import com.adealink.frame.mvvm.viewmodel.BaseViewModel
+import com.adealink.weparty.module.gift.GiftModule
+import com.adealink.weparty.module.room.chat.data.ClearMicNotificationContent
+import com.adealink.weparty.module.room.chat.data.Notification
+import com.adealink.weparty.module.room.chat.data.ROOM_MESSAGE_ENTER_ROOM
+import com.adealink.weparty.module.room.chat.data.ROOM_MESSAGE_SEND_GIFT
+import com.adealink.weparty.module.room.chat.data.SendGiftContent
+import com.adealink.weparty.module.room.chat.data.SendGiftNotificationContent
+import com.adealink.weparty.room.sdk.service.roomService
+import io.trtc.tuikit.atomicxcore.api.barrage.Barrage
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+class RoomGiftViewModel : BaseViewModel() {
+
+    init {
+        viewModelScope.launch {
+            launch {
+                roomService.messageController.notificationsFlow.map { notifications ->
+                    notifications.mapNotNull { if (it.content is SendGiftNotificationContent) it else null }
+                }.distinctUntilChanged().collect { notifications ->
+                    Log.d("zhangfei", "RoomGiftViewModel, ${notifications.joinToString(separator = ",") { it.barrage.sequence.toString() }}")
+                    notifySendGift(notifications)
+                }
+            }
+        }
+    }
+
+    private fun notifySendGift(notifications: List<Notification>) {
+        val roomId = roomService.joinController.getJoinedRoomId() ?: return
+        notifications.mapNotNull {
+            if (it.barrage.liveID == roomId) {
+                it.content as? SendGiftNotificationContent
+            } else {
+                null
+            }
+        }.forEach { content ->
+            addSendGiftMessage(content)
+        }
+    }
+
+    private fun addSendGiftMessage(
+        content: SendGiftNotificationContent
+    ) {
+        viewModelScope.launch {
+            for (contentData in content.list) {
+                for (receiver in contentData.receivers) {
+                    val messageContent = SendGiftContent(
+                        contentData.sender,
+                        receiver,
+                        contentData.quantity,
+                        contentData.resId,
+                        hashMapOf(
+                            contentData.sender to contentData.getUserInfo(contentData.sender),
+                            receiver to contentData.getUserInfo(receiver)
+                        )
+                    )
+                    roomService.messageController.sendLocalMessage(
+                        Barrage().apply {
+                            businessID = ROOM_MESSAGE_SEND_GIFT
+                            data = toJsonErrorNull(messageContent) ?: ""
+                        }
+                    )
+                }
+            }
+        }
+    }
+
+}

+ 2 - 0
module/room/src/main/java/com/adealink/weparty/room/interceptor/EnterRoomUriInterceptor.kt

@@ -28,6 +28,7 @@ import com.adealink.weparty.room.constant.enterRoomJoinReqTime
 import com.adealink.weparty.room.constant.enterRoomJoinResTime
 import com.adealink.weparty.room.constant.enterRoomUILoadTime
 import com.adealink.weparty.room.constant.markEnterRoomTime
+import com.adealink.weparty.room.datasource.local.RoomLocalService
 import com.adealink.weparty.room.sdk.service.roomService
 import com.adealink.weparty.util.parcelableExtra
 import kotlinx.coroutines.CoroutineScope
@@ -174,6 +175,7 @@ class EnterRoomUriInterceptor : UriInterceptor {
         when (result) {
             is Rlt.Success -> {
                 Log.i(TAG_ROOM_ENTER_ROOM, "EnterRoomUriInterceptor, join room success, proceed()")
+                RoomLocalService.lastEnterRoomId = enterRoomInfo.roomId
                 GiftModule.getGifts(enterRoomInfo.roomId, true)
                 chain.proceed(request)
             }

+ 2 - 2
module/room/src/main/java/com/adealink/weparty/room/invite/InviteMicDialog.kt

@@ -19,11 +19,9 @@ import com.adealink.weparty.commonui.ext.show
 import com.adealink.weparty.commonui.recycleview.adapter.MultiTypeListAdapter
 import com.adealink.weparty.commonui.toast.util.showFailedToast
 import com.adealink.weparty.commonui.widget.BottomDialogFragment
-import com.adealink.weparty.commonui.widget.util.getColorX
 import com.adealink.weparty.module.playmate.data.PlaymateCategoryData
 import com.adealink.weparty.module.room.Room
 import com.adealink.weparty.room.R
-import com.adealink.weparty.room.applymic.data.ApplyMicItemData
 import com.adealink.weparty.room.databinding.DialogRoomInviteMicBinding
 import com.adealink.weparty.room.invite.adapter.RoomMemberViewBinder
 import com.adealink.weparty.room.invite.data.RoomMemberItemData
@@ -57,6 +55,8 @@ class InviteMicDialog : BottomDialogFragment(R.layout.dialog_room_invite_mic) {
         this.inviteMicIndex = index
     }
 
+    override val dimAmount: Float = 0.7f
+
     override fun initViews() {
         super.initViews()
 

+ 3 - 2
module/room/src/main/java/com/adealink/weparty/room/sdk/controller/IMessageController.kt

@@ -1,6 +1,7 @@
 package com.adealink.weparty.room.sdk.controller
 
 import com.adealink.frame.base.Rlt
+import com.adealink.weparty.module.room.chat.data.Message
 import com.adealink.weparty.module.room.chat.data.Notification
 import com.adealink.weparty.room.sdk.listener.IMessageListener
 import io.trtc.tuikit.atomicxcore.api.barrage.Barrage
@@ -8,9 +9,9 @@ import kotlinx.coroutines.flow.StateFlow
 
 interface IMessageController<L : IMessageListener> : IController<L> {
 
-    val messagesFlow: StateFlow<List<Barrage>>
+    val messagesFlow: StateFlow<List<Message>>
 
-    val newMessagesFlow: StateFlow<List<Barrage>>
+    val newMessagesFlow: StateFlow<List<Message>>
 
     val notificationsFlow: StateFlow<List<Notification>>
 

+ 4 - 1
module/room/src/main/java/com/adealink/weparty/room/sdk/controller/impl/JoinController.kt

@@ -19,6 +19,7 @@ import com.adealink.weparty.room.constant.TAG_ROOM_ENTER_ROOM
 import com.adealink.weparty.room.constant.enterRoomJoinReqTime
 import com.adealink.weparty.room.constant.enterRoomJoinResTime
 import com.adealink.weparty.room.constant.logRoomTime
+import com.adealink.weparty.room.datasource.local.RoomLocalService
 import com.adealink.weparty.room.sdk.context.IRoomContext
 import com.adealink.weparty.room.sdk.controller.BaseController
 import com.adealink.weparty.room.sdk.controller.IJoinController
@@ -180,7 +181,9 @@ open class JoinController(override val ctx: IRoomContext, serialHandler: Handler
             }
 
             //先尝试退上次未退出的房间,然后再进房,重进房不执行退房逻辑
-            leaveLastRoom(getJoinedRoomId(), reason = LeaveRoomReason.ENTER_OTHER_ROOM, false)
+            if (RoomLocalService.lastEnterRoomId != req.roomId) {
+                leaveLastRoom(RoomLocalService.lastEnterRoomId, reason = LeaveRoomReason.ENTER_OTHER_ROOM, false)
+            }
 
             joiningRoomId = null
             joinedRoomInfo = (joinRoomRes as Rlt.Success).data

+ 38 - 30
module/room/src/main/java/com/adealink/weparty/room/sdk/controller/impl/MessageController.kt

@@ -5,10 +5,13 @@ import com.adealink.frame.base.CommonDataNullError
 import com.adealink.frame.base.IError
 import com.adealink.frame.base.Rlt
 import com.adealink.frame.ext.safeSubList
+import com.adealink.frame.log.Log
 import com.adealink.frame.room.data.FlowStateInfo
 import com.adealink.weparty.module.room.chat.data.MESSAGE_TYPE
 import com.adealink.weparty.module.room.chat.data.MESSAGE_TYPE_TEXT
+import com.adealink.weparty.module.room.chat.data.Message
 import com.adealink.weparty.module.room.chat.data.Notification
+import com.adealink.weparty.module.room.chat.data.toMessages
 import com.adealink.weparty.module.room.chat.data.toNotifications
 import com.adealink.weparty.room.sdk.context.IRoomContext
 import com.adealink.weparty.room.sdk.controller.BaseController
@@ -48,12 +51,13 @@ class MessageController(override val ctx: IRoomContext, serialHandler: Handler)
     }
 
     // 对外暴露【全量】消息列表的Flow,方便UI层订阅
-    private val _messagesFlow = MutableStateFlow<List<Barrage>>(emptyList())
-    override val messagesFlow: StateFlow<List<Barrage>> = _messagesFlow.asStateFlow()
+    private val _messagesFlow = MutableStateFlow<List<Message>>(emptyList())
+    override val messagesFlow: StateFlow<List<Message>> = _messagesFlow.asStateFlow()
 
-    private val _newMessagesFlow = MutableStateFlow<List<Barrage>>(emptyList())
-    override val newMessagesFlow: StateFlow<List<Barrage>> = _newMessagesFlow.asStateFlow()
+    private val _newMessagesFlow = MutableStateFlow<List<Message>>(emptyList())
+    override val newMessagesFlow: StateFlow<List<Message>> = _newMessagesFlow.asStateFlow()
 
+    private val _oldNotificationFlow = MutableStateFlow<List<Notification>>(emptyList())
     private val _notificationFlow = MutableStateFlow<List<Notification>>(emptyList())
     override val notificationsFlow: StateFlow<List<Notification>> = _notificationFlow.asStateFlow()
 
@@ -61,40 +65,43 @@ class MessageController(override val ctx: IRoomContext, serialHandler: Handler)
         messageListJob = launch {
             barrageStore?.barrageState?.messageList?.collect { allMessages ->
                 val roomId = roomService.joinController.getJoinedRoomId() ?: return@collect
-                val textMessages = allMessages.filter { it.liveID == roomId && it.messageType == BarrageType.TEXT }
+                val textMessages = allMessages.filter { it.liveID == roomId && it.messageType == BarrageType.TEXT }.toMessages()
                 handleTextMessage(textMessages)
 
-                val notifications = allMessages.filter { it.liveID == roomId && it.messageType == BarrageType.CUSTOM }
+                val notifications = allMessages.filter { it.liveID == roomId && it.messageType == BarrageType.CUSTOM }.toNotifications()
                 handleNotifications(notifications)
             }
         }
     }
 
-    private fun handleTextMessage(messages: List<Barrage>) {
+    private fun handleTextMessage(messages: List<Message>) {
         val oldMessages = _messagesFlow.value
-
         val lastMessage = oldMessages.lastOrNull()
         if (lastMessage != null) {
-            val lastIndex = messages.indexOfLast { it.sequence == lastMessage.sequence }
+            val lastIndex = messages.indexOfLast { it.barrage.sequence == lastMessage.barrage.sequence }
             val newMessages = messages.safeSubList(lastIndex + 1)
-            _newMessagesFlow.update { newMessages }
+            _newMessagesFlow.value = newMessages
         } else {
-            _newMessagesFlow.update { messages }
+            _newMessagesFlow.value = messages
         }
 
-        _messagesFlow.update { messages }
+        _messagesFlow.value = messages
     }
 
-    private fun handleNotifications(notifications: List<Barrage>) {
-        val oldNotifications = _notificationFlow.value
+    private fun handleNotifications(notifications: List<Notification>) {
+        val oldNotifications = _oldNotificationFlow.value
+        Log.d("zhangfei", "handleNotifications, ${notifications.joinToString(separator = ",") { it.barrage.sequence.toString() }}")
+        Log.d("zhangfei", "   oldNotifications, ${oldNotifications.joinToString(separator = ",") { it.barrage.sequence.toString() }}")
         val lastNotification = oldNotifications.lastOrNull()
         if (lastNotification != null) {
-            val lastIndex = notifications.indexOfLast { it.sequence == lastNotification.barrage.sequence }
-            val newNotifications = notifications.safeSubList(lastIndex + 1).toNotifications()
-            _notificationFlow.update { newNotifications }
+            val lastIndex = notifications.indexOfLast { it.barrage.sequence == lastNotification.barrage.sequence }
+            val newNotifications = notifications.safeSubList(lastIndex + 1)
+            _notificationFlow.value = newNotifications
         } else {
-            _notificationFlow.update { notifications.toNotifications() }
+            _notificationFlow.value = notifications
         }
+
+        _oldNotificationFlow.value = notifications
     }
 
     override suspend fun sendTextMessage(text: String): Rlt<Any> {
@@ -106,21 +113,22 @@ class MessageController(override val ctx: IRoomContext, serialHandler: Handler)
                 }
                 return@suspendCancellableCoroutine
             }
-            barrage.sendTextMessage(text, mapOf(
-                MESSAGE_TYPE to MESSAGE_TYPE_TEXT
-            ), object : CompletionHandler {
-                override fun onFailure(code: Int, desc: String) {
-                    if (continuation.isActive) {
-                        continuation.resume(Rlt.Failed(IError(serverCode = code, msg = desc)))
+            barrage.sendTextMessage(
+                text, mapOf(
+                    MESSAGE_TYPE to MESSAGE_TYPE_TEXT
+                ), object : CompletionHandler {
+                    override fun onFailure(code: Int, desc: String) {
+                        if (continuation.isActive) {
+                            continuation.resume(Rlt.Failed(IError(serverCode = code, msg = desc)))
+                        }
                     }
-                }
 
-                override fun onSuccess() {
-                    if (continuation.isActive) {
-                        continuation.resume(Rlt.Success(Unit))
+                    override fun onSuccess() {
+                        if (continuation.isActive) {
+                            continuation.resume(Rlt.Success(Unit))
+                        }
                     }
-                }
-            })
+                })
         }
     }
 

+ 4 - 0
module/room/src/main/java/com/adealink/weparty/room/viewmodel/RoomViewModelFactory.kt

@@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModelProvider
 import com.adealink.weparty.room.applymic.viewmodel.ApplyMicViewModel
 import com.adealink.weparty.room.chat.viewmodel.ChatMessageViewModel
 import com.adealink.weparty.room.chat.viewmodel.InputStateViewModel
+import com.adealink.weparty.room.gift.viewmodel.RoomGiftViewModel
 import com.adealink.weparty.room.member.viewmodel.RoomMemberViewModel
 import com.adealink.weparty.room.micseat.viewmodel.RoomSeatViewModel
 
@@ -23,6 +24,9 @@ class RoomViewModelFactory : ViewModelProvider.NewInstanceFactory() {
                 isAssignableFrom(RoomMemberViewModel::class.java) ->
                     RoomMemberViewModel()
 
+                isAssignableFrom(RoomGiftViewModel::class.java) ->
+                    RoomGiftViewModel()
+
                 isAssignableFrom(InputStateViewModel::class.java) ->
                     InputStateViewModel()
 

BIN
module/room/src/main/res/drawable-xhdpi/room_gift_selected_bg.png


BIN
module/room/src/main/res/drawable-xhdpi/room_number_add_ic.png


BIN
module/room/src/main/res/drawable-xhdpi/room_number_minus_disable_ic.png


BIN
module/room/src/main/res/drawable-xhdpi/room_number_minus_ic.png


+ 6 - 0
module/room/src/main/res/drawable/room_bottom_send_gift_button.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="20dp" />
+    <solid android:color="@color/color_1AFFFFFF" />
+</shape>

+ 0 - 0
module/room/src/main/res/drawable/shape_room_gift_user_avatar_border.xml → module/room/src/main/res/drawable/room_gift_user_selected_bg.xml


+ 0 - 0
module/room/src/main/res/drawable/shape_room_gift_user_tag_bg.xml → module/room/src/main/res/drawable/room_gift_user_tag_bg.xml


+ 0 - 0
module/room/src/main/res/drawable/shape_room_gift_user_tag_bg_selected.xml → module/room/src/main/res/drawable/room_gift_user_tag_bg_selected.xml


+ 0 - 5
module/room/src/main/res/drawable/shape_room_gift_btn_send_bg.xml

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="#2A86E6" />
-    <corners android:radius="16dp" />
-</shape>

+ 0 - 5
module/room/src/main/res/drawable/shape_room_gift_btn_send_bg_selected.xml

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="#00DFD7" />
-    <corners android:radius="16dp" />
-</shape>

+ 0 - 5
module/room/src/main/res/drawable/shape_room_gift_btn_send_bg_unselected.xml

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="#33FFFFFF" />
-    <corners android:radius="16dp" />
-</shape>

+ 0 - 5
module/room/src/main/res/drawable/shape_room_gift_item_bg.xml

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="@android:color/transparent" />
-    <corners android:radius="12dp" />
-</shape>

+ 0 - 6
module/room/src/main/res/drawable/shape_room_gift_item_bg_selected.xml

@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="#1A00DFD7" />
-    <stroke android:width="1dp" android:color="#FF00DFD7" />
-    <corners android:radius="12dp" />
-</shape>

+ 0 - 5
module/room/src/main/res/drawable/shape_room_gift_panel_bg.xml

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="#242138" />
-    <corners android:topLeftRadius="20dp" android:topRightRadius="20dp" />
-</shape>

+ 0 - 5
module/room/src/main/res/drawable/shape_room_gift_quantity_bg.xml

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="#33FFFFFF" />
-    <corners android:radius="16dp" />
-</shape>

+ 132 - 0
module/room/src/main/res/layout/dialog_diamond_recharge.xml

@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@drawable/commonui_dialog_bg"
+    android:paddingBottom="30dp"
+    app:layout_constraintTop_toTopOf="parent">
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/top_close_btn"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_marginTop="10dp"
+        android:layout_marginEnd="10dp"
+        android:src="@drawable/common_close_ic"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <!--标题-->
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="30dp"
+        android:gravity="center"
+        android:includeFontPadding="false"
+        android:text="@string/room_gift_diamond_less"
+        android:textColor="@color/color_FF4E5969"
+        android:textSize="16sp"
+        app:fontFamily="@font/poppins_semibold"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_content"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="10dp"
+        android:gravity="center"
+        android:includeFontPadding="false"
+        android:textColor="@color/color_FF4E5969"
+        android:textSize="14sp"
+        app:fontFamily="@font/poppins_semibold"
+        app:layout_constraintTop_toBottomOf="@id/tv_title"
+        tools:text="@string/room_gift_coin_available" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_exchanged"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="6dp"
+        android:gravity="center"
+        android:includeFontPadding="false"
+        android:textColor="@color/color_FF4E5969"
+        android:textSize="11sp"
+        app:layout_constraintTop_toBottomOf="@id/tv_content"
+        tools:text="@string/room_gift_exchange_rate" />
+
+    <!--确定-->
+    <com.adealink.weparty.commonui.widget.CommonButton
+        android:id="@+id/btn_confirm"
+        android:layout_width="0dp"
+        android:layout_height="36dp"
+        android:layout_marginHorizontal="50dp"
+        android:layout_marginTop="16dp"
+        app:button_radius="22dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_exchanged"
+        app:layout_constraintWidth_default="spread"
+        app:layout_goneMarginTop="20dp"
+        app:text="@string/wallet_confirm_convert"
+        app:textColor="@color/white"
+        app:textSize="14sp" />
+
+    <!--取消-->
+    <com.adealink.weparty.commonui.widget.CommonButton
+        android:id="@+id/btn_cancel"
+        android:layout_width="0dp"
+        android:layout_height="36dp"
+        android:layout_marginHorizontal="50dp"
+        android:layout_marginTop="16dp"
+        app:button_radius="22dp"
+        app:common_button_type="cancel"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/btn_confirm"
+        app:layout_goneMarginTop="20dp"
+        app:text="@string/commonui_cancel"
+        app:textColor="@color/color_FF4E5969"
+        app:textSize="14sp" />
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cl_never_remind"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="20dp"
+        android:layout_marginTop="16dp"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/btn_cancel">
+
+        <androidx.appcompat.widget.AppCompatImageView
+            android:id="@+id/iv_never_remind"
+            android:layout_width="14dp"
+            android:layout_height="14dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintHorizontal_chainStyle="packed"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/common_check_unselected_ic" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_never_remind"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="4dp"
+            android:gravity="start"
+            android:includeFontPadding="false"
+            android:text="@string/room_gift_exchange_never_remind_today"
+            android:textColor="@color/color_FF4E5969"
+            android:textSize="12sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@id/iv_never_remind"
+            app:layout_constraintTop_toTopOf="parent" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 0 - 2
module/room/src/main/res/layout/dialog_room_apply_mic_host_list.xml

@@ -18,10 +18,8 @@
         app:tabIndicatorHeight="0dp"
         app:tabMaxWidth="0dp"
         app:tabMode="fixed"
-        app:tabPaddingBottom="4dp"
         app:tabPaddingEnd="4dp"
         app:tabPaddingStart="4dp"
-        app:tabPaddingTop="4dp"
         app:tabRippleColor="@null" />
 
     <androidx.viewpager2.widget.ViewPager2

+ 128 - 99
module/room/src/main/res/layout/dialog_room_gift_panel.xml

@@ -3,135 +3,164 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:background="@drawable/shape_room_gift_panel_bg"
-    android:paddingBottom="20dp">
+    android:layout_height="match_parent"
+    android:background="@drawable/room_bottom_dialog_bg"
+    android:paddingBottom="20dp"
+    tools:layout_height="400dp">
 
-    <androidx.appcompat.widget.AppCompatTextView
-        android:id="@+id/tv_send_to"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="16dp"
-        android:layout_marginTop="20dp"
-        android:text="Send to :"
-        android:textColor="@color/color_FF86909C"
-        android:textSize="14sp"
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cl_users"
+        android:layout_width="match_parent"
+        android:layout_height="64dp"
+        app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
+        app:layout_constraintTop_toTopOf="parent">
 
-    <androidx.recyclerview.widget.RecyclerView
-        android:id="@+id/rv_users"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="8dp"
-        android:layout_marginEnd="16dp"
-        app:layout_constraintBottom_toBottomOf="@+id/tv_send_to"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toEndOf="@+id/tv_send_to"
-        app:layout_constraintTop_toTopOf="@+id/tv_send_to" />
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_send_to"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="10dp"
+            android:includeFontPadding="false"
+            android:text="@string/room_gift_send_to"
+            android:textColor="@color/color_FF86909C"
+            android:textSize="14sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/rv_users"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="6dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@+id/tv_send_to"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:itemCount="1"
+            tools:listitem="@layout/item_room_gift_user" />
+
+        <View
+            android:layout_width="0dp"
+            android:layout_height="1dp"
+            android:layout_marginHorizontal="10dp"
+            android:background="@color/color_33FFFFFF"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
 
     <androidx.recyclerview.widget.RecyclerView
         android:id="@+id/rv_gifts"
+        style="@style/CommonVerticalFade"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="16dp"
-        android:layout_marginStart="12dp"
-        android:layout_marginEnd="12dp"
-        android:minHeight="200dp"
-        app:layout_constraintTop_toBottomOf="@+id/rv_users" />
+        android:layout_height="0dp"
+        android:layout_marginHorizontal="7.5dp"
+        android:layout_marginTop="10dp"
+        app:layout_constraintBottom_toTopOf="@id/cl_bottom"
+        app:layout_constraintTop_toBottomOf="@+id/cl_users"
+        tools:listitem="@layout/item_room_gift" />
 
     <androidx.constraintlayout.widget.ConstraintLayout
         android:id="@+id/cl_bottom"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginTop="16dp"
         android:layout_marginStart="16dp"
+        android:layout_marginTop="16dp"
         android:layout_marginEnd="16dp"
-        app:layout_constraintTop_toBottomOf="@+id/rv_gifts">
+        app:layout_constraintBottom_toBottomOf="parent">
 
-        <androidx.appcompat.widget.AppCompatImageView
-            android:id="@+id/iv_diamond"
-            android:layout_width="16dp"
-            android:layout_height="16dp"
-            android:src="@drawable/common_wallet_diamond_ic"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent" />
-
-        <androidx.appcompat.widget.AppCompatTextView
-            android:id="@+id/tv_balance"
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/btn_balance"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_marginStart="4dp"
-            android:text="100 >"
-            android:textColor="@color/white"
-            android:textSize="16sp"
-            android:textStyle="bold"
             app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintStart_toEndOf="@+id/iv_diamond"
-            app:layout_constraintTop_toTopOf="parent" />
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent">
 
-        <androidx.appcompat.widget.AppCompatTextView
-            android:id="@+id/btn_send"
-            android:layout_width="wrap_content"
-            android:layout_height="32dp"
-            android:background="@drawable/shape_room_gift_btn_send_bg"
-            android:gravity="center"
-            android:paddingHorizontal="20dp"
-            android:text="Send"
-            android:textColor="@color/white"
-            android:textSize="14sp"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="parent" />
+            <androidx.appcompat.widget.AppCompatImageView
+                android:id="@+id/iv_diamond"
+                android:layout_width="16dp"
+                android:layout_height="16dp"
+                android:src="@drawable/common_wallet_diamond_ic"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:id="@+id/tv_balance"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="3dp"
+                android:includeFontPadding="false"
+                android:text="0"
+                android:textColor="@color/white"
+                android:textSize="16sp"
+                app:fontFamily="@font/poppins_semibold"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/iv_diamond"
+                app:layout_constraintTop_toTopOf="parent"
+                tools:text="100" />
+
+            <androidx.appcompat.widget.AppCompatImageView
+                android:id="@+id/iv_balance_go"
+                android:layout_width="16dp"
+                android:layout_height="16dp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toEndOf="@id/tv_balance"
+                app:layout_constraintTop_toTopOf="parent"
+                app:srcCompat="@drawable/common_go_white_bold_ic" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
 
-        <LinearLayout
-            android:id="@+id/ll_quantity"
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/cl_right"
             android:layout_width="wrap_content"
-            android:layout_height="32dp"
-            android:layout_marginEnd="12dp"
-            android:background="@drawable/shape_room_gift_quantity_bg"
-            android:gravity="center_vertical"
-            android:orientation="horizontal"
-            android:visibility="gone"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="10dp"
+            android:background="@drawable/room_bottom_send_gift_button"
             app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toStartOf="@+id/btn_send"
+            app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="parent">
 
-            <androidx.appcompat.widget.AppCompatTextView
-                android:id="@+id/btn_minus"
-                android:layout_width="24dp"
-                android:layout_height="24dp"
-                android:layout_marginStart="4dp"
-                android:gravity="center"
-                android:text="-"
-                android:textColor="@color/white"
-                android:textSize="18sp"
-                android:textStyle="bold" />
-
-            <androidx.appcompat.widget.AppCompatTextView
-                android:id="@+id/tv_quantity"
+            <com.adealink.weparty.commonui.widget.NumberIncreaseView
+                android:id="@+id/v_number"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_marginStart="8dp"
-                android:layout_marginEnd="8dp"
-                android:minWidth="30dp"
-                android:gravity="center"
-                android:text="999"
-                android:textColor="@color/white"
-                android:textSize="14sp" />
+                android:layout_marginStart="3.5dp"
+                android:layout_marginEnd="10dp"
+                android:visibility="gone"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toStartOf="@id/btn_send"
+                app:layout_constraintHorizontal_chainStyle="packed"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:number_add_src="@drawable/room_number_add_ic"
+                app:number_minus_disable_src="@drawable/room_number_minus_disable_ic"
+                app:number_minus_src="@drawable/room_number_minus_ic"
+                app:number_text_color="@color/white"
+                tools:visibility="visible" />
 
             <androidx.appcompat.widget.AppCompatTextView
-                android:id="@+id/btn_plus"
-                android:layout_width="24dp"
-                android:layout_height="24dp"
-                android:layout_marginEnd="4dp"
+                android:id="@+id/btn_send"
+                android:layout_width="wrap_content"
+                android:layout_height="32dp"
+                android:background="@drawable/common_button_bg"
                 android:gravity="center"
-                android:text="+"
+                android:includeFontPadding="false"
+                android:paddingHorizontal="12dp"
+                android:text="Send"
                 android:textColor="@color/white"
-                android:textSize="18sp"
-                android:textStyle="bold" />
-        </LinearLayout>
+                android:textSize="14sp"
+                app:fontFamily="@font/poppins_semibold"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@id/v_number"
+                app:layout_constraintTop_toTopOf="parent" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
     </androidx.constraintlayout.widget.ConstraintLayout>
 
 </androidx.constraintlayout.widget.ConstraintLayout>

+ 9 - 5
module/room/src/main/res/layout/dialog_room_invite_mic.xml

@@ -15,18 +15,20 @@
 
         <androidx.appcompat.widget.AppCompatTextView
             android:id="@+id/tv_title"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/common_title_height"
             android:gravity="start|center_vertical"
             android:includeFontPadding="false"
             android:paddingHorizontal="16dp"
+            android:singleLine="true"
             android:textColor="@color/white"
             android:textSize="14sp"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintEnd_toStartOf="@id/btn_filter"
+            app:layout_constraintHorizontal_bias="0"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toTopOf="parent"
-            tools:text="10人在房间内" />
+            tools:text="10人申请上麦10人申请上麦10人申请上麦" />
 
         <androidx.constraintlayout.widget.ConstraintLayout
             android:id="@+id/btn_filter"
@@ -45,6 +47,8 @@
                 android:layout_height="match_parent"
                 android:gravity="start|center_vertical"
                 android:includeFontPadding="false"
+                android:maxWidth="120dp"
+                android:singleLine="true"
                 android:text="@string/room_playmate_category_filter"
                 android:textColor="@color/white"
                 android:textSize="14sp"

+ 1 - 1
module/room/src/main/res/layout/dialog_room_member.xml

@@ -64,7 +64,7 @@
             android:gravity="center"
             android:includeFontPadding="false"
             android:singleLine="true"
-            android:textColor="@color/white"
+            android:textColor="@color/color_80FFFFFF"
             android:textSize="12sp"
             app:layout_constrainedWidth="true"
             app:layout_constraintEnd_toEndOf="parent"

+ 1 - 1
module/room/src/main/res/layout/fragment_dispatch_room_bottom_operate.xml

@@ -48,7 +48,7 @@
         android:id="@+id/operate_box"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginEnd="10dp"
+        android:layout_marginEnd="6dp"
         android:clipChildren="false"
         app:icon_size="@dimen/room_bottom_operate_item_icon_size"
         app:item_height="@dimen/room_bottom_operate_item_width"

+ 8 - 3
module/room/src/main/res/layout/fragment_room_apply_mic_list.xml

@@ -9,17 +9,20 @@
 
     <androidx.appcompat.widget.AppCompatTextView
         android:id="@+id/tv_title"
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="@dimen/common_title_height"
         android:gravity="start|center_vertical"
         android:includeFontPadding="false"
         android:paddingHorizontal="16dp"
+        android:singleLine="true"
         android:textColor="@color/white"
         android:textSize="14sp"
-        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintEnd_toStartOf="@id/btn_filter"
+        app:layout_constraintHorizontal_bias="0"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent"
-        tools:text="10人申请上麦" />
+        tools:text="10人申请上麦10人申请上麦10人申请上麦" />
 
     <androidx.constraintlayout.widget.ConstraintLayout
         android:id="@+id/btn_filter"
@@ -38,6 +41,8 @@
             android:layout_height="match_parent"
             android:gravity="start|center_vertical"
             android:includeFontPadding="false"
+            android:maxWidth="120dp"
+            android:singleLine="true"
             android:text="@string/room_playmate_category_filter"
             android:textColor="@color/white"
             android:textSize="14sp"

+ 1 - 0
module/room/src/main/res/layout/item_message_enter_room.xml

@@ -32,6 +32,7 @@
             android:textColor="@color/white"
             android:textColorHighlight="@color/transparent"
             android:textSize="12sp"
+            app:fontFamily="@font/poppins_semibold"
             app:layout_constrainedWidth="true"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintHorizontal_bias="0"

+ 69 - 0
module/room/src/main/res/layout/item_message_send_gift.xml

@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingStart="10dp"
+    android:paddingEnd="85dp"
+    android:paddingBottom="16dp"
+    tools:background="@color/black">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cl_bg"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="10dp"
+        android:layout_marginTop="4dp"
+        android:background="@drawable/room_chat_message_bg"
+        android:paddingHorizontal="10dp"
+        android:paddingVertical="8dp"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_msg"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="start|center_vertical"
+            android:textColor="@color/white"
+            android:textColorHighlight="@color/transparent"
+            android:textSize="12sp"
+            app:fontFamily="@font/poppins_semibold"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintEnd_toStartOf="@id/iv_gift"
+            app:layout_constraintHorizontal_bias="0"
+            app:layout_constraintHorizontal_chainStyle="packed"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="AAA Send BBB" />
+
+        <com.adealink.frame.image.view.NetworkImageView
+            android:id="@+id/iv_gift"
+            android:layout_width="22dp"
+            android:layout_height="22dp"
+            android:layout_marginHorizontal="2dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@id/tv_count"
+            app:layout_constraintStart_toEndOf="@id/tv_msg"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_count"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="start|center_vertical"
+            android:textColor="@color/color_FF35F3F0"
+            android:textSize="12sp"
+            app:fontFamily="@font/poppins_semibold"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@id/iv_gift"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="x33" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 1 - 0
module/room/src/main/res/layout/item_message_text.xml

@@ -55,6 +55,7 @@
             android:textColor="@color/white"
             android:textColorHighlight="@color/transparent"
             android:textSize="12sp"
+            app:fontFamily="@font/poppins_semibold"
             app:layout_constrainedWidth="true"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintHorizontal_bias="0"

+ 37 - 34
module/room/src/main/res/layout/item_room_gift.xml

@@ -3,59 +3,62 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
-    android:layout_height="120dp"
-    android:layout_margin="4dp"
-    android:background="@drawable/shape_room_gift_item_bg">
+    android:layout_height="96dp"
+    tools:background="@color/color_FF242138"
+    tools:layout_width="90dp">
 
     <com.adealink.frame.image.view.NetworkImageView
         android:id="@+id/iv_icon"
         style="@style/CommonNetworkImage"
-        android:layout_width="64dp"
-        android:layout_height="64dp"
-        android:layout_marginTop="8dp"
+        android:layout_width="58dp"
+        android:layout_height="58dp"
         app:actualImageScaleType="fitCenter"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        tools:src="@color/color_AAAAAA" />
+        app:layout_constraintTop_toTopOf="parent" />
 
     <androidx.appcompat.widget.AppCompatTextView
         android:id="@+id/tv_name"
-        android:layout_width="match_parent"
+        android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:layout_marginTop="4dp"
+        android:layout_marginHorizontal="8dp"
+        android:layout_marginTop="2dp"
+        android:ellipsize="end"
         android:gravity="center"
         android:singleLine="true"
-        android:ellipsize="end"
         android:textColor="@color/white"
-        android:textSize="12sp"
+        android:textSize="11sp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@+id/iv_icon"
         tools:text="宇宙飞船" />
 
-    <LinearLayout
-        android:id="@+id/ll_price"
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@id/iv_price"
+        android:layout_width="10dp"
+        android:layout_height="10dp"
+        android:layout_marginStart="8dp"
+        android:src="@drawable/common_wallet_diamond_ic"
+        app:layout_constraintBottom_toBottomOf="@id/tv_price"
+        app:layout_constraintEnd_toStartOf="@id/tv_price"
+        app:layout_constraintHorizontal_chainStyle="packed"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="@id/tv_price" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_price"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginTop="2dp"
-        android:gravity="center_vertical"
-        android:orientation="horizontal"
+        android:layout_marginStart="2dp"
+        android:layout_marginEnd="8dp"
+        android:ellipsize="end"
+        android:singleLine="true"
+        android:textColor="@color/color_80FFFFFF"
+        android:textSize="10sp"
+        app:layout_constrainedWidth="true"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/tv_name">
-
-        <androidx.appcompat.widget.AppCompatImageView
-            android:layout_width="10dp"
-            android:layout_height="10dp"
-            android:src="@drawable/common_wallet_diamond_ic" />
-
-        <androidx.appcompat.widget.AppCompatTextView
-            android:id="@+id/tv_price"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="2dp"
-            android:textColor="@color/color_FF86909C"
-            android:textSize="10sp"
-            tools:text="32000" />
-    </LinearLayout>
+        app:layout_constraintStart_toEndOf="@id/iv_price"
+        app:layout_constraintTop_toBottomOf="@id/tv_name"
+        tools:text="320000" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>

+ 42 - 0
module/room/src/main/res/layout/item_room_gift_target_user.xml

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="wrap_content"
+    android:layout_height="40dp">
+
+    <View
+        android:id="@+id/v_selected"
+        android:layout_width="34dp"
+        android:layout_height="34dp"
+        android:background="@drawable/room_gift_user_selected_bg"
+        app:layout_constraintBottom_toBottomOf="@id/iv_avatar"
+        app:layout_constraintEnd_toEndOf="@id/iv_avatar"
+        app:layout_constraintStart_toStartOf="@id/iv_avatar"
+        app:layout_constraintTop_toTopOf="@id/iv_avatar" />
+
+    <com.adealink.weparty.commonui.imageview.AvatarView
+        android:id="@+id/iv_avatar"
+        android:layout_width="30dp"
+        android:layout_height="30dp"
+        android:layout_marginStart="4dp"
+        android:background="@drawable/room_gift_user_selected_bg"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_name"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="6dp"
+        android:gravity="start|center"
+        android:textColor="@color/color_FF86909C"
+        android:textSize="14sp"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toEndOf="@id/iv_avatar"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="Host" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 23 - 15
module/room/src/main/res/layout/item_room_gift_user.xml

@@ -2,37 +2,45 @@
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_marginEnd="12dp">
+    android:layout_width="40dp"
+    android:layout_height="40dp"
+    tools:background="@color/color_FF242138">
 
-    <com.adealink.frame.image.view.NetworkImageView
+    <View
+        android:id="@+id/v_selected"
+        android:layout_width="34dp"
+        android:layout_height="34dp"
+        android:background="@drawable/room_gift_user_selected_bg"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="@id/iv_avatar"
+        app:layout_constraintEnd_toEndOf="@id/iv_avatar"
+        app:layout_constraintStart_toStartOf="@id/iv_avatar"
+        app:layout_constraintTop_toTopOf="@id/iv_avatar"
+        tools:visibility="visible" />
+
+    <com.adealink.weparty.commonui.imageview.AvatarView
         android:id="@+id/iv_avatar"
-        style="@style/CommonNetworkImage"
-        android:layout_width="36dp"
-        android:layout_height="36dp"
-        app:actualImageScaleType="focusCrop"
+        android:layout_width="30dp"
+        android:layout_height="30dp"
+        android:layout_marginTop="3dp"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        app:roundAsCircle="true"
-        tools:src="@color/color_AAAAAA" />
+        app:layout_constraintTop_toTopOf="parent" />
 
     <androidx.appcompat.widget.AppCompatTextView
         android:id="@+id/tv_tag"
         android:layout_width="wrap_content"
         android:layout_height="14dp"
         android:layout_marginTop="-6dp"
-        android:background="@drawable/shape_room_gift_user_tag_bg"
+        android:background="@drawable/room_gift_user_tag_bg"
         android:gravity="center"
         android:minWidth="24dp"
         android:paddingHorizontal="4dp"
         android:textColor="@color/color_FF2C2945"
-        android:textSize="9sp"
-        android:textStyle="bold"
+        android:textSize="8sp"
+        app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="@+id/iv_avatar"
         app:layout_constraintStart_toStartOf="@+id/iv_avatar"
-        app:layout_constraintTop_toBottomOf="@+id/iv_avatar"
         tools:text="Host" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>

+ 0 - 1
module/room/src/main/res/layout/layout_room_member_playmate_category_item.xml

@@ -42,7 +42,6 @@
         android:id="@+id/v_price"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginTop="5dp"
         app:layout_constraintBottom_toBottomOf="@id/iv_icon"
         app:layout_constraintStart_toStartOf="@id/tv_name"
         app:layout_constraintTop_toBottomOf="@id/tv_name"

+ 1 - 1
module/room/src/main/res/layout/layout_room_playmate_category_filter_item.xml

@@ -14,7 +14,7 @@
         android:gravity="start|center_vertical"
         android:includeFontPadding="false"
         android:textColor="@color/white"
-        android:textSize="16sp"
+        android:textSize="14sp"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent"

+ 10 - 0
module/room/src/main/res/values-in/strings.xml

@@ -67,4 +67,14 @@
     <string name="room_close_tips">Menutup ruang akan menghapus semua pengguna di ruang~</string>
     <string name="room_unsupported_room_type">Jenis ruang tidak didukung, tidak dapat masuk ruang, tingkatkan APP</string>
     <string name="room_list_dispatch_center_title">Ruang Dispatch</string>
+    <string name="room_gift_send_to">Kirim ke :</string>
+    <string name="room_gift_diamond_less">Berlian Tidak Cukup</string>
+    <string name="room_gift_coin_available">Koin Tersedia: [coin]%s</string>
+    <string name="room_gift_exchange_rate">Nilai Tukar: [coin]1=[diamond]1000</string>
+    <string name="room_gift_exchange_never_remind_today">Jangan ingatkan saya lagi hari ini</string>
+    <string name="room_send_gift_message">%1$s Kirim %2$s</string>
+    <string name="room_send_gift_to_uids_empty">Silakan pilih kepada siapa akan dikirim</string>
+    <string name="room_send_gift_gift_no_select">Silakan pilih hadiah yang akan dikirim</string>
+    <string name="room_send_gift_count_empty">Setidaknya ada satu hadiah untuk dikirim</string>
+    <string name="room_send_gift_balance_not_enough">Berlian tidak cukup, ingin menambah?</string>
 </resources>

+ 10 - 0
module/room/src/main/res/values-zh/strings.xml

@@ -67,4 +67,14 @@
     <string name="room_close_tips">关闭房间将清空房间人员哦~</string>
     <string name="room_unsupported_room_type">不支持的房间类型,无法进入房间,请升级APP</string>
     <string name="room_list_dispatch_center_title">派单厅</string>
+    <string name="room_gift_send_to">送给:</string>
+    <string name="room_gift_diamond_less">钻石不足</string>
+    <string name="room_gift_coin_available">可用金币:[coin]%s</string>
+    <string name="room_gift_exchange_rate">兑换比例: [coin]1=[diamond]1000</string>
+    <string name="room_gift_exchange_never_remind_today">今日不再提醒</string>
+    <string name="room_send_gift_message">%1$s送给%2$s</string>
+    <string name="room_send_gift_to_uids_empty">请选择要送给谁</string>
+    <string name="room_send_gift_gift_no_select">请选择要赠送的礼物</string>
+    <string name="room_send_gift_count_empty">至少赠送一个礼物</string>
+    <string name="room_send_gift_balance_not_enough">钻石不足,是否去充值?</string>
 </resources>

+ 1 - 1
module/room/src/main/res/values/dimens.xml

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <dimen name="room_bottom_operate_item_icon_size">30dp</dimen>
-    <dimen name="room_bottom_operate_item_width">32dp</dimen>
+    <dimen name="room_bottom_operate_item_width">34dp</dimen>
 
 </resources>

+ 10 - 0
module/room/src/main/res/values/strings.xml

@@ -68,4 +68,14 @@
     <string name="room_close_tips">Closing the room will clear all users in the room~</string>
     <string name="room_unsupported_room_type">Unsupported room type, cannot enter the room, please upgrade the APP</string>
     <string name="room_list_dispatch_center_title">Dispatch Hall</string>
+    <string name="room_gift_send_to">Send to :</string>
+    <string name="room_gift_diamond_less">Insufficient Diamonds</string>
+    <string name="room_gift_coin_available">Available Coins: [coin]%s</string>
+    <string name="room_gift_exchange_rate">Exchange Rate: [coin]1=[diamond]1000</string>
+    <string name="room_gift_exchange_never_remind_today">Don\'t remind me again today</string>
+    <string name="room_send_gift_message">%1$s Send %2$s</string>
+    <string name="room_send_gift_to_uids_empty">Please select who to send to</string>
+    <string name="room_send_gift_gift_no_select">Please select the gift to send</string>
+    <string name="room_send_gift_count_empty">At least one gift to send</string>
+    <string name="room_send_gift_balance_not_enough"> Insufficient diamonds, wish to top up?</string>
 </resources>

+ 0 - 2
module/share/src/main/res/layout/dialog_qr_code_share.xml

@@ -60,10 +60,8 @@
         app:tabIndicatorHeight="0dp"
         app:tabMaxWidth="0dp"
         app:tabMode="fixed"
-        app:tabPaddingBottom="4dp"
         app:tabPaddingEnd="4dp"
         app:tabPaddingStart="4dp"
-        app:tabPaddingTop="4dp"
         app:tabRippleColor="@null" />
 
     <androidx.viewpager2.widget.ViewPager2

+ 0 - 5
module/wallet/src/main/java/com/adealink/weparty/wallet/comp/MyIncomeComp.kt

@@ -29,11 +29,6 @@ class MyIncomeComp(
     }
 
     private fun initView() {
-        if (ProfileModule.getMyUserInfo()?.isPlaymate() == true) {
-            binding.root.show()
-        } else {
-            binding.root.gone()
-        }
         binding.btnTopUpCoin.onClick {
             goBeanDetail()
         }

+ 25 - 3
module/wallet/src/main/java/com/adealink/weparty/wallet/pay/PayManager.kt

@@ -3,6 +3,7 @@ package com.adealink.weparty.wallet.pay
 import android.app.Activity
 import android.content.Intent
 import com.adealink.frame.aab.util.getCompatString
+import com.adealink.frame.base.AppBase
 import com.adealink.frame.base.Rlt
 import com.adealink.frame.coroutine.dispatcher.Dispatcher
 import com.adealink.frame.frame.BaseFrame
@@ -33,7 +34,6 @@ import com.adealink.weparty.wallet.recharge.data.PrePurchaseReq
 import com.adealink.weparty.wallet.recharge.data.RechargeConfigReq
 import com.adealink.weparty.wallet.recharge.data.RechargeProduct
 import com.adealink.weparty.wallet.stat.FirstPaymentEvent
-import com.adjust.sdk.AdjustEvent
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.update
@@ -44,7 +44,6 @@ val payManager: IPayManager by lazy { PayManager() }
 /**
  *
  */
-// TODO: 补充订单埋点上报
 class PayManager : IPayManager, IPayListener, BaseFrame<IListener>() {
 
     private val googleBilling by lazy {
@@ -101,7 +100,13 @@ class PayManager : IPayManager, IPayListener, BaseFrame<IListener>() {
                 }
 
                 //获取三方套餐列表
-                var resultList = listOf<RechargeProduct>()
+                var resultList: List<RechargeProduct>
+                //测试包直接返回充值套餐
+                if (!AppBase.isRelease) {
+                    resultList =
+                        getDebugDisplayProductList(productInfoList, channel, currency)
+                    return Rlt.Success(resultList)
+                }
                 val skuListRlt = getPay(channel).querySkuDetails(productInfoList.map { it.skuId() })
                 val skuInfoList = mutableListOf<SkuInfo>()
                 when (skuListRlt) {
@@ -127,6 +132,23 @@ class PayManager : IPayManager, IPayListener, BaseFrame<IListener>() {
         }
     }
 
+    private fun getDebugDisplayProductList(
+        originProductInfo: List<RechargeProduct>,
+        channel: PayChannel,
+        currency: Currency
+    ): List<RechargeProduct> {
+        if (AppBase.isRelease) {
+            throw IllegalStateException("can not invoke getDebugDisplayProductList in release package!!!")
+        }
+        val displayProductList = mutableListOf<RechargeProduct>()
+        originProductInfo.forEach { productInfo ->
+            productInfo.productChannel = channel.type
+            productInfo.productCurrency = currency.type
+            displayProductList.add(productInfo)
+        }
+        return displayProductList
+    }
+
     private fun getDisplayProductList(
         originProductInfo: List<RechargeProduct>,
         skuInfoList: List<SkuInfo>?,

+ 2 - 2
module/wallet/src/main/java/com/adealink/weparty/wallet/recharge/dialog/RechargeDialog.kt

@@ -59,7 +59,7 @@ class RechargeDialog : BottomDialogFragment(R.layout.dialog_recharge) {
             dismiss()
         }
 
-        adapter.register(RechargeItemViewBinder(Currency.COIN) { it ->
+        adapter.register(RechargeItemViewBinder(currency) { it ->
             selectRecharge(it)
         })
         adapter.register(RechargeErrorItemViewBinder())
@@ -131,7 +131,7 @@ class RechargeDialog : BottomDialogFragment(R.layout.dialog_recharge) {
                 }
 
                 is Rlt.Success -> {
-                    rechargeViewModel.pullRechargeList(PayChannel.Google, Currency.COIN)
+                    rechargeViewModel.pullRechargeList(PayChannel.Google, currency)
                 }
             }
         }

+ 7 - 2
module/wallet/src/main/java/com/adealink/weparty/wallet/recharge/fragment/BeanExchangedFragment.kt

@@ -7,9 +7,11 @@ import com.adealink.frame.mvvm.view.viewBinding
 import com.adealink.frame.router.Router
 import com.adealink.frame.util.onClick
 import com.adealink.weparty.commonui.BaseFragment
+import com.adealink.weparty.commonui.ext.gone
 import com.adealink.weparty.commonui.ext.show
 import com.adealink.weparty.commonui.widget.CommonDialog
 import com.adealink.weparty.config.viewmodel.ApprovingVersionViewModel
+import com.adealink.weparty.module.profile.ProfileModule
 import com.adealink.weparty.module.wallet.data.Currency
 import com.adealink.weparty.module.webview.Web
 import com.adealink.weparty.url.UrlConfig
@@ -29,7 +31,6 @@ class BeanExchangedFragment : BaseFragment(R.layout.fragment_recharge_bean) {
 
     override fun initViews() {
         super.initViews()
-        approvingViewModel
         binding.vBeanCard.btnBeanMore.onClick {
             showMore()
         }
@@ -56,7 +57,11 @@ class BeanExchangedFragment : BaseFragment(R.layout.fragment_recharge_bean) {
             binding.vBeanCard.tvUnsettledBalance.text = formatNumberStr(it, false)
         }
         approvingViewModel.approvingLD.observe(viewLifecycleOwner) { approving ->
-            binding.vBeanCard.btnBeanMore.show(!approving)
+            if (ProfileModule.getMyUserInfo()?.isPlaymate() == true) {
+                binding.vBeanCard.btnBeanMore.show(!approving)
+            } else {
+                binding.vBeanCard.btnBeanMore.gone()
+            }
         }
     }
 

+ 0 - 1
module/wallet/src/main/res/values-in/strings.xml

@@ -20,7 +20,6 @@
     <string name="wallet_recharge_not_selected">Silakan pilih paket pengisian ulang.</string>
     <string name="wallet_convert_currency_title">Konversi ke %s</string>
     <string name="wallet_all_convert_button">Semua</string>
-    <string name="wallet_confirm_convert">Konfirmasi Penukaran</string>
     <string name="wallet_convert_from_coin">Dari Koin</string>
     <string name="wallet_convert_to_coin">Ke Koin</string>
     <string name="wallet_convert_from_diamond">Dari Berlian</string>

+ 0 - 1
module/wallet/src/main/res/values-zh/strings.xml

@@ -20,7 +20,6 @@
     <string name="wallet_recharge_not_selected">请选择充值套餐</string>
     <string name="wallet_convert_currency_title">兑换为 %s</string>
     <string name="wallet_all_convert_button">全部兑换</string>
-    <string name="wallet_confirm_convert">确认兑换</string>
     <string name="wallet_convert_from_coin">从金币兑换</string>
     <string name="wallet_convert_to_coin">兑换为金币</string>
     <string name="wallet_convert_from_diamond">从钻石兑换</string>

+ 0 - 1
module/wallet/src/main/res/values/strings.xml

@@ -20,7 +20,6 @@
     <string name="wallet_recharge_not_selected">Please select recharge package.</string>
     <string name="wallet_convert_currency_title">Convert to %s</string>
     <string name="wallet_all_convert_button">All</string>
-    <string name="wallet_confirm_convert">Confirm Exchange</string>
     <string name="wallet_convert_from_coin">From Coins</string>
     <string name="wallet_convert_to_coin">To Coins</string>
     <string name="wallet_convert_from_diamond">From Diamonds</string>

+ 0 - 255
pull_and_fill_translations.py

@@ -1,255 +0,0 @@
-import os
-import shutil
-import subprocess
-import xml.dom.minidom as minidom
-import xml.etree.ElementTree as ET
-from collections import OrderedDict
-import re
-import requests
-
-SUPPORTED_LANGUAGES = [
-    "zh", "en", "ar"
-]
-TOLGEE_PATH = "./tolgee_json"
-TOLGEE_API_URL = "http://47.236.31.243:8090/api"
-TOLGEE_API_KEY = "tgpak_hbpxgyltnryguy3lonyxiyrtgruta4rroryhiolooe3q"
-PROJECT_ID = "8"
-HEADERS = {
-    "X-Api-Key": TOLGEE_API_KEY,
-    "Content-Type": "application/json"
-}
-
-
-def fetch_all_cdata_keys():
-    dataList = [] #key是name,value是id
-    page = 0
-    page_size = 1000
-    while True:
-        url = f"{TOLGEE_API_URL}/v2/projects/{PROJECT_ID}/keys?page={page}&size={page_size}"
-        r = requests.get(url, headers=HEADERS)
-        if r.status_code != 200:
-            print(f"❌ 拉取失败: {r.status_code} -> {r.text}")
-            break
-
-        data = r.json()
-        # print(data)
-
-        dataList.extend(data['_embedded']['keys'])
-
-
-        if data['page']['totalPages'] == page +1 :  # 最后一页
-            break
-        page += 1
-    print(f"✅ 已拉取 {len(dataList)} 个 Tolgee keys")
-    # print(dataList)
-    # 只保留包含 CDATA 的 keys,'custom': {'_androidWrapWithCdata': True}
-    dataList = [item for item in dataList if
-                item['custom'] and '_androidWrapWithCdata' in item['custom'] and item['custom'][
-                    '_androidWrapWithCdata']]
-    # 提取name
-    dataList = [item['name'] for item in dataList if 'name' in item]
-    print(f"🔍 找到 {len(dataList)} 个包含 CDATA 的 keys")
-    print(dataList)
-    return dataList
-
-def run_tolgee_pull():
-    os.makedirs(TOLGEE_PATH, exist_ok=True)
-    cmd = [
-              "tolgee", "pull",
-              "--format", "ANDROID_XML",
-              "--path", TOLGEE_PATH,
-              "--languages"
-          ] + SUPPORTED_LANGUAGES + [
-              "--empty-dir",
-          ]
-    print(f"📥 拉取 Tolgee 翻译中...")
-    subprocess.run(cmd, check=True)
-    print(f"✅ Tolgee 拉取完成:{TOLGEE_PATH}")
-
-
-def find_en_strings_files():
-    en_files = []
-    for root, dirs, files in os.walk("."):
-        normalized_root = root.replace("\\", "/")
-        if "lite" in normalized_root or "lite" in files:
-            continue
-        if "/build/" in normalized_root or normalized_root.endswith("/build"):
-            continue
-        if "strings.xml" in files and "/res/values" in root.replace("\\", "/"):
-            if "values-" not in root:  # 只取英文
-                en_files.append(os.path.join(root, "strings.xml"))
-    return en_files
-
-
-def parse_strings_file(file_path):
-    try:
-        tree = ET.parse(file_path)
-        root = tree.getroot()
-        result = OrderedDict()
-        for e in root.findall("string"):
-            if 'name' in e.attrib:
-                result[e.attrib['name']] = e.text or ""
-        return result
-    except Exception as e:
-        print(f"❌ 解析失败: {file_path} -> {e}")
-        return OrderedDict()
-
-
-# 解析 Tolgee 拉取的翻译,并合并到现有的 strings.xml 中
-def merge_translations(lang, base_dict: dict, pulled_dict: dict, existing_dict: dict,
-                       translatable_false_keys):
-    merged = OrderedDict()
-
-    # 先添加原有 key,保持顺序
-    # 只有en才会有translatable_false_keys的数据
-    for key in existing_dict:
-        merged[key] = existing_dict[key]
-        # if lang == "en" and key in translatable_false_keys:
-        #     merged[key] = pulled_dict.get(key) or existing_dict[key] or base_dict.get(key) or ""
-        # else:
-        #     merged[key] = pulled_dict.get(key) or existing_dict[key] or ""
-
-
-    # 再补充新 key(存在于 base_dict,但不在原文件中)
-    for key in base_dict:
-        #如果 key 不在 pulled_dict 中,跳过
-        if key not in pulled_dict:
-            continue
-        if key in translatable_false_keys and lang != "en":
-            # 如果是非英文语言且在translatable_false_keys中,则不添加,并且移除掉
-            merged.pop(key, None)
-            continue
-        if pulled_dict.get(key) is not None:
-            merged[key] = pulled_dict.get(key)
-
-    return merged
-
-
-def find_translatable_false_keys():
-    result = []
-    for root, dirs, files in os.walk("."):
-        normalized_root = root.replace("\\", "/")
-        if normalized_root.endswith("/res/values") and "strings.xml" in files:
-            file_path = os.path.join(root, "strings.xml")
-            try:
-                tree = ET.parse(file_path)
-                root_elem = tree.getroot()
-                for elem in root_elem.findall("string"):
-                    if elem.attrib.get("translatable") == "false":
-                        result.append(elem.attrib["name"])
-            except Exception as e:
-                print(f"⚠️ 解析失败:{file_path} -> {e}")
-    print(f"🔍 找到 {len(result)} 个 translatable=false 的 key")
-    # print(result)
-    return result
-
-def write_strings_xml(file_path, data: dict,translatable_false_keys,cdata_keys):
-    if data is None or not data:
-        print(f"⚠️ 跳过写入:{file_path},没有数据")
-        return
-
-    os.makedirs(os.path.dirname(file_path), exist_ok=True)
-    cdata_map = {}
-
-    # 创建 <resources> 根节点
-    resources = ET.Element("resources")
-
-    # 遍历 key,构建 string 元素
-    for key, value in data.items():
-        attributes = {"name": key}
-        text = value
-
-        if key in translatable_false_keys:
-            attributes["translatable"] = "false"
-
-        if isinstance(value, dict):
-            text = value.get("value", "")
-            for attr in ["translatable", "formatted", "product"]:
-                if attr in value:
-                    attributes[attr] = value[attr]
-
-        string_elem = ET.SubElement(resources, "string", attrib=attributes)
-        if key in cdata_keys:
-            placeholder = f"__CDATA__{key}__"
-            string_elem.text = placeholder
-            cdata_map[key] = text
-        else:
-            string_elem.text = text
-
-    # 格式化 + 替换 CDATA
-    rough_string = ET.tostring(resources, encoding="utf-8")
-    pretty_xml = minidom.parseString(rough_string).toprettyxml(indent="   ")
-    pretty_xml = "\n".join([line for line in pretty_xml.split("\n") if line.strip()])
-
-    # 替换默认声明为标准的 XML 头部
-    if pretty_xml.startswith('<?xml version="1.0" ?>'):
-        pretty_xml = pretty_xml.replace(
-            '<?xml version="1.0" ?>',
-            '<?xml version="1.0" encoding="utf-8"?>',
-            1
-        )
-
-    for key in cdata_map:
-        placeholder = f"__CDATA__{key}__"
-        cdata_value = cdata_map[key]
-        pretty_xml = pretty_xml.replace(placeholder, f"<![CDATA[{cdata_value}]]>")
-
-    with open(file_path, "w", encoding="utf-8") as f:
-        f.write(pretty_xml + "\n")
-
-
-def get_module_base_path(en_file_path):
-    return os.path.dirname(os.path.dirname(en_file_path))  # 到 src/main/res
-
-
-
-def main():
-    run_tolgee_pull()
-    en_files = find_en_strings_files()
-    translatable_false_keys = find_translatable_false_keys()
-    cdata_keys = fetch_all_cdata_keys()
-
-    for en_path in en_files:
-        print(en_path)
-        en_strings = parse_strings_file(en_path)
-        if not en_strings:
-            continue
-
-        module_res_path = get_module_base_path(en_path)
-        print(f"处理模块:{module_res_path}")
-        for lang in SUPPORTED_LANGUAGES:
-            if lang == "en":
-                lang_dir = "values"
-            elif lang == "id":
-                lang_dir = "values-in"
-            else:
-                lang_dir = f"values-{lang}"
-            pulled_path = os.path.join(TOLGEE_PATH, f"values-{lang}", "strings.xml")
-            out_path = os.path.join(module_res_path, lang_dir, "strings.xml")
-            print(f"处理语言:{lang} → {out_path}, 源:{pulled_path}")
-
-            pulled_translations = parse_strings_file(pulled_path)
-
-            if not pulled_translations:
-                print(f"⚠️ 跳过:{pulled_path},没有可用翻译")
-                continue
-            # print(f"已拉取翻译:{pulled_translations}")
-
-            existing_translations = parse_strings_file(out_path)
-            # print(f"现有翻译:{existing_translations}")
-            merged = merge_translations(
-                lang=lang,
-                base_dict=en_strings,
-                pulled_dict=pulled_translations,
-                existing_dict=existing_translations,
-                translatable_false_keys=translatable_false_keys
-            )
-            write_strings_xml(out_path, merged,translatable_false_keys,cdata_keys)
-    # ✅ 删除中间产物
-    if os.path.exists(TOLGEE_PATH):
-        shutil.rmtree(TOLGEE_PATH)
-        print(f"🧹 已删除中间目录:{TOLGEE_PATH}")
-
-
-if __name__ == "__main__":
-    main()

+ 0 - 207
push_translations.py

@@ -1,207 +0,0 @@
-import os
-import json
-import re
-import subprocess
-import xml.etree.ElementTree as ET
-import requests
-
-# 可配置支持的语言
-SUPPORTED_LANGUAGES = [
-    "zh", "en", "ar"
-]
-
-TOLGEE_API_URL = "http://47.236.31.243:8090/api"
-TOLGEE_API_KEY = "tgpak_hbpxgyltnryguy3lonyxiyrtgruta4rroryhiolooe3q"
-PROJECT_ID = "8"
-
-def find_strings_files(root_dir="."):
-    result = []
-    for root, dirs, files in os.walk(root_dir):
-        normalized_root = root.replace("\\", "/")
-        if "lite" in normalized_root or "lite" in files:
-            continue
-        if "/build/" in normalized_root or normalized_root.endswith("/build"):
-            continue
-
-        if "strings.xml" in files and "/src/" in normalized_root:
-            full_path = os.path.join(root, "strings.xml")
-            rel_path = os.path.relpath(full_path, root_dir).replace("\\", "/")
-            result.append({
-                "path": rel_path
-            })
-    return result
-
-def extract_language_from_path(path):
-    """
-    从 Android 的 values[-xx[-rYY]] 路径中提取语言代码。
-    - 默认返回 'en'
-    - 特殊处理:'in' → 'id'
-    """
-    match = re.search(r'values(?:-([a-zA-Z-]+))?/', path)
-    if match:
-        lang = match.group(1)
-        if not lang:
-            return "en"
-        lang = lang.replace("-r", "-")
-        if lang == "in":
-            return "id"
-        return lang
-    return "en"
-
-def generate_tolgee_config():
-    files = find_strings_files(".")
-
-    push_files = []
-
-    for f in files:
-        lang = extract_language_from_path(f["path"])
-        if lang in SUPPORTED_LANGUAGES:
-            push_files.append({
-                "path": f["path"],
-                "language": lang
-            })
-
-    config = {
-        "apiUrl": TOLGEE_API_URL,
-        "apiKey": TOLGEE_API_KEY,
-        "projectId": PROJECT_ID,
-        "format": "ANDROID_XML",
-        "convertPlaceholdersTo": "icu",
-        "structureDelimiter": ".",
-        "push": {
-            "files": push_files
-        }
-    }
-
-    with open(".tolgeerc", "w", encoding="utf-8") as f:
-        json.dump(config, f, indent=2)
-
-    print(f"✅ Tolgee 配置已生成,共 {len(push_files)} 个语言文件")
-
-def run_tolgee_push():
-    print("🚀 正在执行 tolgee push ...")
-    try:
-        subprocess.run(["tolgee", "push", "--force-mode", "KEEP","--verbose"], check=True)
-        print("✅ 推送成功")
-    except subprocess.CalledProcessError as e:
-        print("❌ 推送失败", e)
-
-
-HEADERS = {
-    "X-Api-Key": TOLGEE_API_KEY,
-    "Content-Type": "application/json"
-}
-
-def fetch_all_keys():
-    dataList = [] #key是name,value是id
-    page = 0
-    page_size = 1000
-    while True:
-        url = f"{TOLGEE_API_URL}/v2/projects/{PROJECT_ID}/keys?page={page}&size={page_size}"
-        r = requests.get(url, headers=HEADERS)
-        if r.status_code != 200:
-            print(f"❌ 拉取失败: {r.status_code} -> {r.text}")
-            break
-
-        data = r.json()
-        # print(data)
-
-        dataList.extend(data['_embedded']['keys'])
-
-
-        if data['page']['totalPages'] == page +1 :  # 最后一页
-            break
-        page += 1
-    print(f"✅ 已拉取 {len(dataList)} 个 Tolgee keys")
-    # print(dataList)
-    return dataList
-
-def find_translatable_false_keys():
-    result = []
-    for root, dirs, files in os.walk("."):
-        normalized_root = root.replace("\\", "/")
-        if normalized_root.endswith("/res/values") and "strings.xml" in files:
-            file_path = os.path.join(root, "strings.xml")
-            try:
-                tree = ET.parse(file_path)
-                root_elem = tree.getroot()
-                for elem in root_elem.findall("string"):
-                    if elem.attrib.get("translatable") == "false":
-                        result.append(elem.attrib["name"])
-            except Exception as e:
-                print(f"⚠️ 解析失败:{file_path} -> {e}")
-    print(f"🔍 找到 {len(result)} 个 translatable=false 的 key")
-    # print(result)
-    return result
-
-def get_key_id_by_name(key_name):
-    url = f"{TOLGEE_API_URL}/v2/projects/{PROJECT_ID}/keys?search={key_name}"
-    try:
-        resp = requests.get(url, headers=HEADERS)
-        data = resp.json()
-        print(f"🔍 查询 key ID:{key_name} -> {data}")
-        if isinstance(data, list) and data:
-            return data[0]["id"]
-    except Exception as e:
-        print(f"❌ 查询 key ID 失败:{key_name} -> {e}")
-    return None
-
-def fetch_language_ids():
-    print("📥 拉取 Tolgee 语言信息...")
-    url = f"{TOLGEE_API_URL}/v2/projects/{PROJECT_ID}/languages"
-    r = requests.get(url, headers=HEADERS)
-    if not r.ok:
-        print(f"❌ 获取 languages 失败:{r.status_code} -> {r.text}")
-        return []
-
-    languages = r.json()['_embedded']['languages']
-    print(languages)
-    # result = {}
-    # for lang in languages:
-    #     result[lang["tag"]] = lang["id"]
-    # print(f"✅ 拉取语言: {result}")
-    return {lang["tag"]: lang["id"] for lang in languages}
-
-def mark_keys_as_disabled(keys: list):
-    print(f"🔧 准备禁用 {len(keys)} 个不可翻译的 key")
-    all_keys = fetch_all_keys()
-    all_languages = fetch_language_ids()
-    disabled_lang_ids = [
-        lang_id for tag, lang_id in all_languages.items() if tag != 'en'
-    ]
-    print(f"🔍 禁用语言 ID: {disabled_lang_ids}")
-
-    key_map = {item["name"]: item["id"] for item in all_keys}
-    for key in keys:
-        key_id = key_map.get(key)
-        if not key_id:
-            print(f"⚠️ 跳过:{key},未找到 keyId")
-            continue
-
-        url = f"{TOLGEE_API_URL}/v2/projects/{PROJECT_ID}/keys/{key_id}/disabled-languages"
-
-        payload = json.dumps({
-            "languageIds": disabled_lang_ids
-        })
-
-        try:
-            resp =  requests.request("PUT", url, headers=HEADERS, data=payload)
-            if resp.status_code == 200:
-                print(f"✅ 已禁用:{key}")
-            else:
-                print(f"❌ 禁用失败:{key} -> {resp.status_code}: {resp.text}")
-        except Exception as e:
-            print(f"❌ 异常:{key} -> {e}")
-
-
-
-def main():
-    generate_tolgee_config()
-    run_tolgee_push()
-    # 处理 translatable=false 的 key
-    false_keys = find_translatable_false_keys()
-    if false_keys:
-        mark_keys_as_disabled(false_keys)
-
-if __name__ == "__main__":
-    main()