|
|
@@ -0,0 +1,277 @@
|
|
|
+package com.adealink.weparty.im.session.comp
|
|
|
+
|
|
|
+import android.annotation.SuppressLint
|
|
|
+import androidx.lifecycle.LifecycleOwner
|
|
|
+import com.adealink.frame.log.Log
|
|
|
+import com.adealink.frame.mvvm.view.ViewComponent
|
|
|
+import com.adealink.frame.mvvm.viewmodel.activityViewModels
|
|
|
+import com.adealink.frame.util.onClick
|
|
|
+import com.adealink.frame.util.runOnUiThread
|
|
|
+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.InputAction
|
|
|
+import com.adealink.weparty.im.session.comp.input.InputMachineTransaction
|
|
|
+import com.adealink.weparty.im.session.comp.input.InputState
|
|
|
+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.AudioRecorder
|
|
|
+import com.tencent.qcloud.tuikit.tuichat.component.audio.AudioRecorder.AudioRecorderCallback
|
|
|
+import com.tencent.qcloud.tuikit.tuichat.presenter.ChatPresenter
|
|
|
+import com.tencent.qcloud.tuikit.tuichat.util.ChatMessageBuilder
|
|
|
+import java.util.Timer
|
|
|
+import java.util.TimerTask
|
|
|
+
|
|
|
+@SuppressLint("SetTextI18n")
|
|
|
+class SessionBottomAudioComp(
|
|
|
+ lifecycleOwner: LifecycleOwner,
|
|
|
+ val voiceBar: LayoutSessionBottomVoiceBarBinding,
|
|
|
+ val chatInfo: ChatInfo? = null,
|
|
|
+ val presenter: ChatPresenter? = null
|
|
|
+) : ViewComponent(lifecycleOwner) {
|
|
|
+
|
|
|
+ private var mTimer: Timer? = null
|
|
|
+ private var times = 0
|
|
|
+ private val inputViewModel by activityViewModels<SessionInputViewModel>()
|
|
|
+
|
|
|
+ private var isPause = false
|
|
|
+ override fun onCreate() {
|
|
|
+ super.onCreate()
|
|
|
+ initView()
|
|
|
+ observeViewModel()
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private fun initView() {
|
|
|
+ voiceBar.btnRemove.onClick {
|
|
|
+ clickDelete()
|
|
|
+ }
|
|
|
+ voiceBar.btnPause.onClick {
|
|
|
+ clickPause()
|
|
|
+ }
|
|
|
+ voiceBar.btnSend.onClick {
|
|
|
+ clickSend()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun observeViewModel() {
|
|
|
+ /**
|
|
|
+ * transition to [InputState.STATE_AUDIO_INPUT]
|
|
|
+ */
|
|
|
+ inputViewModel.registerTransaction(
|
|
|
+ InputMachineTransaction(
|
|
|
+ currentState = InputState.STATE_SOFT_INPUT,
|
|
|
+ action = InputAction.CLICK_AUDIO,
|
|
|
+ nextState = InputState.STATE_AUDIO_INPUT
|
|
|
+ ).also {
|
|
|
+ it.event.observeWithoutCache(viewLifecycleOwner) {
|
|
|
+ startAudioRecord()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ inputViewModel.registerTransaction(
|
|
|
+ InputMachineTransaction(
|
|
|
+ currentState = InputState.STATE_EMOJI_INPUT,
|
|
|
+ action = InputAction.CLICK_AUDIO,
|
|
|
+ nextState = InputState.STATE_AUDIO_INPUT
|
|
|
+ ).also {
|
|
|
+ it.event.observeWithoutCache(viewLifecycleOwner) {
|
|
|
+ startAudioRecord()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ inputViewModel.registerTransaction(
|
|
|
+ InputMachineTransaction(
|
|
|
+ currentState = InputState.STATE_NONE,
|
|
|
+ action = InputAction.CLICK_AUDIO,
|
|
|
+ nextState = InputState.STATE_AUDIO_INPUT
|
|
|
+ ).also {
|
|
|
+ it.event.observeWithoutCache(viewLifecycleOwner) {
|
|
|
+ startAudioRecord()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ /**
|
|
|
+ * transition from [InputState.STATE_AUDIO_INPUT]
|
|
|
+ */
|
|
|
+ inputViewModel.inputStateLD.observe(viewLifecycleOwner) { state ->
|
|
|
+ if (state.lastState == InputState.STATE_AUDIO_INPUT
|
|
|
+ && state.currentState != InputState.STATE_AUDIO_INPUT
|
|
|
+ ) {
|
|
|
+ stopAudioRecord()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun clickDelete() {
|
|
|
+ inputViewModel.execute(InputAction.CANCEL_AUDIO)
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun clickPause() {
|
|
|
+ isPause = !isPause
|
|
|
+ if (isPause) {
|
|
|
+ voiceBar.btnPause.setImageResource(R.drawable.im_session_voice_record_resume_ic)
|
|
|
+ } else {
|
|
|
+ voiceBar.btnPause.setImageResource(R.drawable.im_session_voice_record_pause_ic)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun clickSend() {
|
|
|
+ inputViewModel.execute(InputAction.CANCEL_AUDIO)
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun startAudioRecord() {
|
|
|
+ Log.d(TAG_IM_AUDIO, "startAudioRecord")
|
|
|
+ AudioRecorder.startRecord(object : AudioRecorderCallback {
|
|
|
+ override fun onStarted() {
|
|
|
+ Log.d(TAG_IM_AUDIO, "startAudioRecord, onStarted")
|
|
|
+ showVoiceLayout()
|
|
|
+ if (mTimer == null) {
|
|
|
+ mTimer = Timer()
|
|
|
+ }
|
|
|
+ mTimer?.schedule(object : TimerTask() {
|
|
|
+ override fun run() {
|
|
|
+ runOnUiThread {
|
|
|
+ times++
|
|
|
+ voiceBar.tvRecordTime.text = formatMiss(times)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, 0, 1000)
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onFinished(outputPath: String?) {
|
|
|
+ Log.d(TAG_IM_AUDIO, "startAudioRecord, onFinished:$outputPath")
|
|
|
+ val duration = AudioRecorder.getDuration(outputPath)
|
|
|
+ if (duration < 1000) {
|
|
|
+ showToast(R.string.im_audio_say_time_short)
|
|
|
+ return
|
|
|
+ }
|
|
|
+// if (mVoiceWaveView != null) {
|
|
|
+// mVoiceWaveView.stop()
|
|
|
+// }
|
|
|
+ sendAudioMessage(outputPath, duration)
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onFailed(errorCode: Int, errorMessage: String?) {
|
|
|
+ Log.d(
|
|
|
+ TAG_IM_AUDIO,
|
|
|
+ "startAudioRecord, onFailed, errorCode:$errorCode, errorMessage:$errorMessage"
|
|
|
+ )
|
|
|
+ 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)
|
|
|
+ )
|
|
|
+ } else {
|
|
|
+ ToastUtil.toastLongMessage(
|
|
|
+ TUIChatService.getAppContext().getString(R.string.im_record_audio_failed)
|
|
|
+ )
|
|
|
+ }
|
|
|
+ hideVoiceLayout()
|
|
|
+ Log.e(
|
|
|
+ TAG_IM_AUDIO,
|
|
|
+ "record audio failed, errorCode $errorCode, errorMessage $errorMessage"
|
|
|
+ )
|
|
|
+// if (mVoiceWaveView != null) {
|
|
|
+// mVoiceWaveView.stop()
|
|
|
+// }
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onCanceled() {
|
|
|
+ Log.d(TAG_IM_AUDIO, "startAudioRecord, onCanceled")
|
|
|
+// if (mChatInputHandler != null) {
|
|
|
+// mChatInputHandler.onRecordStatusChanged(InputView.ChatInputHandler.RECORD_CANCEL)
|
|
|
+// }
|
|
|
+// if (mVoiceWaveView != null) {
|
|
|
+// mVoiceWaveView.stop()
|
|
|
+// }
|
|
|
+ hideVoiceLayout()
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onVoiceDb(db: Double) {
|
|
|
+ //Log.d(TAG_IM_AUDIO, "startAudioRecord, onVoiceDb: $db")
|
|
|
+ //var db = db
|
|
|
+ // TODO: 声音分贝
|
|
|
+// if (mSendAudioButtonLayout.getVisibility() == View.VISIBLE) {
|
|
|
+// if (db == 0.0) {
|
|
|
+// db = 2.0
|
|
|
+// }
|
|
|
+// mVoiceWaveView.addBody(db.toInt())
|
|
|
+// mVoiceWaveView.start()
|
|
|
+// }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 stopAudioRecord() {
|
|
|
+ Log.d(TAG_IM_AUDIO, "stopAudioRecord")
|
|
|
+ AudioRecorder.stopRecord()
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun resetVoiceView() {
|
|
|
+ Log.d(TAG_IM_AUDIO, "resetVoiceView")
|
|
|
+// initVoiceWaveView()
|
|
|
+ mTimer?.cancel()
|
|
|
+ mTimer = null
|
|
|
+ voiceBar.tvRecordTime.text = "0:00"
|
|
|
+ times = 0
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun sendAudioMessage(outputPath: String?, duration: Int) {
|
|
|
+ Log.d(TAG_IM_AUDIO, "sendAudioMessage, outputPath:$outputPath, duration:$duration")
|
|
|
+ inputViewModel.sendMessage(ChatMessageBuilder.buildAudioMessage(outputPath, duration))
|
|
|
+ }
|
|
|
+
|
|
|
+ // TODO: 手指操作
|
|
|
+// private fun readyToCancelRecord() {
|
|
|
+// if (mChatInputHandler != null) {
|
|
|
+// mChatInputHandler.onRecordStatusChanged(InputView.ChatInputHandler.RECORD_READY_TO_CANCEL)
|
|
|
+// }
|
|
|
+// voiceDeleteImage.setBackgroundResource(R.drawable.minimalist_delete_start_icon)
|
|
|
+// voiceDeleteImage.getBackground().setAutoMirrored(true)
|
|
|
+// mSendAudioButton.setBackground(getResources().getDrawable(R.drawable.minimalist_corner_bg_red))
|
|
|
+// }
|
|
|
+//
|
|
|
+// private fun continueRecord() {
|
|
|
+// if (mChatInputHandler != null) {
|
|
|
+// mChatInputHandler.onRecordStatusChanged(InputView.ChatInputHandler.RECORD_CONTINUE)
|
|
|
+// }
|
|
|
+// voiceDeleteImage.setBackgroundResource(R.drawable.minimalist_delete_icon)
|
|
|
+// mSendAudioButton.setBackground(getResources().getDrawable(R.drawable.minimalist_corner_bg_blue))
|
|
|
+// }
|
|
|
+
|
|
|
+ private fun formatMiss(miss: Int): String {
|
|
|
+ // String hh = miss / 3600 > 9 ? miss / 3600 + "" : "0" + miss / 3600;
|
|
|
+ val mm =
|
|
|
+ if ((miss % 3600) / 60 > 9) ((miss % 3600) / 60).toString() + "" else "0" + (miss % 3600) / 60
|
|
|
+ val ss =
|
|
|
+ if ((miss % 3600) % 60 > 9) ((miss % 3600) % 60).toString() + "" else "0" + (miss % 3600) % 60
|
|
|
+ return "$mm:$ss"
|
|
|
+ }
|
|
|
+}
|