DoggyZhang 3 сар өмнө
parent
commit
00b9933981

+ 64 - 49
module/im/src/main/java/com/adealink/weparty/im/session/comp/SessionBottomAudioComp.kt

@@ -12,13 +12,13 @@ import com.adealink.weparty.commonui.ext.dpf
 import com.adealink.weparty.commonui.toast.util.showToast
 import com.adealink.weparty.im.R
 import com.adealink.weparty.im.databinding.LayoutSessionBottomVoiceBarBinding
+import com.adealink.weparty.im.session.comp.input.AudioInputStatus
 import com.adealink.weparty.im.session.comp.input.InputAction
 import com.adealink.weparty.im.session.comp.input.InputState
+import com.adealink.weparty.im.session.comp.viewmodel.SessionAudioViewModel
 import com.adealink.weparty.im.session.comp.viewmodel.SessionInputViewModel
 import com.adealink.weparty.module.im.data.TAG_IM_AUDIO
 import com.tencent.qcloud.tuicore.TUIConstants
-import com.tencent.qcloud.tuicore.util.ToastUtil
-import com.tencent.qcloud.tuikit.tuichat.TUIChatService
 import com.tencent.qcloud.tuikit.tuichat.bean.ChatInfo
 import com.tencent.qcloud.tuikit.tuichat.component.audio.AudioPlayer
 import com.tencent.qcloud.tuikit.tuichat.component.audio.AudioRecorder
