DoggyZhang 1 день назад
Родитель
Сommit
0170674046
35 измененных файлов с 841 добавлено и 201 удалено
  1. 9 0
      app/src/main/java/com/adealink/weparty/commonui/widget/NumberIncreaseView.kt
  2. 9 0
      app/src/main/java/com/adealink/weparty/module/gift/GiftModule.kt
  3. 3 0
      app/src/main/java/com/adealink/weparty/module/gift/IGiftService.kt
  4. 2 1
      app/src/main/java/com/adealink/weparty/module/gift/viewmodel/IGiftViewModel.kt
  5. 28 0
      app/src/main/java/com/adealink/weparty/module/room/chat/data/Message.kt
  6. 37 0
      app/src/main/java/com/adealink/weparty/module/room/chat/data/Notification.kt
  7. 1 0
      app/src/main/res/values-in/strings.xml
  8. 2 1
      app/src/main/res/values-zh/strings.xml
  9. 1 0
      app/src/main/res/values/attrs.xml
  10. 2 1
      app/src/main/res/values/strings.xml
  11. 5 0
      module/gift/src/main/java/com/adealink/weparty/gift/GiftServiceImpl.kt
  12. 4 0
      module/gift/src/main/java/com/adealink/weparty/gift/manager/GiftManager.kt
  13. 3 0
      module/gift/src/main/java/com/adealink/weparty/gift/manager/IGiftManager.kt
  14. 3 4
      module/gift/src/main/java/com/adealink/weparty/gift/viewmodel/GiftViewModel.kt
  15. 6 0
      module/room/src/main/java/com/adealink/weparty/room/chat/ChatMessageFragment.kt
  16. 95 0
      module/room/src/main/java/com/adealink/weparty/room/chat/adapter/SendGiftMessageViewBinder.kt
  17. 5 5
      module/room/src/main/java/com/adealink/weparty/room/chat/viewmodel/ChatMessageViewModel.kt
  18. 20 12
      module/room/src/main/java/com/adealink/weparty/room/chatroom/page/dispatchcenter/DispatchRoomEventViewModel.kt
  19. 9 0
      module/room/src/main/java/com/adealink/weparty/room/datasource/local/RoomLocalService.kt
  20. 86 49
      module/room/src/main/java/com/adealink/weparty/room/gift/RoomGiftPanelDialog.kt
  21. 110 0
      module/room/src/main/java/com/adealink/weparty/room/gift/dialog/DiamondRechargeDialog.kt
  22. 74 0
      module/room/src/main/java/com/adealink/weparty/room/gift/viewmodel/RoomGiftViewModel.kt
  23. 3 2
      module/room/src/main/java/com/adealink/weparty/room/sdk/controller/IMessageController.kt
  24. 38 30
      module/room/src/main/java/com/adealink/weparty/room/sdk/controller/impl/MessageController.kt
  25. 4 0
      module/room/src/main/java/com/adealink/weparty/room/viewmodel/RoomViewModelFactory.kt
  26. 6 0
      module/room/src/main/res/drawable/room_bottom_send_gift_button.xml
  27. 0 5
      module/room/src/main/res/drawable/shape_room_gift_btn_send_bg_selected.xml
  28. 0 5
      module/room/src/main/res/drawable/shape_room_gift_btn_send_bg_unselected.xml
  29. 132 0
      module/room/src/main/res/layout/dialog_diamond_recharge.xml
  30. 69 83
      module/room/src/main/res/layout/dialog_room_gift_panel.xml
  31. 69 0
      module/room/src/main/res/layout/item_message_send_gift.xml
  32. 6 0
      module/room/src/main/res/values/strings.xml
  33. 0 1
      module/wallet/src/main/res/values-in/strings.xml
  34. 0 1
      module/wallet/src/main/res/values-zh/strings.xml
  35. 0 1
      module/wallet/src/main/res/values/strings.xml

+ 9 - 0
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,8 @@ class NumberIncreaseView @JvmOverloads constructor(
     private val binding =
         LayoutNumberIncreaseViewBinding.inflate(LayoutInflater.from(context), this)
 
+    private var numberTextColor = getColorX(R.color.color_FF1D2129)
+
     private var number = 0
 
     private var listener: OnNumberChangedListener? = null
@@ -33,9 +36,15 @@ class NumberIncreaseView @JvmOverloads constructor(
                 R.styleable.NumberIncreaseView_default_number,
                 0
             ).coerceIn(MIN_NUMBER, MAX_NUMBER)
+
+            numberTextColor = getColor(
+                R.styleable.NumberIncreaseView_number_text_color,
+                numberTextColor
+            )
         }
 
         binding.tvNumber.text = number.toString()
