DoggyZhang 2 сар өмнө
parent
commit
0ca7e76c61

+ 11 - 0
app/src/main/java/com/adealink/weparty/util/TimeUtil.kt

@@ -166,4 +166,15 @@ fun getSessionTimeStr(thisTime: Long, lastTime: Long?): String? {
             formatTimeTo(thisTime, "dd/MM/yyyy")
         }
     }
+}
+
+
+
+fun formatMissTime(seconds: Int): String {
+    // String hh = miss / 3600 > 9 ? miss / 3600 + "" : "0" + miss / 3600;
+    val mm =
+        if ((seconds % 3600) / 60 > 9) ((seconds % 3600) / 60).toString() + "" else "0" + (seconds % 3600) / 60
+    val ss =
+        if ((seconds % 3600) % 60 > 9) ((seconds % 3600) % 60).toString() + "" else "0" + (seconds % 3600) % 60
+    return "$mm:$ss"
 }

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

@@ -306,4 +306,6 @@
     <string name="common_pay_channel_unknown_error">Unsupported payment</string>
     <string name="common_cannot_select_more_interest">You can select a maximum of %s items.</string>
     <string name="common_choose_country">选择国家或地区</string>
+    <string name="common_mic_is_being_used_cant_record">麦克风正在被其他功能使用,无法录音。</string>
+    <string name="common_record_audio_failed">录音失败。</string>
 </resources>

+ 1 - 2
module/account/src/main/java/com/adealink/weparty/account/login/LoginDialog.kt

@@ -26,7 +26,6 @@ import com.adealink.frame.router.Router
 import com.adealink.frame.router.annotation.RouterUri
 import com.adealink.frame.util.DisplayUtil
 import com.adealink.frame.util.copyToClipBoard
-import com.adealink.frame.util.naviBarHeight
 import com.adealink.frame.util.onClick
 import com.adealink.frame.util.statusBarHeight
 import com.adealink.weparty.App
@@ -73,7 +72,7 @@ class LoginDialog : BaseDialogFragment(R.layout.dialog_login) {
                 topMargin = act.statusBarHeight() + 12.dp()
             }
             binding.userAgreementTv.updateLayoutParams<ConstraintLayout.LayoutParams> {
-                bottomMargin = act.naviBarHeight() + 36.dp()
+                bottomMargin = 36.dp()
             }
         }
 

+ 1 - 2
module/account/src/main/java/com/adealink/weparty/account/login/phone/fragment/InputPhoneFragment.kt

@@ -10,7 +10,6 @@ import com.adealink.frame.base.Rlt
 import com.adealink.frame.locale.country.CountryCode
 import com.adealink.frame.log.Log
 import com.adealink.frame.mvvm.view.viewBinding
-import com.adealink.frame.util.naviBarHeight
 import com.adealink.frame.util.onClick
 import com.adealink.frame.util.statusBarHeight
 import com.adealink.weparty.account.R
