Browse Source

补充一些麦位操作

DoggyZhang 2 weeks ago
parent
commit
d29cbd02f6
29 changed files with 670 additions and 419 deletions
  1. 4 4
      app/dependencies/releaseRuntimeClasspath.txt
  2. 0 0
      app/src/main/res/drawable-xhdpi/common_emoji_ic.png
  3. 0 263
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/CallView.kt
  4. 0 119
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/widget/multi/MultiCallWaitingView.kt
  5. 3 1
      frame/room/src/main/java/com/adealink/frame/room/data/RoomState.kt
  6. 4 4
      gradle/libs.versions.toml
  7. 1 1
      module/im/src/main/java/com/adealink/weparty/im/session/comp/SessionBottomInputComp.kt
  8. 1 1
      module/im/src/main/res/layout/layout_session_bottom_emotion_bar.xml
  9. 1 1
      module/im/src/main/res/layout/layout_session_bottom_input_bar.xml
  10. 103 0
      module/room/src/main/java/com/adealink/weparty/room/chat/ChatInputDialog.kt
  11. 4 0
      module/room/src/main/java/com/adealink/weparty/room/chatroom/page/dispatchcenter/DispatchCenterRoomInfoFragment.kt
  12. 9 1
      module/room/src/main/java/com/adealink/weparty/room/create/CreateRoomDialog.kt
  13. 6 6
      module/room/src/main/java/com/adealink/weparty/room/interceptor/EnterRoomUriInterceptor.kt
  14. 42 0
      module/room/src/main/java/com/adealink/weparty/room/invite/InviteMicDialog.kt
  15. 43 4
      module/room/src/main/java/com/adealink/weparty/room/micseat/BaseSeatsTemplate.kt
  16. 6 9
      module/room/src/main/java/com/adealink/weparty/room/operate/RoomBottomOperateFragment.kt
  17. 18 0
      module/room/src/main/java/com/adealink/weparty/room/sdk/controller/IJoinController.kt
  18. 8 0
      module/room/src/main/java/com/adealink/weparty/room/sdk/controller/ISeatController.kt
  19. 99 5
      module/room/src/main/java/com/adealink/weparty/room/sdk/controller/impl/JoinController.kt
  20. 58 0
      module/room/src/main/java/com/adealink/weparty/room/sdk/controller/impl/SeatController.kt
  21. 29 0
      module/room/src/main/java/com/adealink/weparty/room/setting/RoomSettingDialog.kt
  22. BIN
      module/room/src/main/res/drawable-xhdpi/room_setting_close_ic.png
  23. BIN
      module/room/src/main/res/drawable-xhdpi/room_setting_setting_ic.png
  24. 5 0
      module/room/src/main/res/drawable/room_setting_item_bg.xml
  25. 69 0
      module/room/src/main/res/layout/dialog_chat_message_input.xml
  26. 61 0
      module/room/src/main/res/layout/dialog_room_invite_mic.xml
  27. 91 0
      module/room/src/main/res/layout/dialog_room_setting.xml
  28. 1 0
      module/room/src/main/res/values/ids.xml
  29. 4 0
      module/room/src/main/res/values/strings.xml

+ 4 - 4
app/dependencies/releaseRuntimeClasspath.txt

@@ -64,7 +64,7 @@ androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4
 androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.4
 androidx.lifecycle:lifecycle-viewmodel:2.8.4
 androidx.loader:loader:1.0.0
-androidx.localbroadcastmanager:localbroadcastmanager:1.0.0
+androidx.localbroadcastmanager:localbroadcastmanager:1.1.0
 androidx.media3:media3-common:1.4.1
 androidx.media3:media3-container:1.4.1
 androidx.media3:media3-database:1.4.1
@@ -274,9 +274,9 @@ io.github.scwang90:refresh-header-material:3.0.0-alpha
 io.github.scwang90:refresh-layout-kernel:3.0.0-alpha
 io.reactivex.rxjava3:rxandroid:3.0.0
 io.reactivex.rxjava3:rxjava:3.0.4
-io.trtc.uikit:atomicx-core:3.6.1.66
-io.trtc.uikit:common:3.3.0.1194
-io.trtc.uikit:rtc_room_engine:3.6.1.65
+io.trtc.uikit:atomicx-core:4.0.0.110
+io.trtc.uikit:common:3.5.0.1332
+io.trtc.uikit:rtc_room_engine:3.6.1.76
 javax.inject:javax.inject:1
 org.checkerframework:checker-qual:3.43.0
 org.conscrypt:conscrypt-android:2.5.3

+ 0 - 0
module/im/src/main/res/drawable-xhdpi/im_session_emoji_ic.png → app/src/main/res/drawable-xhdpi/common_emoji_ic.png


+ 0 - 263
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/CallView.kt