+        binding.tvNumber.setTextColor(numberTextColor)
         binding.ivMinus.setOnClickListener {
             clickMinus()
         }

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

+ 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)

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

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

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

@@ -608,6 +608,7 @@
 
     <declare-styleable name="NumberIncreaseView">
         <attr name="default_number" format="integer" />
+        <attr name="number_text_color" format="color" />
     </declare-styleable>
 
     <declare-styleable name="PriceView">

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

@@ -155,7 +155,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 +409,5 @@
     <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>
 </resources>

+ 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)

+ 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

+ 9 - 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,13 @@ object RoomLocalService : TypeDelegationPrefs(
     }
 ) {
 
+    /**
+     * 上一次钻石不足提示提醒的时间
+     */
+    var lastDiamondRemindTs: Long by PrefKey("key_last_diamond_remind_ts", 0)
+
+    fun showDiamondRemind(): Boolean {
+        return !isSameDay(lastDiamondRemindTs, System.currentTimeMillis())
+    }
 
 }

+ 86 - 49
module/room/src/main/java/com/adealink/weparty/room/gift/RoomGiftPanelDialog.kt

@@ -1,6 +1,5 @@
 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
@@ -9,21 +8,27 @@ import com.adealink.frame.base.Rlt
 import com.adealink.frame.base.fastLazy
 import com.adealink.frame.mvvm.view.viewBinding
 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.toast.util.showFailedToast
 import com.adealink.weparty.commonui.widget.BottomDialogFragment
+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.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.RoomGiftUserItemViewBinder
 import com.adealink.weparty.room.gift.data.RoomGiftItemData
 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
@@ -37,7 +42,10 @@ class RoomGiftPanelDialog : BottomDialogFragment(R.layout.dialog_room_gift_panel
     private val userAdapter by fastLazy { MultiTypeListAdapter<RoomGiftUserItemData>() }
     private val giftAdapter by fastLazy { MultiTypeListAdapter<RoomGiftItemData>() }
     private var tagetUid: String? = null
-    private var giftQuantity = 1
+
+    private var selectGift: GiftInfo? = null
+
+    override val height: Int = 400.dp()
 
     fun setTargetUid(uid: String?) {
         this.tagetUid = uid
@@ -45,8 +53,41 @@ class RoomGiftPanelDialog : BottomDialogFragment(R.layout.dialog_room_gift_panel
 
     override fun initViews() {
         super.initViews()
+        binding.tvBalance.onClick {
+            WalletModule.showRechargeDialog(childFragmentManager, Currency.DIAMOND)
+        }
         initRecyclerViews()
-        initListeners()
+        binding.vNumber.setListener(object : NumberIncreaseView.OnNumberChangedListener {
+            override fun onNumberChanged(number: Int) {
+                updateBottomPanel()
+            }
+        })
+        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
+            }
+            val giftCount = binding.vNumber.getNumber()
+            if (giftCount <= 0) {
+                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()
     }
 
@@ -112,66 +153,62 @@ class RoomGiftPanelDialog : BottomDialogFragment(R.layout.dialog_room_gift_panel
 
         // Gifts RecyclerView
         giftAdapter.register(RoomGiftItemViewBinder { clickedItem ->
-            val list = giftAdapter.getCurrentList().map {
-                it.copy(isSelected = it.giftInfo.id == clickedItem.giftInfo.id)
+            val list = giftAdapter.getCurrentList().map {itemData->
+                itemData.copy(isSelected = itemData.giftInfo.id == clickedItem.giftInfo.id).also {
+                    if(it.isSelected){
+                        this@RoomGiftPanelDialog.selectGift = it.giftInfo
+                    }
+                }
             }
             giftAdapter.submitList(list)
-            giftQuantity = 1 // Reset quantity when a new gift is selected
+            binding.vNumber.setNumber(1)
             updateBottomPanel()
         })
         binding.rvGifts.layoutManager = GridLayoutManager(context, 4)
         binding.rvGifts.adapter = giftAdapter
     }
 
-    private fun initListeners() {
-        binding.btnPlus.onClick {
-            giftQuantity++
-            updateBottomPanel()
-        }
-        binding.btnMinus.onClick {
-            if (giftQuantity > 1) {
-                giftQuantity--
-                updateBottomPanel()
-            }
+    private fun updateBottomPanel() {
+        if (selectGift != null) {
+            binding.vNumber.show()
+            binding.btnSend.isEnabled = true
+        } else {
+            binding.vNumber.gone()
+            binding.btnSend.isEnabled = false
         }
-        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
-            }
-            roomGiftViewModel?.sendGift(toUids.toSet(), selectedGift, giftQuantity)?.observe(viewLifecycleOwner) { rlt ->
-                when (rlt) {
-                    is Rlt.Failed -> {
-                        showFailedToast(rlt)
-                    }
+    }
 
-                    is Rlt.Success<*> -> {
-                        walletViewModel?.refreshWalletData()
-                    }
-                }
+    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
         }
+        //金币兑换后也不足, 提示充值
+        WalletModule.showRechargeDialog(childFragmentManager, Currency.DIAMOND)
     }
 
-    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 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()
+                }
+            }
         }
     }
 }

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