@@ -46,6 +46,7 @@ class SessionBottomAudioComp(
     private var mTimer: Timer? = null
     private var times = 0
     private val inputViewModel by activityViewModels<SessionInputViewModel>()
+    private val audioViewModel by activityViewModels<SessionAudioViewModel>()
 
     private var isPause = false
     override fun onCreate() {
@@ -56,8 +57,8 @@ class SessionBottomAudioComp(
 
 
     private fun initView() {
-        voiceBar.btnRemove.onClick {
-            clickDelete()
+        voiceBar.btnCancel.onClick {
+            clickCancel()
         }
         voiceBar.btnPause.onClick {
             clickPause()
@@ -92,6 +93,23 @@ class SessionBottomAudioComp(
         ).event.observeWithoutCache(viewLifecycleOwner) {
             startAudioRecord()
         }
+
+
+        inputViewModel.registerTransaction(
+            currentState = InputState.STATE_AUDIO_INPUT,
+            action = InputAction.CLICK_AUDIO,
+            nextState = InputState.STATE_NONE
+        ).event.observeWithoutCache(viewLifecycleOwner) {
+            stopAudioRecord()
+        }
+        inputViewModel.registerTransaction(
+            currentState = InputState.STATE_AUDIO_INPUT,
+            action = InputAction.CANCEL_AUDIO,
+            nextState = InputState.STATE_NONE
+        ).event.observeWithoutCache(viewLifecycleOwner) {
+            cancelAudioRecord()
+        }
+
         /**
          * transition from [InputState.STATE_AUDIO_INPUT]
          */
@@ -102,27 +120,41 @@ class SessionBottomAudioComp(
                 stopAudioRecord()
             }
         }
+
+
+        audioViewModel.audioInputStatusLD.observeWithoutCache(viewLifecycleOwner) { status ->
+//            when (status) {
+//                AudioInputStatus.RECORD_CONTINUE -> TODO()
+//                AudioInputStatus.RECORD_FAILED -> TODO()
+//                AudioInputStatus.RECORD_START -> TODO()
+//                AudioInputStatus.RECORD_STOP -> TODO()
+//                AudioInputStatus.RECORD_READY_TO_CANCEL -> TODO()
+//                AudioInputStatus.RECORD_TOO_SHORT -> {
+//
+//                }
+//                AudioInputStatus.RECORD_CANCEL -> TODO()
+//            }
+        }
     }
 
-    private fun clickDelete() {
+    private fun clickCancel() {
         inputViewModel.execute(InputAction.CANCEL_AUDIO)
-        stopAudioRecord()
     }
 
     private fun clickPause() {
         if (isPause) {
             isPause = false
-            voiceBar.btnPause.setImageResource(R.drawable.im_session_voice_record_resume_ic)
+            voiceBar.btnPause.setImageResource(R.drawable.im_session_voice_record_pause_ic)
             resumeAudioRecord()
         } else {
             isPause = true
-            voiceBar.btnPause.setImageResource(R.drawable.im_session_voice_record_pause_ic)
+            voiceBar.btnPause.setImageResource(R.drawable.im_session_voice_record_resume_ic)
             pauseAudioRecord()
         }
     }
 
     private fun clickSend() {
-        inputViewModel.execute(InputAction.CANCEL_AUDIO)
+        inputViewModel.execute(InputAction.CLICK_AUDIO)
     }
 
     private fun startAudioRecord() {
@@ -133,6 +165,7 @@ class SessionBottomAudioComp(
         AudioRecorder.startRecord(object : AudioRecorderCallback {
             override fun onStarted() {
                 Log.d(TAG_IM_AUDIO, "startAudioRecord, onStarted")
+                audioViewModel.changeStatus(AudioInputStatus.RECORD_START)
                 showVoiceLayout()
                 if (mTimer == null) {
                     mTimer = Timer()
@@ -151,11 +184,14 @@ class SessionBottomAudioComp(
 
             override fun onFinished(outputPath: String?) {
                 Log.d(TAG_IM_AUDIO, "startAudioRecord, onFinished:$outputPath")
+                hideVoiceLayout()
                 val duration = AudioRecorder.getDuration(outputPath)
                 if (duration < 1000) {
                     showToast(R.string.im_audio_say_time_short)
+                    audioViewModel.changeStatus(AudioInputStatus.RECORD_TOO_SHORT)
                     return
                 }
+                audioViewModel.changeStatus(AudioInputStatus.RECORD_STOP)
                 voiceBar.vVoiceWave.stop()
                 sendAudioMessage(outputPath, duration)
             }
@@ -165,34 +201,32 @@ class SessionBottomAudioComp(
                     TAG_IM_AUDIO,
                     "startAudioRecord, onFailed, errorCode:$errorCode, errorMessage:$errorMessage"
                 )
+                hideVoiceLayout()
                 if (errorCode == AudioRecorder.ERROR_CODE_MIC_IS_BEING_USED || errorCode == TUIConstants.TUICalling.ERROR_STATUS_IN_CALL) {
-                    ToastUtil.toastLongMessage(
-                        TUIChatService.getAppContext()
-                            .getString(R.string.im_mic_is_being_used_cant_record)
-                    )
+                    showToast(R.string.im_mic_is_being_used_cant_record)
                 } else {
-                    ToastUtil.toastLongMessage(
-                        TUIChatService.getAppContext().getString(R.string.im_record_audio_failed)
-                    )
+                    showToast(R.string.im_record_audio_failed)
                 }
-                hideVoiceLayout()
                 Log.e(
                     TAG_IM_AUDIO,
                     "record audio failed, errorCode $errorCode, errorMessage $errorMessage"
                 )
+                audioViewModel.changeStatus(AudioInputStatus.RECORD_FAILED)
                 voiceBar.vVoiceWave.stop()
+                inputViewModel.execute(InputAction.EMPTY_CLICKED)
             }
 
             override fun onCanceled() {
                 Log.d(TAG_IM_AUDIO, "startAudioRecord, onCanceled")
-//                if (mChatInputHandler != null) {
-//                    mChatInputHandler.onRecordStatusChanged(InputView.ChatInputHandler.RECORD_CANCEL)
-//                }
-                voiceBar.vVoiceWave.stop()
                 hideVoiceLayout()
+                audioViewModel.changeStatus(AudioInputStatus.RECORD_CANCEL)
+                voiceBar.vVoiceWave.stop()
             }
 
             override fun onVoiceDb(db: Double) {
+                if (isPause) {
+                    return
+                }
                 Log.d(TAG_IM_AUDIO, "startAudioRecord, onVoiceDb: $db")
                 voiceBar.vVoiceWave.updateDecibel(
                     max(
@@ -205,36 +239,25 @@ class SessionBottomAudioComp(
     }
 
     private fun pauseAudioRecord() {
+        Log.d(TAG_IM_AUDIO, "pauseAudioRecord")
         voiceBar.vVoiceWave.pause()
-        AudioRecorder.stopRecord()
+        AudioRecorder.pauseRecord()
     }
 
     private fun resumeAudioRecord() {
+        Log.d(TAG_IM_AUDIO, "resumeAudioRecord")
         voiceBar.vVoiceWave.resume()
-        mTimer?.cancel()
+        AudioRecorder.resumeRecord()
     }
 
-
     private fun hideVoiceLayout() {
         Log.d(TAG_IM_AUDIO, "hideVoiceLayout")
         resetVoiceView()
-//        voiceBtn.setVisibility(View.VISIBLE)
-//        mSendAudioButtonLayout.setVisibility(View.GONE)
-//        showInputMoreButton()
-//        showTextInputLayout()
-//        showImageButton()
-//        hideVoiceDeleteImage()
     }
 
     private fun showVoiceLayout() {
         Log.d(TAG_IM_AUDIO, "showVoiceLayout")
         initVoiceWaveView()
-//        mSendAudioButtonLayout.setVisibility(View.VISIBLE)
-//        voiceBtn.setVisibility(View.GONE)
-//        hideInputMoreButton()
-//        hideTextInputLayout()
-//        hideImageButton()
-//        showVoiceDeleteImage()
     }
 
     private fun initVoiceWaveView() {
@@ -246,18 +269,6 @@ class SessionBottomAudioComp(
         voiceBar.vVoiceWave.barSpacing = 2.dpf()
         voiceBar.vVoiceWave.showGlow = false
         voiceBar.vVoiceWave.clear()
-//        voiceBar.vVoiceWave.addHeader(MIN_VOICE_DB)
-//        for (i in 0..100) {
-//            voiceBar.vVoiceWave.addBody(MIN_VOICE_DB)
-//        }
-//        voiceBar.vVoiceWave.addFooter(MIN_VOICE_DB)
-//        voiceBar.vVoiceWave.addHeader(0)
-//        for (i in 0..100) {
-//            voiceBar.vVoiceWave.addBody(0)
-//        }
-//        voiceBar.vVoiceWave.addFooter(0)
-//        voiceDeleteImage.setBackgroundResource(R.drawable.minimalist_delete_icon)
-//        mSendAudioButton.setBackground(getResources().getDrawable(R.drawable.minimalist_corner_bg_blue))
     }
 
     private fun stopAudioRecord() {
@@ -267,9 +278,13 @@ class SessionBottomAudioComp(
         voiceBar.vVoiceWave.clear()
     }
 
+    private fun cancelAudioRecord() {
+        Log.d(TAG_IM_AUDIO, "cancelAudioRecord")
+        AudioRecorder.cancelRecord()
+    }
+
     private fun resetVoiceView() {
         Log.d(TAG_IM_AUDIO, "resetVoiceView")
-//        initVoiceWaveView()
         mTimer?.cancel()
         mTimer = null
         voiceBar.tvRecordTime.text = "0:00"

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

@@ -263,7 +263,6 @@ class SessionBottomInputComp(
         sessionInputViewModel.inputStateLD
         sessionInputViewModel.inputStateLD.observe(viewLifecycleOwner) { state ->
             when (state.currentState) {
-                InputState.STATE_NONE,
                 InputState.STATE_SOFT_INPUT,
                 InputState.STATE_EMOJI_INPUT,
                 InputState.STATE_IMAGE_INPUT -> {
@@ -273,6 +272,7 @@ class SessionBottomInputComp(
                     }
                 }
 
+                InputState.STATE_NONE,
                 InputState.STATE_AUDIO_INPUT -> {
                     inputBar.btnVoice.setImageResource(R.drawable.im_session_voice_ic)
                     inputBar.btnVoice.onClick {

+ 11 - 0
module/im/src/main/java/com/adealink/weparty/im/session/comp/input/SessionInputData.kt

@@ -0,0 +1,11 @@
+package com.adealink.weparty.im.session.comp.input
+
+enum class AudioInputStatus {
+    RECORD_START, //录制开始
+    RECORD_STOP,  //停止录制
+    RECORD_CANCEL, //取消录制
+    RECORD_TOO_SHORT, //录制内容太短
+    RECORD_FAILED, //录制失败
+    RECORD_CONTINUE, //录制继续(手指离开取消区域)
+    RECORD_READY_TO_CANCEL; //录制准备取消(手指拖动到取消位置)
+}

+ 42 - 13
module/im/src/main/java/com/adealink/weparty/im/session/comp/input/SessionInputMachine.kt

@@ -19,15 +19,18 @@ enum class InputAction {
 
 }
 
-//输入状态
-enum class InputState {
-    STATE_NONE,
+/**
+ * 输入状态
+ * @param priority 优先级,不能同一个操作导致多个相同优先级的事件
+ */
+enum class InputState(val priority: Int) {
+    STATE_NONE(0),
 
     //    STATE_MORE_INPUT,
-    STATE_SOFT_INPUT,
-    STATE_EMOJI_INPUT,
-    STATE_AUDIO_INPUT,
-    STATE_IMAGE_INPUT,
+    STATE_SOFT_INPUT(1),
+    STATE_EMOJI_INPUT(1),
+    STATE_AUDIO_INPUT(1),
+    STATE_IMAGE_INPUT(2),
 }
 
 class SessionInputMachine(
@@ -60,7 +63,8 @@ class SessionInputMachine(
     }
 
     fun execute(action: InputAction) {
-        var nextState: InputState? = null
+        val nextStates = mutableSetOf<InputState>()
+        val pendingTransactions = mutableListOf<InputMachineTransaction>()
         for (transaction in transactionList) {
             if (transaction.currentState == currentState && transaction.action == action) {
                 Log.d(
@@ -68,17 +72,42 @@ class SessionInputMachine(
                     "SessionInputMachine, action:$action, $currentState => ${transaction.nextState}"
                 )
                 transaction.runEvent()
-                if (nextState == null) {
-                    nextState = transaction.nextState
-                } else if (nextState != transaction.nextState) {
-                    throw IllegalStateException("InputMachineTransaction define error, action:$action so marny nextState($nextState, ${transaction.nextState})")
-                }
+                pendingTransactions.add(transaction)
+                nextStates.add(transaction.nextState)
             }
         }
 
+        var nextState: InputState? = null
+        for (state in nextStates) {
+            if (nextState == null) {
+                nextState = state
+            } else if (nextState.priority < state.priority) {
+                nextState = state
+            } else if (nextState != state
+                && nextState.priority > 0
+                && nextState.priority == state.priority
+            ) {
+                throw IllegalStateException(
+                    "InputMachineTransaction define error, action(${action}) => nextState(${
+                        nextStates.joinToString(
+                            separator = ","
+                        )
+                    })"
+                )
+            }
+        }
         if (nextState != null) {
             notifyStateChanged(nextState)
+        } else {
+            Log.w(
+                TAG_IM_INPUT_FLOW,
+                "SessionInputMachine, action:$action can not find nextState"
+            )
         }
+//        for (transaction in pendingTransactions) {
+//            transaction.runEvent()
+//        }
+
     }
 
     private fun notifyStateChanged(nextState: InputState) {

+ 23 - 0
module/im/src/main/java/com/adealink/weparty/im/session/comp/viewmodel/SessionAudioViewModel.kt

@@ -0,0 +1,23 @@
+package com.adealink.weparty.im.session.comp.viewmodel
+
+import com.adealink.frame.log.Log
+import com.adealink.frame.mvvm.livedata.ExtMutableLiveData
+import com.adealink.frame.mvvm.viewmodel.BaseViewModel
+import com.adealink.weparty.im.session.comp.input.AudioInputStatus
+import com.adealink.weparty.module.im.data.TAG_IM_AUDIO
+
+class SessionAudioViewModel : BaseViewModel() {
+
+    val audioInputStatusLD = ExtMutableLiveData<AudioInputStatus>()
+
+    init {
+        //默认开始状态STOP
+        audioInputStatusLD.send(AudioInputStatus.RECORD_STOP)
+    }
+
+    fun changeStatus(status: AudioInputStatus) {
+        Log.d(TAG_IM_AUDIO, "changeStatus, ${audioInputStatusLD.value} => $status")
+        audioInputStatusLD.send(status)
+    }
+
+}

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

@@ -60,7 +60,7 @@
         app:layout_constraintTop_toBottomOf="@id/cl_voice_record">
 
         <androidx.appcompat.widget.AppCompatImageView
-            android:id="@+id/btn_remove"
+            android:id="@+id/btn_cancel"
             android:layout_width="40dp"
             android:layout_height="40dp"
             app:layout_constraintBottom_toBottomOf="@id/cl_op"