@@ -1,263 +0,0 @@
-package io.trtc.tuikit.atomicx.callview
-
-import android.content.Context
-import android.util.AttributeSet
-import android.view.LayoutInflater
-import android.widget.FrameLayout
-import android.widget.LinearLayout
-import androidx.constraintlayout.widget.ConstraintLayout
-import com.trtc.tuikit.common.util.ScreenUtil.dip2px
-import io.trtc.tuikit.atomicx.R
-import io.trtc.tuikit.atomicx.callview.core.CallViewFunction
-import io.trtc.tuikit.atomicx.callview.core.common.utils.CallUtils
-import io.trtc.tuikit.atomicx.callview.core.common.utils.ImageResourceCache
-import io.trtc.tuikit.atomicx.callview.widget.controls.MultiCallControlsView
-import io.trtc.tuikit.atomicx.callview.widget.controls.SingleCallControlsView
-import io.trtc.tuikit.atomicx.callview.widget.hint.HintView
-import io.trtc.tuikit.atomicx.callview.widget.hint.TimerView
-import io.trtc.tuikit.atomicx.callview.widget.multi.MultiCallWaitingView
-import io.trtc.tuikit.atomicxcore.api.call.CallParticipantInfo
-import io.trtc.tuikit.atomicxcore.api.call.CallParticipantStatus
-import io.trtc.tuikit.atomicxcore.api.call.CallStore
-import io.trtc.tuikit.atomicxcore.api.device.NetworkQuality
-import io.trtc.tuikit.atomicxcore.api.view.CallCoreView
-import io.trtc.tuikit.atomicxcore.api.view.CallLayoutTemplate
-import io.trtc.tuikit.atomicxcore.api.view.VolumeLevel
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.supervisorScope
-import java.io.File
-import java.util.concurrent.atomic.AtomicInteger
-
-class CallView @JvmOverloads constructor(
-    context: Context,
-    attrs: AttributeSet? = null,
-    defStyleAttr: Int = 0
-) : ConstraintLayout(context, attrs, defStyleAttr), CallViewFunction {
-    private data class ParticipantAvatarInfo(
-        var originalUrl: String,
-        var cachedPath: String?
-    )
-    
-    private var callMainView: CallCoreView? = null
-    private var subscribeStateJob: Job? = null
-    private val participantAvatarInfoMap: MutableMap<String, ParticipantAvatarInfo> = mutableMapOf()
-    private val imageResourceCache = ImageResourceCache(context)
-
-    private var layoutFunction: FrameLayout? = null
-    private var layoutTimer: FrameLayout? = null
-    private var layoutCallHint: FrameLayout? = null
-    private var multiCallWaitingViewContainer: LinearLayout? = null
-
-    init {
-        initView()
-        setIconResourcePath()
-    }
-
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        addFunctionLayout()
-        updateWaitingView()
-        subscribeStateJob = CoroutineScope(Dispatchers.Main).launch {
-            supervisorScope {
-                launch { observeSelfInfo() }
-                launch { observeParticipantInfo() }
-            }
-        }
-    }
-
-    override fun onDetachedFromWindow() {
-        super.onDetachedFromWindow()
-        callMainView?.removeAllViews()
-        subscribeStateJob?.cancel()
-    }
-
-    private suspend fun observeSelfInfo() {
-        CallStore.shared.observerState.selfInfo.collect { selfInfo ->
-            if (selfInfo.status == CallParticipantStatus.Accept && callMainView?.visibility == GONE) {
-                updateWaitingView()
-            }
-        }
-    }
-
-    private suspend fun observeParticipantInfo() {
-        CallStore.shared.observerState.allParticipants.collect { allParticipants ->
-            updateParticipantsAvatars(allParticipants)
-        }
-    }
-
-    private fun initView() {
-        LayoutInflater.from(context).inflate(R.layout.callview_root_view, this, true)
-        multiCallWaitingViewContainer = findViewById(R.id.ll_callee_waiting_view)
-        layoutFunction = findViewById(R.id.rl_layout_function)
-        layoutTimer = findViewById(R.id.rl_layout_call_time)
-        layoutCallHint = findViewById(R.id.rl_layout_call_hint)
-        callMainView = CallCoreView(context)
-        val layoutParams = ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_PARENT, ConstraintLayout.LayoutParams.MATCH_PARENT)
-        this.addView(callMainView, 0, layoutParams)
-    }
-
-    private fun updateWaitingView() {
-        val selfUser = CallStore.shared.observerState.selfInfo.value
-        if (CallParticipantStatus.Waiting == selfUser.status && !CallUtils.isCaller(selfUser.id) && isMultiCall()) {
-            multiCallWaitingViewContainer?.addView(MultiCallWaitingView(context))
-            multiCallWaitingViewContainer?.visibility = VISIBLE
-            callMainView?.visibility = GONE
-            layoutCallHint?.visibility = GONE
-            layoutTimer?.visibility = GONE
-        } else {
-            multiCallWaitingViewContainer?.visibility = GONE
-            callMainView?.visibility = VISIBLE
-            layoutCallHint?.visibility = VISIBLE
-            layoutTimer?.visibility = VISIBLE
-        }
-    }
-
-    private fun setIconResourcePath() {
-        val volumeLevelIcons = mapOf(
-            VolumeLevel.Mute to imageResourceCache.getDrawablePath(R.drawable.callview_ic_self_mute),
-            VolumeLevel.Low to imageResourceCache.getDrawablePath(R.drawable.callview_ic_audio_input),
-            VolumeLevel.Medium to imageResourceCache.getDrawablePath(R.drawable.callview_ic_audio_input),
-            VolumeLevel.High to imageResourceCache.getDrawablePath(R.drawable.callview_ic_audio_input),
-            VolumeLevel.Peak to imageResourceCache.getDrawablePath(R.drawable.callview_ic_audio_input),
-        )
-        callMainView?.setVolumeLevelIcons(volumeLevelIcons)
-
-        val networkQualityIcons = mapOf(
-            NetworkQuality.BAD to imageResourceCache.getDrawablePath(R.drawable.callview_ic_network_bad),
-            NetworkQuality.VERY_BAD to imageResourceCache.getDrawablePath(R.drawable.callview_ic_network_bad)
-        )
-        callMainView?.setNetworkQualityIcons(networkQualityIcons)
-        callMainView?.setWaitingAnimation(imageResourceCache.getDrawablePath(R.drawable.callview_ic_loading))
-    }
-
-    private fun addFunctionLayout() {
-        if (isMultiCall()) {
-            addMultiFunctionLayout()
-        } else {
-            addSingleFunctionLayout()
-        }
-    }
-
-    private fun addMultiFunctionLayout() {
-        layoutFunction?.addView(MultiCallControlsView(context))
-        layoutTimer?.addView(TimerView(context))
-        layoutCallHint?.addView(HintView(context))
-    }
-
-    private fun addSingleFunctionLayout() {
-        layoutFunction?.addView(SingleCallControlsView(context))
-        layoutTimer?.addView(TimerView(context))
-        layoutCallHint?.addView(HintView(context))
-    }
-
-    private fun updateParticipantsAvatars(participants: Collection<CallParticipantInfo>) {
-        val participantsToUpdate = mutableListOf<Pair<String, String>>()
-        val currentParticipantIds = participants.map { it.id }.toSet()
-        val removedIds = participantAvatarInfoMap.keys.filter { it !in currentParticipantIds }
-        val hasRemovedParticipants = removedIds.isNotEmpty()
-        removedIds.forEach { id ->
-            participantAvatarInfoMap.remove(id)
-        }
-        for (participant in participants) {
-            val participantId = participant.id
-            val currentAvatarUrl = participant.avatarUrl ?: ""
-            val existingInfo = participantAvatarInfoMap[participantId]
-            if (existingInfo?.originalUrl != currentAvatarUrl) {
-                participantsToUpdate.add(participantId to currentAvatarUrl)
-                participantAvatarInfoMap[participantId] = ParticipantAvatarInfo(
-                    originalUrl = currentAvatarUrl,
-                    cachedPath = existingInfo?.cachedPath
-                )
-            }
-        }
-        
-        if (participantsToUpdate.isEmpty()) {
-            if (hasRemovedParticipants) {
-                val avatarMap = buildAvatarPathMap()
-                callMainView?.setParticipantAvatars(avatarMap)
-            }
-            return
-        }
-        
-        val completedCount = AtomicInteger(0)
-        val totalCount = participantsToUpdate.size
-        
-        for ((participantId, avatarUrl) in participantsToUpdate) {
-            imageResourceCache.cacheNetworkImage(avatarUrl) { cachedPath ->
-                synchronized(participantAvatarInfoMap) {
-                    val info = participantAvatarInfoMap[participantId]
-                    if (info != null) {
-                        if (cachedPath != null) {
-                            info.cachedPath = File(cachedPath).absolutePath
-                        } else {
-                            val defaultAvatarPath = imageResourceCache.getDefaultAvatarPath(R.drawable.callview_ic_avatar)
-                            info.cachedPath = defaultAvatarPath
-                            if (defaultAvatarPath == null) {
-                                participantAvatarInfoMap.remove(participantId)
-                            }
-                        }
-                    }
-                    if (completedCount.incrementAndGet() == totalCount) {
-                        val avatarMap = buildAvatarPathMap()
-                        callMainView?.setParticipantAvatars(avatarMap)
-                    }
-                }
-            }
-        }
-    }
-
-    private fun buildAvatarPathMap(): Map<String, String> {
-        return participantAvatarInfoMap
-            .filter { it.value.cachedPath != null }
-            .mapValues { it.value.cachedPath!! }
-    }
-
-    override fun setLayoutTemplate(template: CallLayoutTemplate) {
-        val isPipView = template == CallLayoutTemplate.Pip
-        layoutFunction?.visibility = if (isPipView) GONE else VISIBLE
-        layoutCallHint?.visibility = if (isPipView) GONE else VISIBLE
-        layoutTimer?.visibility = if (isPipView) GONE else VISIBLE
-        multiCallWaitingViewContainer?.visibility = if (isPipView) GONE else VISIBLE
-        callMainView?.visibility = VISIBLE
-        updateCallCoreViewTopMargin(template)
-        callMainView?.setLayoutTemplate(template)
-    }
-
-    private fun updateCallCoreViewTopMargin(template: CallLayoutTemplate) {
-        val marginTop = if (template == CallLayoutTemplate.Grid) {
-            getStatusBarHeight() + dip2px(GRID_VIDEO_CONTAINER_MARGIN_TOP_DP)
-        } else {
-            0
-        }
-        val layoutParams = callMainView?.layoutParams as? ConstraintLayout.LayoutParams
-        layoutParams?.let {
-            it.topMargin = marginTop
-            callMainView?.layoutParams = it
-        }
-    }
-
-    private fun getStatusBarHeight(): Int {
-        var statusBarHeight = 0
-        val resourceId = this.resources.getIdentifier(STATUS_BAR_HEIGHT, DIMEN, ANDROID)
-        if (resourceId > 0) {
-            statusBarHeight = this.resources.getDimensionPixelSize(resourceId)
-        }
-        return statusBarHeight
-    }
-
-    private fun isMultiCall(): Boolean {
-        val inviteeIdListSize = CallStore.shared.observerState.activeCall.value.inviteeIds.size
-        val chatGroupId = CallStore.shared.observerState.activeCall.value.chatGroupId
-        return chatGroupId.isNotEmpty() || inviteeIdListSize > 1
-    }
-
-    companion object {
-        private const val GRID_VIDEO_CONTAINER_MARGIN_TOP_DP = 45f
-        private const val STATUS_BAR_HEIGHT = "status_bar_height"
-        private const val DIMEN = "dimen"
-        private const val ANDROID = "android"
-    }
-}