@@ -0,0 +1,110 @@
+package com.adealink.weparty.room.gift.dialog
+
+import android.text.SpannableStringBuilder
+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 initViews() {
+        super.initViews()
+        updateCoin(walletViewModel?.getCoin() ?: 0.0)
+        updateExchangeRate()
+        binding.topCloseBtn.onClick {
+            dismiss()
+        }
+        binding.btnConfirm.onClick {
+            if (todayNeverRemind) {
+                RoomLocalService.lastDiamondRemindTs = System.currentTimeMillis()
+            }
+            onConfirmClick?.invoke()
+        }
+        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) ?: ""
+                        }
+                    )
+                }
+            }
+        }
+    }
+
+}

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

+ 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()
 

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

+ 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="24dp"
+        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>

+ 69 - 83
module/room/src/main/res/layout/dialog_room_gift_panel.xml

@@ -3,51 +3,64 @@
     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:layout_height="match_parent"
     android:background="@drawable/shape_room_gift_panel_bg"
-    android:paddingBottom="20dp">
+    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="match_parent"
+            android:layout_marginStart="6dp"
+            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"
+            tools:listitem="@layout/item_room_gift_user" />
+
+    </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="10dp"
+        app:layout_constrainedHeight="true"
+        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"
@@ -71,67 +84,40 @@
             app:layout_constraintStart_toEndOf="@+id/iv_diamond"
             app:layout_constraintTop_toTopOf="parent" />
 
-        <androidx.appcompat.widget.AppCompatTextView
-            android:id="@+id/btn_send"
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/cl_right"
             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"
+            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_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="parent" />
-
-        <LinearLayout
-            android:id="@+id/ll_quantity"
-            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"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toStartOf="@+id/btn_send"
             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_marginEnd="10dp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toStartOf="@id/btn_send"
+                app:layout_constraintTop_toTopOf="parent"
+                app:number_text_color="@color/white" />
+
+            <com.adealink.weparty.commonui.widget.CommonButton
+                android:id="@+id/btn_send"
+                android:layout_width="wrap_content"
+                android:layout_height="32dp"
+                android:background="@drawable/shape_room_gift_btn_send_bg"
+                android:paddingHorizontal="12dp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:text="Send"
+                app:textColor="@color/white"
+                app:textSize="14sp" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
 
-            <androidx.appcompat.widget.AppCompatTextView
-                android:id="@+id/btn_plus"
-                android:layout_width="24dp"
-                android:layout_height="24dp"
-                android:layout_marginEnd="4dp"
-                android:gravity="center"
-                android:text="+"
-                android:textColor="@color/white"
-                android:textSize="18sp"
-                android:textStyle="bold" />
-        </LinearLayout>
     </androidx.constraintlayout.widget.ConstraintLayout>
 
 </androidx.constraintlayout.widget.ConstraintLayout>

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

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

@@ -68,4 +68,10 @@
     <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">钻石不足</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 Send %2$s</string>
 </resources>

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