@@ -41,7 +40,7 @@ class InputPhoneFragment : BaseFragment(R.layout.fragment_account_input_phone) {
             topMargin = activity?.statusBarHeight() ?: 0
         }
         binding.clOtherLogin.updateLayoutParams<ConstraintLayout.LayoutParams> {
-            bottomMargin = (activity?.naviBarHeight() ?: 0) + 18.dp()
+            bottomMargin = 18.dp()
         }
 
         binding.ivCountry.onClick {

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

@@ -23,6 +23,7 @@ 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.adealink.weparty.permission.PermissionUtils
+import com.adealink.weparty.util.formatMissTime
 import com.tencent.qcloud.tuicore.TUIConstants.TUICalling
 import com.tencent.qcloud.tuikit.tuichat.bean.ChatInfo
 import com.tencent.qcloud.tuikit.tuichat.component.audio.AudioPlayer
@@ -189,7 +190,7 @@ class SessionBottomAudioComp(
                         runOnUiThread {
                             if (!isPause) {
                                 times++
-                                voiceBar.tvRecordTime.text = formatMiss(times)
+                                voiceBar.tvRecordTime.text = formatMissTime(times)
                             }
                         }
                     }
@@ -345,7 +346,7 @@ class SessionBottomAudioComp(
         Log.d(TAG_IM_AUDIO, "resetVoiceView")
         mTimer?.cancel()
         mTimer = null
-        voiceBar.tvRecordTime.text = "0:00"
+        voiceBar.tvRecordTime.text = "00:00"
         times = 0
     }
 
@@ -372,15 +373,6 @@ class SessionBottomAudioComp(
 //        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"
-    }
-
     /**
      * 声音分贝数值(0-100)
      */

+ 18 - 6
module/profile/src/main/java/com/adealink/weparty/profile/edit/dialog/EditTalentVoiceDialog.kt

@@ -1,7 +1,10 @@
 package com.adealink.weparty.profile.edit.dialog
 
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.updateLayoutParams
 import androidx.fragment.app.viewModels
 import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.util.naviBarHeight
 import com.adealink.weparty.commonui.widget.BottomDialogFragment
 import com.adealink.weparty.module.profile.data.VoiceData
 import com.adealink.weparty.profile.R
@@ -35,16 +38,13 @@ class EditTalentVoiceDialog : BottomDialogFragment(R.layout.dialog_edit_talent_v
 
     override fun initViews() {
         super.initViews()
-        if (voice == null || voice?.isNull() == true) {
-            //未上传声音
-            viewModel.showPage(EditVoicePage.EDIT)
-        } else {
-            viewModel.showPage(EditVoicePage.VOICE)
-        }
     }
 
     override fun observeViewModel() {
         super.observeViewModel()
+        binding.flContent.updateLayoutParams<ConstraintLayout.LayoutParams> {
+            bottomMargin = activity?.naviBarHeight() ?: 0
+        }
         viewModel.pageLD.observeWithoutCache(viewLifecycleOwner) { page ->
             when (page) {
                 EditVoicePage.VOICE -> {
@@ -58,6 +58,18 @@ class EditTalentVoiceDialog : BottomDialogFragment(R.layout.dialog_edit_talent_v
         }
     }
 
+    override fun loadData() {
+        super.loadData()
+        viewModel.showPage(EditVoicePage.EDIT)
+        // TODO: zhangfei 先做录音部分, 审核后续再加
+//        if (voice == null || voice?.isNull() == true) {
+//            //未上传声音
+//            viewModel.showPage(EditVoicePage.EDIT)
+//        } else {
+//            viewModel.showPage(EditVoicePage.VOICE)
+//        }
+    }
+
     private fun inflateVoiceFragment() {
         val fragment = childFragmentManager.findFragmentByTag(TAG_VOICE)
         if (fragment?.isDetached == true) {

+ 212 - 2
module/profile/src/main/java/com/adealink/weparty/profile/edit/dialog/fragment/VoiceEditFragment.kt

@@ -1,32 +1,242 @@
 package com.adealink.weparty.profile.edit.dialog.fragment
 
+import android.Manifest
+import android.annotation.SuppressLint
+import androidx.fragment.app.FragmentActivity
+import androidx.lifecycle.lifecycleScope
+import com.adealink.frame.aab.util.getCompatString
+import com.adealink.frame.ext.isViewBindingValid
+import com.adealink.frame.log.Log
 import com.adealink.frame.mvvm.view.viewBinding
 import com.adealink.frame.util.onClick
+import com.adealink.frame.util.runOnUiThread
 import com.adealink.weparty.commonui.BaseFragment
+import com.adealink.weparty.commonui.ext.gone
+import com.adealink.weparty.commonui.ext.show
+import com.adealink.weparty.commonui.toast.util.debugToast
+import com.adealink.weparty.commonui.toast.util.showToast
+import com.adealink.weparty.permission.PermissionUtils
 import com.adealink.weparty.profile.R
 import com.adealink.weparty.profile.databinding.FragmentTalentVoiceEditBinding
 import com.adealink.weparty.profile.edit.dialog.EditTalentVoiceDialog
 import com.adealink.weparty.profile.edit.dialog.viewmodel.EditVoiceViewModel
+import com.adealink.weparty.util.formatMissTime
 import com.adealink.weparty.viewmodel.parentFragmentViewModels
+import com.tencent.qcloud.tuicore.TUIConstants.TUICalling
+import com.tencent.qcloud.tuikit.tuichat.component.audio.AudioPlayer
+import com.tencent.qcloud.tuikit.tuichat.component.audio.AudioRecorder
+import com.tencent.qcloud.tuikit.tuichat.component.audio.AudioRecorder.AudioRecorderCallback
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+import java.util.Timer
+import java.util.TimerTask
+import kotlin.coroutines.resume
+import com.adealink.weparty.R as APP_R
 
 class VoiceEditFragment : BaseFragment(R.layout.fragment_talent_voice_edit) {
 
+    companion object {
+        private const val TAG = "VoiceEditFragment"
+    }
+
     private val binding by viewBinding(FragmentTalentVoiceEditBinding::bind)
 
-    private val viewModel by parentFragmentViewModels<EditVoiceViewModel, EditTalentVoiceDialog>(
+    private val editViewModel by parentFragmentViewModels<EditVoiceViewModel, EditTalentVoiceDialog>(
         EditTalentVoiceDialog::class.java
     )
 
+    private var mTimer: Timer? = null
+    private var times = 0
+
+    private var isRecording: Boolean = false
+
     override fun initViews() {
         super.initViews()
-        //binding.vSound.setSoundUrl()
         binding.btnRecord.onClick {
+            clickRecord()
+        }
+        binding.btnReRecord.onClick {
+            clickReRecord()
+        }
+        binding.btnPreview.onClick {
+            audioPreview()
+        }
+        binding.btnSubmit.onClick {
+            debugToast("录音审核功能未实现,目前仅支持录音过程")
+        }
+    }
 
+    override fun observeViewModel() {
+        super.observeViewModel()
+        editViewModel.voiceDataLD.observeWithoutCache(viewLifecycleOwner) {
+            if (it.path.isNullOrEmpty()) {
+                binding.clRecord.show()
+                binding.clPreview.gone()
+                binding.btnSubmit.isEnabled = false
+            } else {
+                binding.clRecord.gone()
+                binding.clPreview.show()
+                binding.btnSubmit.isEnabled = true
+            }
         }
     }
 
     override fun loadData() {
         super.loadData()
+        isRecording = false
+        editViewModel.setVoiceData(null)
+    }
+
+    private fun clickRecord() {
+        if (!isRecording) {
+            //启动录制
+            startRecord()
+        } else {
+            stopRecord()
+        }
+    }
+
+    private fun clickReRecord() {
+        editViewModel.setVoiceData(null)
+        resetRecord()
+        startRecord()
+    }
+
+    private fun audioPreview() {
+        val voicePath = editViewModel.voiceDataLD.value?.path
+        if (voicePath.isNullOrEmpty()) {
+            return
+        }
+        AudioPlayer.getInstance().stopPlay()
+        AudioPlayer.getInstance().startPlay(voicePath) { }
+    }
+
+    private fun startRecord() {
+        //录音前关闭音频播放
+        AudioPlayer.getInstance().stopPlay()
+
+        Log.d(TAG, "startAudioRecord")
+        AudioRecorder.startRecord(object : AudioRecorderCallback {
+            override fun onStarted() {
+                Log.d(TAG, "startAudioRecord, onStarted")
+                isRecording = true
+                if (mTimer == null) {
+                    mTimer = Timer()
+                }
+                mTimer?.schedule(object : TimerTask() {
+                    override fun run() {
+                        runOnUiThread {
+                            times++
+                            if (isViewBindingValid()) {
+                                binding.tvDuration.text = formatMissTime(times)
+                            }
+                        }
+                    }
+                }, 0, 1000)
+                binding.tvDurationTips.text =
+                    getCompatString(R.string.profile_edit_talent_voice_stop)
+                binding.btnRecord.setImageResource(R.drawable.profile_edit_talent_voice_stop_ic)
+            }
+
+            override fun onFinished(outputPath: String?) {
+                Log.d(TAG, "startAudioRecord, onFinished:$outputPath")
+                resetRecord()
+                val duration = AudioRecorder.getDuration(outputPath)
+                if (duration < 3000) {
+                    showToast(R.string.profile_record_voice_less_than_3_seconds)
+                    editViewModel.setVoiceData(null)
+                    return
+                }
+                editViewModel.setVoiceData(outputPath)
+            }
+
+            override fun onFailed(errorCode: Int, errorMessage: String?) {
+                Log.d(
+                    TAG,
+                    "startAudioRecord, onFailed, errorCode:$errorCode, errorMessage:$errorMessage"
+                )
+                resetRecord()
+                if (errorCode == TUICalling.ERROR_MIC_PERMISSION_REFUSED) {
+                    //没有授予权限
+                    requestPermission()
+                } else if (errorCode == AudioRecorder.ERROR_CODE_MIC_IS_BEING_USED || errorCode == TUICalling.ERROR_STATUS_IN_CALL) {
+                    showToast(APP_R.string.common_mic_is_being_used_cant_record)
+                } else {
+                    showToast(APP_R.string.common_record_audio_failed)
+                }
+                Log.e(
+                    TAG,
+                    "record audio failed, errorCode $errorCode, errorMessage $errorMessage"
+                )
+                binding.btnRecord.setImageResource(R.drawable.profile_edit_talent_voice_record_ic)
+            }
+
+            override fun onCanceled() {
+                Log.d(TAG, "startAudioRecord, onCanceled")
+                resetRecord()
+                binding.btnRecord.setImageResource(R.drawable.profile_edit_talent_voice_record_ic)
+            }
+        })
+    }
+
+    private fun requestPermission() {
+        val act = activity ?: return
+        viewLifecycleOwner.lifecycleScope.launch {
+            val granted = requestPermission(act)
+            if (granted) {
+                startRecord()
+            }
+        }
+    }
+
+    private val permissions = arrayOf(Manifest.permission.RECORD_AUDIO)
+    private suspend fun requestPermission(activity: FragmentActivity): Boolean {
+        return suspendCancellableCoroutine { coroutine ->
+            if (PermissionUtils.hasPermissions(activity, *permissions)) {
+                if (coroutine.isActive) {
+                    coroutine.resume(true)
+                }
+                return@suspendCancellableCoroutine
+            }
+            PermissionUtils.requestPermissions(
+                activity,
+                permissions.toList(),
+                getCompatString(R.string.profile_record_voice_permission_request_tips),
+                onGranted = {
+                    if (coroutine.isActive) {
+                        coroutine.resume(true)
+                    }
+                },
+                onDenied = {
+                    if (coroutine.isActive) {
+                        coroutine.resume(false)
+                    }
+                }
+            )
+        }
+    }
+
+    private fun stopRecord() {
+        AudioRecorder.stopRecord()
+    }
+
+    @SuppressLint("SetTextI18n")
+    private fun resetRecord() {
+        isRecording = false
+        mTimer?.cancel()
+        mTimer = null
+        times = 0
+        if (isViewBindingValid()) {
+            binding.tvDuration.text = "00:00"
+            binding.tvDurationTips.text = getCompatString(R.string.profile_edit_talent_voice_record)
+            binding.btnRecord.setImageResource(R.drawable.profile_edit_talent_voice_record_ic)
+        }
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        AudioPlayer.getInstance().stopPlay()
+        AudioRecorder.stopRecord()
     }
 
 }

+ 15 - 0
module/profile/src/main/java/com/adealink/weparty/profile/edit/dialog/viewmodel/EditVoiceViewModel.kt

@@ -2,6 +2,7 @@ package com.adealink.weparty.profile.edit.dialog.viewmodel
 
 import com.adealink.frame.mvvm.livedata.ExtMutableLiveData
 import com.adealink.frame.mvvm.viewmodel.BaseViewModel
+import com.adealink.weparty.module.profile.data.VoiceData
 
 enum class EditVoicePage {
     VOICE,
@@ -16,4 +17,18 @@ class EditVoiceViewModel : BaseViewModel() {
         pageLD.send(page)
     }
 
+
+    /**
+     * 录制声音
+     */
+
+    private var voiceData: VoiceData? = null
+    val voiceDataLD = ExtMutableLiveData<VoiceData>()
+
+    fun setVoiceData(path: String?) {
+        val data = VoiceData(url = null, path = path)
+        voiceData = data
+        voiceDataLD.send(data)
+    }
+
 }

+ 2 - 0
module/profile/src/main/java/com/adealink/weparty/profile/edit/viewmodel/EditProfileViewModel.kt

@@ -66,6 +66,7 @@ class EditProfileViewModel : BaseViewModel() {
                 )
             )
             editUserInfo.photos = photoList
+            editUserInfoLD.send(editUserInfo)
             onPictureListChanged()
         }
     }
@@ -74,6 +75,7 @@ class EditProfileViewModel : BaseViewModel() {
         viewModelScope.launch {
             photoList.remove(data)
             editUserInfo.photos = photoList
+            editUserInfoLD.send(editUserInfo)
             onPictureListChanged()
         }
     }

+ 3 - 2
module/profile/src/main/res/layout/dialog_edit_talent_voice.xml

@@ -3,7 +3,8 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:background="@drawable/common_bottom_dialog_bg">
+    android:background="@drawable/common_bottom_dialog_bg"
+    android:paddingBottom="16dp">
 
     <androidx.appcompat.widget.AppCompatTextView
         android:id="@+id/tv_title"
@@ -22,7 +23,7 @@
     <androidx.fragment.app.FragmentContainerView
         android:id="@+id/fl_content"
         android:layout_width="match_parent"
-        android:layout_height="240dp"
+        android:layout_height="@dimen/edit_talent_voice_height"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@id/tv_title" />

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

@@ -2,7 +2,7 @@
 <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="240dp">
+    android:layout_height="@dimen/edit_talent_voice_height">
 
     <com.adealink.weparty.module.playmate.widget.SoundView
         android:id="@+id/v_sound"

+ 7 - 7
module/profile/src/main/res/layout/fragment_talent_voice_edit.xml

@@ -3,7 +3,7 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
-    android:layout_height="240dp">
+    android:layout_height="@dimen/edit_talent_voice_height">
 
     <!--录制-->
     <androidx.constraintlayout.widget.ConstraintLayout
@@ -14,7 +14,8 @@
         app:layout_constraintBottom_toTopOf="@id/btn_submit"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent">
+        app:layout_constraintTop_toTopOf="parent"
+        tools:visibility="visible">
 
         <androidx.appcompat.widget.AppCompatTextView
             android:id="@+id/tv_duration"
@@ -22,14 +23,14 @@
             android:layout_height="wrap_content"
             android:gravity="center"
             android:includeFontPadding="false"
+            android:text="00:00"
             android:textColor="@color/color_FF1D2129"
             android:textSize="14sp"
             app:layout_constraintBottom_toTopOf="@id/btn_record"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintVertical_chainStyle="packed"
-            tools:text="00:00" />
+            app:layout_constraintVertical_chainStyle="packed" />
 
         <androidx.appcompat.widget.AppCompatImageView
             android:id="@+id/btn_record"
@@ -143,15 +144,14 @@
 
     </androidx.constraintlayout.widget.ConstraintLayout>
 
-
     <com.adealink.weparty.commonui.widget.CommonButton
         android:id="@+id/btn_submit"
         android:layout_width="0dp"
         android:layout_height="@dimen/common_button_height"
         android:layout_marginHorizontal="20dp"
-        app:text="@string/common_submit"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent" />
+        app:layout_constraintStart_toStartOf="parent"
+        app:text="@string/common_submit" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>

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

@@ -1,4 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <dimen name="profile_header_height">360dp</dimen>
+    <dimen name="edit_talent_voice_height">240dp</dimen>
 </resources>

+ 2 - 0
module/profile/src/main/res/values/strings.xml

@@ -72,4 +72,6 @@
     <string name="profile_edit_interest_tips">Select to find more compatible friends</string>
     <string name="profile_edit_not_save_tips">Edits not saved, confirm exit?</string>
     <string name="profile_edit_voice_not_empty">On display</string>
+    <string name="profile_record_voice_permission_request_tips">录制声音需要授予麦克风权限</string>
+    <string name="profile_record_voice_less_than_3_seconds">录音时间不能低于3秒哦</string>
 </resources>