+ 0 - 119
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/widget/multi/MultiCallWaitingView.kt

@@ -1,119 +0,0 @@
-package io.trtc.tuikit.atomicx.callview.widget.multi
-
-import android.content.Context
-import android.view.Gravity
-import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.LinearLayout
-import android.widget.TextView
-import androidx.constraintlayout.utils.widget.ImageFilterView
-import androidx.core.content.ContextCompat
-import com.trtc.tuikit.common.imageloader.ImageLoader
-import com.trtc.tuikit.common.util.ScreenUtil
-import io.trtc.tuikit.atomicx.R
-import io.trtc.tuikit.atomicx.callview.core.common.utils.CallUtils
-import io.trtc.tuikit.atomicxcore.api.call.CallParticipantInfo
-import io.trtc.tuikit.atomicxcore.api.call.CallStore
-import kotlinx.coroutines.MainScope
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.launch
-
-class MultiCallWaitingView(context: Context) : LinearLayout(context) {
-    private val mainScope = MainScope()
-    private var caller: CallParticipantInfo = CallParticipantInfo()
-    private val squareWidth = context.resources.getDimensionPixelOffset(R.dimen.callview_small_image_size)
-    private val defaultMargin = context.resources.getDimensionPixelOffset(R.dimen.callview_small_image_left_margin)
-
-    private lateinit var textWaitingUserName: TextView
-    private lateinit var imageCallerAvatar: ImageFilterView
-    private lateinit var layoutAvatarList: LinearLayout
-
-    init {
-        this.orientation = VERTICAL
-        this.gravity = Gravity.CENTER
-        val self = CallStore.shared.observerState.selfInfo.value.copy()
-        if (CallUtils.isCaller(self.id)) {
-            caller = self
-        } else {
-            val callerId = CallStore.shared.observerState.activeCall.value.inviterId
-            caller.id = callerId
-        }
-    }
-
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        initView()
-        registerParticipantsObserver()
-    }
-
-    override fun onDetachedFromWindow() {
-        super.onDetachedFromWindow()
-        mainScope.cancel()
-    }
-
-    private fun registerParticipantsObserver() {
-        mainScope.launch {
-            CallStore.shared.observerState.allParticipants.collect { participants ->
-                for (participant in participants) {
-                    if (CallUtils.isCaller(participant.id)) {
-                        ImageLoader.load(context, imageCallerAvatar, participant.avatarUrl, R.drawable.callview_ic_avatar)
-                        textWaitingUserName.text = participant.name
-                    }
-                }
-                updateAvatarListView()
-            }
-        }
-    }
-
-    private fun initView() {
-        imageCallerAvatar = createImageView(caller, ScreenUtil.dip2px(100f), 0, 20)
-        textWaitingUserName = createTextView(caller.name, 48)
-        textWaitingUserName.textSize = 18f
-        layoutAvatarList = LinearLayout(context)
-        layoutAvatarList.layoutParams =
-            LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
-
-        addView(imageCallerAvatar)
-        addView(textWaitingUserName)
-        addView(createTextView(context.getString(R.string.callview_invitee_user_list), 24))
-        addView(layoutAvatarList)
-    }
-
-    private fun updateAvatarListView() {
-        layoutAvatarList.removeAllViews()
-        val list = HashSet<CallParticipantInfo>()
-        val allParticipants = CallStore.shared.observerState.allParticipants.value.toSet()
-        val inviterId = CallStore.shared.observerState.activeCall.value.inviterId
-        val selfId = CallStore.shared.observerState.selfInfo.value.id
-        val inviteeList = allParticipants.filter { it.id != inviterId && it.id != selfId}
-        list.addAll(inviteeList)
-
-        for (user in list) {
-            layoutAvatarList.addView(createImageView(user, squareWidth, defaultMargin, 0))
-        }
-    }
-
-    private fun createImageView(user: CallParticipantInfo, width: Int, start: Int, bottom: Int): ImageFilterView {
-        val imageView = ImageFilterView(context)
-        val layoutParams = LayoutParams(width, width)
-        layoutParams.marginStart = start
-        layoutParams.bottomMargin = bottom
-        imageView.round = 12f
-        imageView.scaleType = ImageView.ScaleType.CENTER_CROP
-        imageView.layoutParams = layoutParams
-        ImageLoader.load(context, imageView, user.avatarUrl, R.drawable.callview_ic_avatar)
-        return imageView
-    }
-
-    private fun createTextView(text: String, margin: Int): TextView {
-        val textView = TextView(context)
-        textView.text = text
-        textView.textSize = 12f
-        textView.setTextColor(ContextCompat.getColor(context, R.color.callview_color_white))
-        val param = LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
-        param.bottomMargin = margin
-        param.gravity = Gravity.CENTER
-        textView.layoutParams = param
-        return textView
-    }
-}

+ 3 - 1
frame/room/src/main/java/com/adealink/frame/room/data/RoomState.kt

@@ -19,7 +19,9 @@ enum class ChannelState {
 class FlowStateInfo(var roomId: String = "") {
     var reason: String = ""
 
+    var leaveUI = false
+
     override fun toString(): String {
-        return "FlowStateInfo(roomId=$roomId, reason:$reason)"
+        return "FlowStateInfo(roomId=$roomId, reason:$reason, leaveUI:$leaveUI)"
     }
 }

+ 4 - 4
gradle/libs.versions.toml

@@ -180,12 +180,12 @@ fixandroid14debuggablelag = "1.0.0"
 # tuicore
 tencentIMSDK = "8.8.7357"
 tencnetUiCore = "8.8.7357"
-tencnetUiCommon = "3.3.0.1194"
-tencentLiteavSDK = "12.4.0.17372"
-tencnetRoommEngine = "3.3.1.1124"
+tencnetUiCommon = "3.5.0.1332"
+tencentLiteavSDK = "13.0.0.19676"
+tencnetRoommEngine = "3.6.1.76"
 tencnetTimpush = "8.8.7357"
 tencnetTimpushFcm = "8.8.7357"
-tencnetAtomicX = "3.6.1.66"
+tencnetAtomicX = "4.0.0.110"
 
 # rong cloud
 rongcloud = "5.24.0"

+ 1 - 1
module/im/src/main/java/com/adealink/weparty/im/session/comp/SessionBottomInputComp.kt

@@ -408,7 +408,7 @@ class SessionBottomInputComp(
 
     private fun hideEmotionPanel() {
         emotionContainer.gone()
-        inputBar.btnEmoji.setImageResource(R.drawable.im_session_emoji_ic)
+        inputBar.btnEmoji.setImageResource(APP_R.drawable.common_emoji_ic)
     }
 
     private fun resetInput() {

+ 1 - 1
module/im/src/main/res/layout/layout_session_bottom_emotion_bar.xml

@@ -29,7 +29,7 @@
             android:layout_marginBottom="8dp"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintStart_toStartOf="parent"
-            app:srcCompat="@drawable/im_session_emoji_ic" />
+            app:srcCompat="@drawable/common_emoji_ic" />
 
         <androidx.appcompat.widget.AppCompatEditText
             android:id="@+id/et_input_message"

+ 1 - 1
module/im/src/main/res/layout/layout_session_bottom_input_bar.xml

@@ -29,7 +29,7 @@
             android:layout_marginBottom="8dp"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintStart_toStartOf="parent"
-            app:srcCompat="@drawable/im_session_emoji_ic" />
+            app:srcCompat="@drawable/common_emoji_ic" />
 
         <com.tencent.qcloud.tuikit.tuichat.component.inputedittext.TIMMentionEditText
             android:id="@+id/et_input_message"

+ 103 - 0
module/room/src/main/java/com/adealink/weparty/room/chat/ChatInputDialog.kt

@@ -0,0 +1,103 @@
+package com.adealink.weparty.room.chat
+
+import android.annotation.SuppressLint
+import android.text.Editable
+import android.text.TextWatcher
+import android.view.KeyEvent
+import android.view.inputmethod.EditorInfo
+import android.widget.TextView
+import android.widget.TextView.OnEditorActionListener
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.util.onClick
+import com.adealink.weparty.commonui.text.setMaxLength
+import com.adealink.weparty.commonui.widget.KeyboardBottomDialogFragment
+import com.adealink.weparty.constant.IM_MESSAGE_MAX_LENGTH
+import com.adealink.weparty.room.R
+import com.adealink.weparty.room.databinding.DialogChatMessageInputBinding
+import com.adealink.weparty.R as APP_R
+
+class ChatInputDialog : KeyboardBottomDialogFragment(R.layout.dialog_chat_message_input) {
+
+    private val binding by viewBinding(DialogChatMessageInputBinding::bind)
+
+    private var isTextSendEnable = false
+
+    override fun initViews() {
+        super.initViews()
+
+        binding.btnEmoji.onClick {
+            //clickEmoji()
+        }
+        binding.btnSend.onClick {
+            sendTextMessage()
+        }
+        initInputText()
+    }
+
+    @SuppressLint("ClickableViewAccessibility")
+    private fun initInputText() {
+        var inputContent: String? = null
+
+        binding.etInputMessage.setMaxLength(IM_MESSAGE_MAX_LENGTH)
+        binding.etInputMessage.setText(inputContent)
+        binding.etInputMessage.setSelection(binding.etInputMessage.getText()?.length ?: 0)
+
+        binding.etInputMessage.isClickable = true
+        binding.etInputMessage.setOnEditorActionListener(object : OnEditorActionListener {
+            override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
+                if (actionId == EditorInfo.IME_ACTION_SEND
+                    || (actionId == EditorInfo.IME_ACTION_UNSPECIFIED && event != null && event.keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN)
+                ) {
+                    sendTextMessage()
+                }
+                return true
+            }
+        })
+        binding.etInputMessage.addTextChangedListener(object : TextWatcher {
+            override fun beforeTextChanged(
+                s: CharSequence?,
+                start: Int,
+                count: Int,
+                after: Int
+            ) {
+                inputContent = s?.toString()
+            }
+
+            override fun onTextChanged(
+                s: CharSequence?,
+                start: Int,
+                before: Int,
+                count: Int
+            ) {
+            }
+
+            override fun afterTextChanged(s: Editable?) {
+                val input = s?.toString()?.trim()
+                if (input.isNullOrEmpty()) {
+                    isTextSendEnable = false
+                } else {
+                    isTextSendEnable = true
+                }
+                if (binding.etInputMessage.lineCount > 1) {
+                    //大于一行
+                    binding.clInput.setBackgroundResource(APP_R.drawable.common_input_edit_bg)
+                } else {
+                    binding.clInput.setBackgroundResource(APP_R.drawable.common_input_single_edit_bg)
+                }
+                binding.btnSend.isEnabled = isTextSendEnable
+            }
+        })
+
+        binding.btnSend.isEnabled = false
+    }
+
+    private fun sendTextMessage() {
+        if (!isTextSendEnable) {
+            return
+        }
+        // TODO: 发送消息
+        //sessionInputViewModel.sendMessage(ChatMessageBuilder.buildTextMessage(inputBar.etInputMessage.text?.toString()))
+        binding.etInputMessage.setText(null)
+    }
+
+}

+ 4 - 0
module/room/src/main/java/com/adealink/weparty/room/chatroom/page/dispatchcenter/DispatchCenterRoomInfoFragment.kt

@@ -19,5 +19,9 @@ class DispatchCenterRoomInfoFragment : BaseFragment(R.layout.fragment_dispatch_c
         binding.roomId.text = "ID ${roomService.joinController.getJoinedRoomId()}"
     }
 
+    override fun observeViewModel() {
+        super.observeViewModel()
+    }
+
 
 }

+ 9 - 1
module/room/src/main/java/com/adealink/weparty/room/create/CreateRoomDialog.kt

@@ -3,7 +3,9 @@ package com.adealink.weparty.room.create
 import androidx.fragment.app.viewModels
 import com.adealink.frame.base.IError
 import com.adealink.frame.base.Rlt
+import com.adealink.frame.log.Log
 import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.room.data.TAG_ROOM
 import com.adealink.frame.router.Router
 import com.adealink.frame.util.onClick
 import com.adealink.weparty.commonui.ext.gone
@@ -22,6 +24,8 @@ import com.adealink.weparty.room.databinding.DialogCreateRoomBinding
 import io.trtc.tuikit.atomicxcore.api.live.LiveInfo
 import io.trtc.tuikit.atomicxcore.api.live.LiveInfoCompletionHandler
 import io.trtc.tuikit.atomicxcore.api.live.LiveListStore
+import io.trtc.tuikit.atomicxcore.api.live.SeatLayoutTemplate
+import io.trtc.tuikit.atomicxcore.api.live.TakeSeatMode
 
 class CreateRoomDialog : BottomDialogFragment(R.layout.dialog_create_room) {
     private val binding by viewBinding(DialogCreateRoomBinding::bind)
@@ -160,7 +164,10 @@ class CreateRoomDialog : BottomDialogFragment(R.layout.dialog_create_room) {
         }
         LiveListStore.shared().createLive(
             LiveInfo(
-                liveID = roomId
+                liveID = roomId,
+                maxSeatCount = 10,
+                seatTemplate = SeatLayoutTemplate.AudioSalon(10),
+                seatMode = TakeSeatMode.FREE
             ),
             object : LiveInfoCompletionHandler {
                 override fun onFailure(code: Int, desc: String) {
@@ -168,6 +175,7 @@ class CreateRoomDialog : BottomDialogFragment(R.layout.dialog_create_room) {
                 }
 
                 override fun onSuccess(liveInfo: LiveInfo) {
+                    Log.d(TAG_ROOM, "createRoom, $liveInfo")
                     Router.build(act, Room.Room.PATH)
                         .putExtra(Room.Room.EXTRA_ENTER_ROOM_ID, liveInfo.liveID)
                         .start()

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

@@ -129,12 +129,12 @@ class EnterRoomUriInterceptor : UriInterceptor {
         intent.putExtra(EXTRA_ENTER_ROOM_INFO, enterRoomInfo)
 
         //当前已经是进房状态,直接进入房间
-        val joinedRoomId = roomService.joinController.getJoinedRoomId()
-        if (joinedRoomId == enterRoomInfo.roomId) {
-            Log.d(TAG_ROOM_ENTER_ROOM, "EnterRoomUriInterceptor, already joined room, proceed()")
-            chain.proceed(request)
-            return
-        }
+//        val joinedRoomId = roomService.joinController.getJoinedRoomId()
+//        if (joinedRoomId == enterRoomInfo.roomId) {
+//            Log.d(TAG_ROOM_ENTER_ROOM, "EnterRoomUriInterceptor, already joined room, proceed()")
+//            chain.proceed(request)
+//            return
+//        }
 
         //房间媒体冲突处理失败,进房失败
         val conflictRlt = App.instance.mediaManager.conflictHandle(

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

@@ -0,0 +1,42 @@
+package com.adealink.weparty.room.invite
+
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.weparty.commonui.widget.BottomDialogFragment
+import com.adealink.weparty.room.R
+import com.adealink.weparty.room.databinding.DialogRoomInviteMicBinding
+import com.scwang.smart.refresh.layout.api.RefreshLayout
+import com.scwang.smart.refresh.layout.listener.OnRefreshLoadMoreListener
+
+class InviteMicDialog : BottomDialogFragment(R.layout.dialog_room_invite_mic) {
+
+    private val binding by viewBinding(DialogRoomInviteMicBinding::bind)
+
+    override fun initViews() {
+        super.initViews()
+
+        binding.vRefresh.setEnableRefresh(true)
+        binding.vRefresh.setEnableLoadMore(true)
+        binding.vRefresh.setEnableAutoLoadMore(true)
+        binding.vRefresh.setOnRefreshLoadMoreListener(object : OnRefreshLoadMoreListener {
+            override fun onRefresh(refreshLayout: RefreshLayout) {
+
+            }
+
+
+            override fun onLoadMore(refreshLayout: RefreshLayout) {
+
+            }
+        })
+
+//        listAdapter.register(VisitorItemViewBinder(this))
+//        binding.rvVisitor.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
+//        binding.rvVisitor.adapter = listAdapter
+//        binding.rvVisitor.addItemDecoration(
+//            VerticalSpaceItemDecoration(
+//                0,
+//                firstSpaceHeight = 6.dp()
+//            )
+//        )
+    }
+
+}

+ 43 - 4
module/room/src/main/java/com/adealink/weparty/room/micseat/BaseSeatsTemplate.kt

@@ -15,9 +15,11 @@ import com.adealink.weparty.commonui.toast.util.showToast
 import com.adealink.weparty.module.profile.data.UserInfo
 import com.adealink.weparty.module.room.data.MIC_OFF
 import com.adealink.weparty.module.room.data.MicIndex
+import com.adealink.weparty.module.room.data.isSeatEmpty
 import com.adealink.weparty.module.room.listener.IMemberInfoListener
 import com.adealink.weparty.room.R
 import com.adealink.weparty.room.constant.logRoomTime
+import com.adealink.weparty.room.invite.InviteMicDialog
 import com.adealink.weparty.room.member.RoomMemberDialog
 import com.adealink.weparty.room.member.viewmodel.RoomMemberViewModel
 import com.adealink.weparty.room.micseat.defaults.BaseDefaultSeatView
@@ -109,6 +111,7 @@ abstract class BaseSeatsTemplate<SV : BaseDefaultSeatView, VM : BaseSeatsTemplat
             .addItem(R.id.id_mic_operate_on, getCompatString(R.string.room_mic_operate_on))
             .addItem(R.id.id_mic_operate_off, getCompatString(R.string.room_mic_operate_off))
             .addItem(R.id.id_mic_operate_lock, getCompatString(R.string.room_mic_operate_lock))
+            .addItem(R.id.id_mic_operate_unlock, getCompatString(R.string.room_mic_operate_unlock))
             .addItem(R.id.id_mic_operate_mute, getCompatString(R.string.room_mic_operate_mute))
             .addItem(
                 R.id.id_mic_operate_un_mute,
@@ -118,7 +121,7 @@ abstract class BaseSeatsTemplate<SV : BaseDefaultSeatView, VM : BaseSeatsTemplat
                 override fun onActionClick(viewId: Int) {
                     when (viewId) {
                         R.id.id_mic_operate_invite -> {
-
+                            InviteMicDialog().show(childFragmentManager)
                         }
 
                         R.id.id_mic_operate_on -> {
@@ -145,15 +148,52 @@ abstract class BaseSeatsTemplate<SV : BaseDefaultSeatView, VM : BaseSeatsTemplat
                         }
 
                         R.id.id_mic_operate_lock -> {
+                            roomService.seatController.micLock(
+                                seatView.micIndex.index,
+                                onSuccess = {},
+                                onFail = {
+                                    showToast(it)
+                                }
+                            )
+                        }
 
+                        R.id.id_mic_operate_unlock -> {
+                            roomService.seatController.micUnLock(
+                                seatView.micIndex.index,
+                                onSuccess = {},
+                                onFail = {
+                                    showToast(it)
+                                }
+                            )
                         }
 
-                        R.id.id_mic_operate_mute -> {
 
+                        R.id.id_mic_operate_mute -> {
+                            val seatInfo = seatView.micSeatInfo
+                            if (seatInfo == null || seatInfo.isSeatEmpty() || seatInfo.userInfo.userID.isNullOrEmpty()) {
+                                return
+                            }
+                            roomService.seatController.micMute(
+                                seatInfo.userInfo.userID,
+                                onSuccess = {},
+                                onFail = {
+                                    showToast(it)
+                                }
+                            )
                         }
 
                         R.id.id_mic_operate_un_mute -> {
-
+                            val seatInfo = seatView.micSeatInfo
+                            if (seatInfo == null || seatInfo.isSeatEmpty() || seatInfo.userInfo.userID.isNullOrEmpty()) {
+                                return
+                            }
+                            roomService.seatController.micUnMute(
+                                seatInfo.userInfo.userID,
+                                onSuccess = {},
+                                onFail = {
+                                    showToast(it)
+                                }
+                            )
                         }
                     }
                 }
@@ -259,5 +299,4 @@ abstract class BaseSeatsTemplate<SV : BaseDefaultSeatView, VM : BaseSeatsTemplat
     }
 
 
-
 }

+ 6 - 9
module/room/src/main/java/com/adealink/weparty/room/operate/RoomBottomOperateFragment.kt

@@ -19,8 +19,10 @@ import com.adealink.weparty.permission.PermissionUtils
 import com.adealink.weparty.room.R
 import com.adealink.weparty.room.applymic.ApplyMicDialog
 import com.adealink.weparty.room.applymic.ApplyMicListDialog
+import com.adealink.weparty.room.chat.ChatInputDialog
 import com.adealink.weparty.room.databinding.FragmentRoomBottomOperateBinding
 import com.adealink.weparty.room.micseat.viewmodel.RoomSeatViewModel
+import com.adealink.weparty.room.setting.RoomSettingDialog
 import com.adealink.weparty.room.viewmodel.RoomViewModelFactory
 
 open class RoomBottomOperateFragment : BaseFragment(R.layout.fragment_room_bottom_operate),
@@ -146,7 +148,7 @@ open class RoomBottomOperateFragment : BaseFragment(R.layout.fragment_room_botto
             }
 
             settingOperateItem -> {
-                onPlayCenterClick()
+                onSettingClick()
             }
         }
     }
@@ -189,16 +191,11 @@ open class RoomBottomOperateFragment : BaseFragment(R.layout.fragment_room_botto
     }
 
     private fun inputBoxClick() {
-//        showDialogFragment<ChatInputDialog>(childFragmentManager, Bundle().apply {
-//            val roomType = roomService.attrController.getRoomType()
-//            putBoolean(Room.ChatInputDialog.EXTRA_OPEN_DANMAKU, roomType == RoomType.CHAT)
-//        })
-//        reportBtnClick(RoomBaseStatEvent.Btn.INPUT_BOX)
+        ChatInputDialog().show(childFragmentManager)
     }
 
-    private fun onPlayCenterClick() {
-//        RoomPlayCenterPanelFragment.show(childFragmentManager)
-//        reportBtnClick(RoomBaseStatEvent.Btn.PLAY_CENTER_ENTRANCE)
+    private fun onSettingClick() {
+        RoomSettingDialog().show(childFragmentManager)
     }
 
     override fun onNewIntent(intent: Intent?) {

+ 18 - 0
module/room/src/main/java/com/adealink/weparty/room/sdk/controller/IJoinController.kt

@@ -3,6 +3,7 @@ package com.adealink.weparty.room.sdk.controller
 import com.adealink.frame.base.IError
 import com.adealink.frame.base.Rlt
 import com.adealink.frame.room.data.JoinRoomReq
+import com.adealink.frame.room.data.LeaveRoomReason
 import com.adealink.weparty.room.sdk.listener.IJoinListener
 import io.trtc.tuikit.atomicxcore.api.live.LiveInfo
 
@@ -17,6 +18,23 @@ interface IJoinController<L : IJoinListener> : IController<L> {
     fun getJoinedRoomId(): String?
 
     suspend fun joinRoom(req: JoinRoomReq): Rlt<Any>
+
+    /**
+     * 离开直播(下麦+退房)
+     */
+    suspend fun leaveRoom(
+        reason: LeaveRoomReason = LeaveRoomReason.INITIATIVE,
+        leaveUI: Boolean = false
+    )
+
+    /**
+     * 结束直播(解散房间)
+     */
+    suspend fun tryEndRoom(
+        reason: LeaveRoomReason = LeaveRoomReason.INITIATIVE,
+        leaveUI: Boolean = false
+    )
+
 }
 
 interface JoinChannelCallback {

+ 8 - 0
module/room/src/main/java/com/adealink/weparty/room/sdk/controller/ISeatController.kt

@@ -19,5 +19,13 @@ interface ISeatController<L : ISeatListener> : IController<L> {
 
     fun micOff(onSuccess: () -> Unit, onFail: (error: IError) -> Unit)
 
+    fun micLock(micInex: Int, onSuccess: () -> Unit, onFail: (error: IError) -> Unit)
+
+    fun micUnLock(micInex: Int, onSuccess: () -> Unit, onFail: (error: IError) -> Unit)
+
+    fun micMute(uid: String, onSuccess: () -> Unit, onFail: (error: IError) -> Unit)
+
+    fun micUnMute(uid: String, onSuccess: () -> Unit, onFail: (error: IError) -> Unit)
+
     suspend fun getMicSeatsInfo()
 }

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

@@ -8,6 +8,7 @@ import com.adealink.frame.media.data.RtcType
 import com.adealink.frame.room.data.ChannelState
 import com.adealink.frame.room.data.FlowStateInfo
 import com.adealink.frame.room.data.JoinRoomReq
+import com.adealink.frame.room.data.LeaveRoomReason
 import com.adealink.frame.room.data.RoomSameJoiningError
 import com.adealink.frame.room.data.RoomState
 import com.adealink.frame.room.data.TAG_ROOM_FLOW
@@ -18,9 +19,12 @@ import com.adealink.weparty.room.sdk.context.IRoomContext
 import com.adealink.weparty.room.sdk.controller.BaseController
 import com.adealink.weparty.room.sdk.controller.IJoinController
 import com.adealink.weparty.room.sdk.listener.IJoinListener
+import com.tencent.cloud.tuikit.engine.extension.TUILiveListManager
+import io.trtc.tuikit.atomicxcore.api.CompletionHandler
 import io.trtc.tuikit.atomicxcore.api.live.LiveInfo
 import io.trtc.tuikit.atomicxcore.api.live.LiveInfoCompletionHandler
 import io.trtc.tuikit.atomicxcore.api.live.LiveListStore
+import io.trtc.tuikit.atomicxcore.api.live.StopLiveCompletionHandler
 import kotlinx.coroutines.async
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withContext
@@ -75,11 +79,11 @@ open class JoinController(override val ctx: IRoomContext, serialHandler: Handler
         Log.d(TAG_ROOM_FLOW, "joinRoom: $req")
         return withContext(this.coroutineContext) {
             logRoomTime("joinRoom enter")
-            val joinedRoomId = getJoinedRoomId()
-            if (joinedRoomId == req.roomId) {
-                Log.i(TAG_ROOM_FLOW, "joinRoom, already joined, joinedRoomId:${joinedRoomId}")
-                return@withContext Rlt.Success(Any())
-            }
+//            val joinedRoomId = getJoinedRoomId()
+//            if (joinedRoomId == req.roomId) {
+//                Log.i(TAG_ROOM_FLOW, "joinRoom, already joined, joinedRoomId:${joinedRoomId}")
+//                return@withContext Rlt.Success(Any())
+//            }
 
             if (joiningRoomId == req.roomId) {
                 Log.i(TAG_ROOM_FLOW, "joinRoom, same room joining, joiningRoomId:${req.roomId}")
@@ -137,6 +141,96 @@ open class JoinController(override val ctx: IRoomContext, serialHandler: Handler
         }
     }
 
+    override suspend fun leaveRoom(
+        reason: LeaveRoomReason,
+        leaveUI: Boolean
+    ) {
+        val joinedRoomId = getJoinedRoomId() ?: return
+        withContext(this.coroutineContext) {
+            leaveLastRoom(joinedRoomId, reason, leaveUI)
+        }
+    }
+
+
+    private suspend fun leaveLastRoom(roomId: String?, reason: LeaveRoomReason, leaveUI: Boolean) {
+        val joinedRoomId = roomId ?: return
+        val flowStateInfo = FlowStateInfo().apply {
+            this.roomId = joinedRoomId
+            this.reason = reason.reason
+            this.leaveUI = leaveUI
+        }
+        clearJoinedRoomInfo()
+        innerLeaveRoom(joinedRoomId)
+        changeRoomState(RoomState.ROOM_LEAVE, flowStateInfo)
+    }
+
+    private fun clearJoinedRoomInfo() {
+        joinedRoomInfo = null
+    }
+
+    private suspend fun innerLeaveRoom(roomId: String): Rlt<Any> {
+        return suspendCancellableCoroutine { coroutine ->
+            LiveListStore.shared().leaveLive(object : CompletionHandler {
+                override fun onFailure(code: Int, desc: String) {
+                    if (coroutine.isActive) {
+                        coroutine.resume(
+                            Rlt.Failed(IError(serverCode = code, msg = desc))
+                        )
+                    }
+                }
+
+                override fun onSuccess() {
+                    if (coroutine.isActive) {
+                        coroutine.resume(
+                            Rlt.Success(Any())
+                        )
+                    }
+                }
+            })
+        }
+    }
+
+    override suspend fun tryEndRoom(reason: LeaveRoomReason, leaveUI: Boolean) {
+        val joinedRoomId = getJoinedRoomId() ?: return
+        withContext(this.coroutineContext) {
+            endLastRoom(joinedRoomId, reason, leaveUI)
+        }
+    }
+
+    private suspend fun endLastRoom(roomId: String?, reason: LeaveRoomReason, leaveUI: Boolean) {
+        val joinedRoomId = roomId ?: return
+        val flowStateInfo = FlowStateInfo().apply {
+            this.roomId = joinedRoomId
+            this.reason = reason.reason
+            this.leaveUI = leaveUI
+        }
+        clearJoinedRoomInfo()
+        innerEndRoom(joinedRoomId)
+        changeRoomState(RoomState.ROOM_LEAVE, flowStateInfo)
+    }
+
+    private suspend fun innerEndRoom(roomId: String): Rlt<Any> {
+        return suspendCancellableCoroutine { coroutine ->
+            LiveListStore.shared().endLive(object : StopLiveCompletionHandler {
+                override fun onFailure(code: Int, desc: String) {
+                    if (coroutine.isActive) {
+                        coroutine.resume(
+                            Rlt.Failed(IError(serverCode = code, msg = desc))
+                        )
+                    }
+                }
+
+                override fun onSuccess(statisticsData: TUILiveListManager.LiveStatisticsData) {
+                    if (coroutine.isActive) {
+                        coroutine.resume(
+                            Rlt.Success(Any())
+                        )
+                    }
+                }
+            })
+        }
+    }
+
 
     private fun changeRoomState(toState: RoomState, flowStateInfo: FlowStateInfo) {
         Log.i(

+ 58 - 0
module/room/src/main/java/com/adealink/weparty/room/sdk/controller/impl/SeatController.kt

@@ -24,6 +24,7 @@ import com.adealink.weparty.room.sdk.listener.ISeatListener
 import com.adealink.weparty.room.sdk.service.roomService
 import io.trtc.tuikit.atomicxcore.api.CompletionHandler
 import io.trtc.tuikit.atomicxcore.api.device.DeviceStore
+import io.trtc.tuikit.atomicxcore.api.live.DeviceControlPolicy
 import io.trtc.tuikit.atomicxcore.api.live.LiveListStore
 import io.trtc.tuikit.atomicxcore.api.live.LiveSeatStore
 import io.trtc.tuikit.atomicxcore.api.live.SeatInfo
@@ -49,12 +50,17 @@ class SeatController(override val ctx: IRoomContext, serialHandler: Handler) :
     private var micSeats: MutableMap<Int, SeatInfo> = hashMapOf() //key:mic index
 
     override fun onRoomIn(flowStateInfo: FlowStateInfo) {
+        Log.d(TAG_ROOM_SEAT, "onRoomIn, ${flowStateInfo.roomId}")
         liveSeatRoomId = flowStateInfo.roomId
         liveSeatStore = LiveSeatStore.create(flowStateInfo.roomId)
         observeSeatList()
     }
 
     override fun onRoomLeaved(flowStateInfo: FlowStateInfo) {
+        Log.d(
+            TAG_ROOM_SEAT,
+            "onRoomLeaved, ${flowStateInfo.roomId}, reason:${flowStateInfo.reason}"
+        )
         liveSeatStore?.liveSeatState?.seatList
     }
 
@@ -203,6 +209,58 @@ class SeatController(override val ctx: IRoomContext, serialHandler: Handler) :
         })
     }
 
+    override fun micLock(micInex: Int, onSuccess: () -> Unit, onFail: (error: IError) -> Unit) {
+        liveSeatStore?.lockSeat(micInex, object : CompletionHandler {
+            override fun onFailure(code: Int, desc: String) {
+                onFail.invoke(IError(serverCode = code, msg = desc))
+            }
+
+            override fun onSuccess() {
+                onSuccess.invoke()
+            }
+
+        })
+    }
+
+    override fun micUnLock(micInex: Int, onSuccess: () -> Unit, onFail: (error: IError) -> Unit) {
+        liveSeatStore?.unlockSeat(micInex, object : CompletionHandler {
+            override fun onFailure(code: Int, desc: String) {
+                onFail.invoke(IError(serverCode = code, msg = desc))
+            }
+
+            override fun onSuccess() {
+                onSuccess.invoke()
+            }
+        })
+    }
+
+    override fun micMute(uid: String, onSuccess: () -> Unit, onFail: (error: IError) -> Unit) {
+        liveSeatStore?.closeRemoteMicrophone(uid, object : CompletionHandler {
+            override fun onFailure(code: Int, desc: String) {
+                onFail.invoke(IError(serverCode = code, msg = desc))
+            }
+
+            override fun onSuccess() {
+                onSuccess.invoke()
+            }
+        })
+    }
+
+    override fun micUnMute(uid: String, onSuccess: () -> Unit, onFail: (error: IError) -> Unit) {
+        liveSeatStore?.openRemoteMicrophone(
+            uid,
+            DeviceControlPolicy.UNLOCK_ONLY,
+            object : CompletionHandler {
+                override fun onFailure(code: Int, desc: String) {
+                    onFail.invoke(IError(serverCode = code, msg = desc))
+                }
+
+                override fun onSuccess() {
+                    onSuccess.invoke()
+                }
+            })
+    }
+
     override suspend fun getMicSeatsInfo() {
         val roomId = liveSeatRoomId ?: return
         val seatInfoList = liveSeatStore?.liveSeatState?.seatList?.value ?: emptyList()

+ 29 - 0
module/room/src/main/java/com/adealink/weparty/room/setting/RoomSettingDialog.kt

@@ -0,0 +1,29 @@
+package com.adealink.weparty.room.setting
+
+import com.adealink.frame.coroutine.dispatcher.Dispatcher
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.util.onClick
+import com.adealink.weparty.commonui.widget.BottomDialogFragment
+import com.adealink.weparty.room.R
+import com.adealink.weparty.room.databinding.DialogRoomSettingBinding
+import com.adealink.weparty.room.sdk.service.roomService
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+class RoomSettingDialog : BottomDialogFragment(R.layout.dialog_room_setting) {
+
+    private val binding by viewBinding(DialogRoomSettingBinding::bind)
+
+    override fun initViews() {
+        super.initViews()
+        binding.btnSetting.onClick {
+
+        }
+        binding.btnClose.onClick {
+            CoroutineScope(Dispatcher.UI).launch {
+                roomService.joinController.tryEndRoom()
+            }
+        }
+    }
+
+}

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


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


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

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

+ 69 - 0
module/room/src/main/res/layout/dialog_chat_message_input.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"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@color/white"
+    android:paddingHorizontal="12dp"
+    android:paddingTop="8dp"
+    android:paddingBottom="10dp">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cl_input"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="10dp"
+        android:background="@drawable/common_input_single_edit_bg"
+        android:minHeight="40dp"
+        android:paddingHorizontal="10dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/btn_send"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <androidx.appcompat.widget.AppCompatImageView
+            android:id="@+id/btn_emoji"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:layout_marginBottom="8dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:srcCompat="@drawable/common_emoji_ic" />
+
+        <androidx.appcompat.widget.AppCompatEditText
+            android:id="@+id/et_input_message"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="6dp"
+            android:background="@null"
+            android:gravity="start|center_vertical"
+            android:hint="@string/room_chat_say_hi"
+            android:includeFontPadding="false"
+            android:maxLines="4"
+            android:paddingVertical="8dp"
+            android:textColor="@color/color_FF1D2129"
+            android:textColorHint="@color/color_FFC9CDD4"
+            android:textSize="14sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0"
+            app:layout_constraintStart_toEndOf="@id/btn_emoji"
+            app:layout_constraintTop_toTopOf="parent" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <com.adealink.weparty.commonui.widget.CommonButton
+        android:id="@+id/btn_send"
+        android:layout_width="wrap_content"
+        android:layout_height="32dp"
+        android:minWidth="68dp"
+        android:paddingHorizontal="12dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:text="@string/common_send"
+        app:textColor="@color/white"
+        app:textSize="14sp" />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 61 - 0
module/room/src/main/res/layout/dialog_room_invite_mic.xml

@@ -0,0 +1,61 @@
+<?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/room_bottom_dialog_bg"
+    android:paddingBottom="24dp">
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_title"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/common_title_height"
+        android:gravity="start|center_vertical"
+        android:includeFontPadding="false"
+        android:paddingHorizontal="16dp"
+        android:textColor="@color/white"
+        android:textSize="14sp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="10人申请上麦" />
+
+    <com.scwang.smart.refresh.layout.SmartRefreshLayout
+        android:id="@+id/v_refresh"
+        android:layout_width="match_parent"
+        android:layout_height="400dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_title">
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/rv_list"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+
+    </com.scwang.smart.refresh.layout.SmartRefreshLayout>
+
+    <View
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="@drawable/room_bottom_mask_bg"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="@id/btn_cancel" />
+
+    <com.adealink.weparty.commonui.widget.CommonButton
+        android:id="@+id/btn_cancel"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/common_button_height"
+        android:layout_marginHorizontal="20dp"
+        app:common_button_type="cancel"
+        app:layout_constraintBottom_toBottomOf="@id/v_refresh"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:text="@string/room_apply_mic_cancel"
+        app:textSize="16sp" />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 91 - 0
module/room/src/main/res/layout/dialog_room_setting.xml

@@ -0,0 +1,91 @@
+<?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"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@drawable/room_bottom_dialog_bg"
+    android:paddingBottom="24dp">
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_title"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/common_title_height"
+        android:fontFamily="@font/poppins_semibold"
+        android:gravity="start|center_vertical"
+        android:includeFontPadding="false"
+        android:paddingHorizontal="16dp"
+        android:text="@string/room_setting_title"
+        android:textColor="@color/white"
+        android:textSize="16sp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/btn_setting"
+        android:layout_width="70dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="10dp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_title">
+
+        <androidx.appcompat.widget.AppCompatImageView
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:background="@drawable/room_setting_item_bg"
+            android:padding="10dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/room_setting_setting_ic" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="55dp"
+            android:gravity="center"
+            android:includeFontPadding="false"
+            android:text="@string/room_setting_setting"
+            android:textColor="@color/color_FFC9CDD4"
+            android:textSize="12sp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/btn_close"
+        android:layout_width="70dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="10dp"
+        app:layout_constraintStart_toEndOf="@id/btn_setting"
+        app:layout_constraintTop_toBottomOf="@id/tv_title">
+
+        <androidx.appcompat.widget.AppCompatImageView
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:background="@drawable/room_setting_item_bg"
+            android:padding="10dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/room_setting_close_ic" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="55dp"
+            android:gravity="center"
+            android:includeFontPadding="false"
+            android:text="@string/room_setting_close"
+            android:textColor="@color/color_FFC9CDD4"
+            android:textSize="12sp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

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

@@ -41,6 +41,7 @@
     <item name="id_mic_operate_on" type="id" />
     <item name="id_mic_operate_off" type="id" />
     <item name="id_mic_operate_lock" type="id" />
+    <item name="id_mic_operate_unlock" type="id" />
     <item name="id_mic_operate_mute" type="id" />
     <item name="id_mic_operate_un_mute" type="id" />
     <item name="id_mic_operate" type="id" />

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

@@ -34,6 +34,7 @@
     <string name="room_mic_operate_invite">邀请用户上麦</string>
     <string name="room_mic_operate_member_info">查看资料</string>
     <string name="room_mic_operate_lock">关闭麦位</string>
+    <string name="room_mic_operate_unlock">开启麦位</string>
     <string name="room_mic_operate_mute">禁麦</string>
     <string name="room_mic_operate_un_mute">解除禁麦</string>
     <string name="room_mic_operate_on">上麦</string>
@@ -47,4 +48,7 @@
     <string name="room_how_to_order_3_desc">8 Teman Main Perkenalan satu per satu</string>
     <string name="room_how_to_order_4_title">Langkah 4:Pesan</string>
     <string name="room_how_to_order_4_desc">Pilih 1 teman main lalu pesan.</string>
+    <string name="room_setting_title">房间设置</string>
+    <string name="room_setting_setting">Setting</string>
+    <string name="room_setting_close">Close</string>
 </resources>