diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ee25edab..53e768a9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -29,6 +29,7 @@ <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <!-- 读取缓存数据 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.RECORD_AUDIO" /> <!--android:largeHeap="true" 大内存 128M --> <application android:name=".OMQSApplication" diff --git a/app/src/main/java/com/navinfo/omqs/Constant.kt b/app/src/main/java/com/navinfo/omqs/Constant.kt index e74563a6..43bad93b 100644 --- a/app/src/main/java/com/navinfo/omqs/Constant.kt +++ b/app/src/main/java/com/navinfo/omqs/Constant.kt @@ -32,6 +32,11 @@ class Constant { */ lateinit var USER_DATA_PATH: String + /** + * 用户附件数据目录 + */ + lateinit var USER_DATA_ATTACHEMNT_PATH: String + /** * 离线地图目录 */ @@ -49,6 +54,8 @@ class Constant { const val DEBUG = true + var IS_VIDEO_SPEED by kotlin.properties.Delegates.notNull<Boolean>() + const val message_status_late = "预约,待发送" const val message_status_send_over = "已发送" const val message_version_right_off = "1" //立即发送 diff --git a/app/src/main/java/com/navinfo/omqs/bean/Attachment.kt b/app/src/main/java/com/navinfo/omqs/bean/Attachment.kt new file mode 100644 index 00000000..11088a69 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/bean/Attachment.kt @@ -0,0 +1,43 @@ +package com.navinfo.omqs.bean + +import java.io.Serializable +import java.util.* + +class Attachment(filename: String, type: Int) : Serializable, + Cloneable { + //内容 + var filename: String = "" + + //标识 默认照片0 录音1 + var type: Int + + override fun toString(): String { + return "TipsAttachment{" + + "filename='" + filename + '\'' + + ", type=" + type + + '}' + } + + override fun equals(o: Any?): Boolean { + if (this === o) return true + if (o == null || javaClass != o.javaClass) return false + val that = o as Attachment + return type == that.type && + filename == that.filename + } + + override fun hashCode(): Int { + return Objects.hash(filename, type) + } + + @kotlin.Throws(CloneNotSupportedException::class) + public override fun clone(): Any { + return super.clone() + } + + init { + this.filename = filename + this.type = type + } +} + diff --git a/app/src/main/java/com/navinfo/omqs/bean/ChatMsgEntity.kt b/app/src/main/java/com/navinfo/omqs/bean/ChatMsgEntity.kt new file mode 100644 index 00000000..1a6f4548 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/bean/ChatMsgEntity.kt @@ -0,0 +1,23 @@ +package com.navinfo.omqs.bean + +import java.io.Serializable + +class ChatMsgEntity : Serializable, Cloneable { + var voiceUri //声音存储地址 + : String? = null + var voiceTimeLong //声音时间长度 + : String? = null + var name //声音名字 + : String? = null + var isDelete //是否被删除 + = false + + @kotlin.Throws(CloneNotSupportedException::class) + public override fun clone(): Any { + return super.clone() + } + + companion object { + private val TAG: String = ChatMsgEntity::class.java.getSimpleName() + } +} diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/CheckPermissionsActivity.java b/app/src/main/java/com/navinfo/omqs/ui/activity/CheckPermissionsActivity.java index be35ad09..6690ea72 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/activity/CheckPermissionsActivity.java +++ b/app/src/main/java/com/navinfo/omqs/ui/activity/CheckPermissionsActivity.java @@ -37,6 +37,7 @@ public class CheckPermissionsActivity extends BaseActivity { Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.RECORD_AUDIO }; private static final int PERMISSON_REQUESTCODE = 0; @@ -51,6 +52,7 @@ public class CheckPermissionsActivity extends BaseActivity { Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.RECORD_AUDIO, BACKGROUND_LOCATION_PERMISSION }; } diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt index 94d4f611..1b054ca1 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt @@ -156,12 +156,17 @@ class LoginViewModel @Inject constructor( * 创建用户目录 */ private fun createUserFolder(context: Context, userId: String) { + Constant.IS_VIDEO_SPEED = false Constant.USER_ID = userId Constant.VERSION_ID = userId Constant.USER_DATA_PATH = Constant.DATA_PATH + Constant.USER_ID + "/" + Constant.VERSION_ID + Constant.USER_DATA_ATTACHEMNT_PATH = Constant.USER_DATA_PATH + "/attachment/" // 在SD卡创建用户目录,解压资源等 val userFolder = File(Constant.USER_DATA_PATH) if (!userFolder.exists()) userFolder.mkdirs() + //创建附件目录 + val userAttachmentFolder = File(Constant.USER_DATA_ATTACHEMNT_PATH) + if (!userAttachmentFolder.exists()) userAttachmentFolder.mkdirs() // 初始化Realm Realm.init(context.applicationContext) val password = "encryp".encodeToByteArray().copyInto(ByteArray(64)) diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainActivity.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainActivity.kt index 44732ade..cb0a6eb3 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainActivity.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainActivity.kt @@ -1,7 +1,12 @@ package com.navinfo.omqs.ui.activity.map +import android.os.Build import android.os.Bundle +import android.util.Log +import android.view.MotionEvent +import android.view.View import androidx.activity.viewModels +import androidx.annotation.RequiresApi import androidx.core.view.WindowCompat import androidx.databinding.DataBindingUtil import androidx.navigation.findNavController @@ -52,6 +57,26 @@ class MainActivity : BaseActivity() { //给xml传递viewModel对象 binding.viewModel = viewModel + binding.mainActivityVoice.setOnTouchListener(object : View.OnTouchListener { + @RequiresApi(Build.VERSION_CODES.Q) + override fun onTouch(v: View?, event: MotionEvent?): Boolean { + Log.e("qj",event?.action.toString()) + when (event?.action) { + MotionEvent.ACTION_DOWN ->{ + voiceOnTouchStart()//Do Something + Log.e("qj","voiceOnTouchStart") + } + MotionEvent.ACTION_UP ->{ + voiceOnTouchStop()//Do Something + Log.e("qj","voiceOnTouchStop") + } + } + + + return v?.onTouchEvent(event) ?: true + } + }) + viewModel.liveDataQsRecordIdList.observe(this) { //处理页面跳转 viewModel.navigation(this, it) @@ -68,8 +93,8 @@ class MainActivity : BaseActivity() { mapController.locationLayerHandler.setNiLocationListener(NiLocationListener { //ToastUtils.showLong("定位${it.longitude}") binding!!.viewModel!!.addSaveTrace(it) - binding!!.viewModel!!.startSaveTraceThread(this) }) + binding!!.viewModel!!.startSaveTraceThread(this) //显示轨迹图层 // mapController.layerManagerHandler.showNiLocationLayer(Constant.DATA_PATH+ SystemConstant.USER_ID+"/trace.sqlite") mapController.layerManagerHandler.showNiLocationLayer() @@ -110,8 +135,19 @@ class MainActivity : BaseActivity() { * 点击录音按钮 */ fun voiceOnclick() { - val naviController = findNavController(R.id.main_activity_right_fragment) - naviController.navigate(R.id.EvaluationResultFragment) +/* val naviController = findNavController(R.id.main_activity_right_fragment) + naviController.navigate(R.id.EvaluationResultFragment)*/ + } + + fun voiceOnTouchStart(){ + binding!!.viewModel!!.startSoundMetter(this,mapController.locationLayerHandler.getCurrentNiLocation(),binding.mainActivityVoice) + } + + @RequiresApi(Build.VERSION_CODES.Q) + fun voiceOnTouchStop(){ + if(Constant.IS_VIDEO_SPEED){ + binding!!.viewModel!!.stopSoundMeter() + } } // override fun onBackPressed() { diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainViewModel.kt index e1bc8b5f..8447afb9 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainViewModel.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainViewModel.kt @@ -1,12 +1,24 @@ package com.navinfo.omqs.ui.activity.map +import android.app.Activity import android.content.Context import android.content.DialogInterface +import android.graphics.drawable.AnimationDrawable +import android.graphics.drawable.BitmapDrawable +import android.os.Build import android.os.Bundle +import android.text.TextUtils import android.util.Log +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.PopupWindow +import androidx.annotation.RequiresApi import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.navigation.findNavController +import com.blankj.utilcode.util.ToastUtils import com.navinfo.collect.library.data.dao.impl.TraceDataBase import com.navinfo.collect.library.data.entity.NiLocation import com.navinfo.collect.library.map.NIMapController @@ -17,10 +29,15 @@ import com.navinfo.omqs.Constant import com.navinfo.omqs.R import com.navinfo.omqs.ui.dialog.CommonDialog import com.navinfo.omqs.ui.manager.TakePhotoManager +import com.navinfo.omqs.util.DateTimeUtil +import com.navinfo.omqs.util.SoundMeter +import com.navinfo.omqs.util.SpeakMode import dagger.hilt.android.lifecycle.HiltViewModel import io.realm.RealmSet import org.oscim.core.GeoPoint import org.videolan.libvlc.LibVlcUtil +import java.io.File +import java.util.* import javax.inject.Inject /** @@ -28,14 +45,23 @@ import javax.inject.Inject */ @HiltViewModel class MainViewModel @Inject constructor( - private val mapController: NIMapController, + private val mapController: NIMapController ) : ViewModel() { val liveDataQsRecordIdList = MutableLiveData<List<String>>() private var mCameraDialog: CommonDialog? = null + //语音窗体 + private var pop: PopupWindow? = null + + private var mSpeakMode: SpeakMode? = null + private var niLocationList: MutableList<NiLocation> = ArrayList<NiLocation>() + //录音图标 + var volume: ImageView? = null + var mSoundMeter: SoundMeter? = null + init { mapController.markerHandle.setOnQsRecordItemClickListener(object : OnQsRecordItemClickListener { @@ -43,6 +69,7 @@ class MainViewModel @Inject constructor( liveDataQsRecordIdList.value = list } }) + } /** @@ -61,8 +88,6 @@ class MainViewModel @Inject constructor( Log.e("qj", LibVlcUtil.hasCompatibleCPU(context).toString()) - //ToastUtils.showShort("点击了相机") - if (mCameraDialog == null) { mCameraDialog = CommonDialog( context, @@ -100,11 +125,15 @@ class MainViewModel @Inject constructor( Thread(Runnable { try { while (true) { - if (niLocationList != null && niLocationList.size > 0) { var niLocation = niLocationList[0] - val geometry = GeometryTools.createGeometry(GeoPoint(niLocation.latitude,niLocation.longitude)) + val geometry = GeometryTools.createGeometry( + GeoPoint( + niLocation.latitude, + niLocation.longitude + ) + ) val tileX = RealmSet<Int>() GeometryToolsKt.getTileXByGeometry(geometry.toString(), tileX) val tileY = RealmSet<Int>() @@ -118,11 +147,16 @@ class MainViewModel @Inject constructor( } } - TraceDataBase.getDatabase(context, Constant.USER_DATA_PATH + "/trace.sqlite").niLocationDao.insert(niLocation) - + TraceDataBase.getDatabase( + context, + Constant.USER_DATA_PATH + "/trace.sqlite" + ).niLocationDao.insert(niLocation) + val list = TraceDataBase.getDatabase( + context, + Constant.USER_DATA_PATH + "/trace.sqlite" + ).niLocationDao.findAll() niLocationList.remove(niLocation) - - Log.e("qj", "saveTrace==${niLocationList.size}") + Log.e("qj", "saveTrace==${niLocationList.size}===${list.size}") } Thread.sleep(30) @@ -141,6 +175,90 @@ class MainViewModel @Inject constructor( } } + fun startSoundMetter(context: Context, niLocation: NiLocation?, v: View) { + if (niLocation == null) { + ToastUtils.showLong("未获取到GPS信息,请检查GPS是否正常!") + //停止录音动画 + if (pop != null && pop!!.isShowing()) + pop!!.dismiss(); + return; + } + + if(mSpeakMode==null){ + mSpeakMode = SpeakMode(context as Activity?) + } + + //语音识别动画 + if (pop == null) { + pop = PopupWindow() + pop!!.width = ViewGroup.LayoutParams.MATCH_PARENT + pop!!.height = ViewGroup.LayoutParams.WRAP_CONTENT + pop!!.setBackgroundDrawable(BitmapDrawable()) + val view = View.inflate(context, R.layout.cv_card_voice_rcd_hint_window, null) + pop!!.contentView = view + volume = view.findViewById(R.id.volume) + } + + pop!!.update() + + Constant.IS_VIDEO_SPEED = true + //录音动画 + //录音动画 + if (pop != null) { + pop!!.showAtLocation(v, Gravity.CENTER, 0, 0) + } + volume!!.setBackgroundResource(R.drawable.pop_voice_img) + val animation = volume!!.background as AnimationDrawable + animation.start() + + val name: String = DateTimeUtil.getTimeSSS().toString() + ".m4a" + if (mSoundMeter == null) { + mSoundMeter = SoundMeter() + } + mSoundMeter!!.setmListener(object : SoundMeter.OnSoundMeterListener { + @RequiresApi(Build.VERSION_CODES.Q) + override fun onSuccess(filePath: String?) { + if (!TextUtils.isEmpty(filePath) && File(filePath).exists()) { + if (File(filePath) == null || File(filePath).length() < 1600) { + ToastUtils.showLong("语音时间太短,无效!") + mSpeakMode!!.speakText("语音时间太短,无效") + stopSoundMeter() + return + } + } + mSpeakMode!!.speakText("结束录音") + //获取右侧fragment容器 + val naviController = (context as Activity).findNavController(R.id.main_activity_right_fragment) + val bundle = Bundle() + bundle.putString("filePath", filePath) + naviController.navigate(R.id.EvaluationResultFragment, bundle) + } + + @RequiresApi(api = Build.VERSION_CODES.Q) + override fun onfaild(message: String?) { + ToastUtils.showLong("录制失败!") + mSpeakMode!!.speakText("录制失败") + stopSoundMeter() + } + }) + + mSoundMeter!!.start(Constant.USER_DATA_ATTACHEMNT_PATH + name) + ToastUtils.showLong("开始录音") + mSpeakMode!!.speakText("开始录音") + } + + //停止语音录制 + @RequiresApi(api = Build.VERSION_CODES.Q) + fun stopSoundMeter() { + //先重置标识,防止按钮抬起时触发语音结束 + Constant.IS_VIDEO_SPEED = false + if (mSoundMeter != null && mSoundMeter!!.isStartSound()) { + mSoundMeter!!.stop() + } + if (pop != null && pop!!.isShowing) pop!!.dismiss() + } + + fun navigation(activity: MainActivity, list: List<String>) { //获取右侧fragment容器 val naviController = activity.findNavController(R.id.main_activity_right_fragment) diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/evaluationresult/EvaluationResultFragment.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/evaluationresult/EvaluationResultFragment.kt index 65ca7324..c0ff35a9 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/fragment/evaluationresult/EvaluationResultFragment.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/evaluationresult/EvaluationResultFragment.kt @@ -7,9 +7,11 @@ import android.view.ViewGroup import androidx.databinding.DataBindingUtil import androidx.navigation.NavOptions import androidx.navigation.findNavController +import androidx.recyclerview.widget.LinearLayoutManager import com.navinfo.omqs.R import com.navinfo.omqs.databinding.FragmentEvaluationResultBinding import com.navinfo.omqs.ui.fragment.BaseFragment +import com.navinfo.omqs.ui.fragment.tasklist.TaskListAdapter import com.navinfo.omqs.ui.other.shareViewModels import dagger.hilt.android.AndroidEntryPoint @@ -17,15 +19,26 @@ import dagger.hilt.android.AndroidEntryPoint class EvaluationResultFragment : BaseFragment(), View.OnClickListener { private lateinit var binding: FragmentEvaluationResultBinding private val viewModel by shareViewModels<EvaluationResultViewModel>("QsRecode") - + private val adapter: SoundtListAdapter by lazy { + SoundtListAdapter() + } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { binding = DataBindingUtil.inflate(inflater, R.layout.fragment_evaluation_result, container, false) binding.fragment = this + val layoutManager = LinearLayoutManager(context) binding.viewModel = viewModel binding.lifecycleOwner = this + //// 设置 RecyclerView 的固定大小,避免在滚动时重新计算视图大小和布局,提高性能 + binding.evaluationVoiceRecyclerview.setHasFixedSize(true) + binding.evaluationVoiceRecyclerview.layoutManager = layoutManager + binding.evaluationVoiceRecyclerview.adapter = adapter + viewModel.listDataChatMsgEntityList.observe(viewLifecycleOwner) { + adapter.refreshData(it) + } + viewModel.getChatMsgEntityList() return binding.root } diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/evaluationresult/EvaluationResultViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/evaluationresult/EvaluationResultViewModel.kt index f1914053..50233488 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/fragment/evaluationresult/EvaluationResultViewModel.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/evaluationresult/EvaluationResultViewModel.kt @@ -1,12 +1,17 @@ package com.navinfo.omqs.ui.fragment.evaluationresult +import android.os.Build import android.util.Log +import androidx.annotation.RequiresApi import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.navinfo.collect.library.data.entity.QsRecordBean import com.navinfo.collect.library.map.NIMapController import com.navinfo.collect.library.utils.GeometryTools +import com.navinfo.omqs.Constant +import com.navinfo.omqs.bean.Attachment +import com.navinfo.omqs.bean.ChatMsgEntity import com.navinfo.omqs.db.RealmOperateHelper import com.navinfo.omqs.db.RoomAppDatabase import dagger.hilt.android.lifecycle.HiltViewModel @@ -14,7 +19,6 @@ import io.realm.Realm import io.realm.kotlin.where import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import org.locationtech.jts.geom.Point import java.util.* import javax.inject.Inject @@ -47,9 +51,12 @@ class EvaluationResultViewModel @Inject constructor( */ val liveDataRightTypeList = MutableLiveData<List<RightBean>>() - var liveDataQsRecordBean = MutableLiveData<QsRecordBean>() + var listDataChatMsgEntityList = MutableLiveData<MutableList<ChatMsgEntity>>() + + var listDataAttachmentList = MutableLiveData<MutableList<Attachment>>() + var oldBean: QsRecordBean? = null init { @@ -274,4 +281,22 @@ class EvaluationResultViewModel @Inject constructor( } } } + + /** + * 查询问题类型列表 + */ + @RequiresApi(Build.VERSION_CODES.N) + fun getChatMsgEntityList() { + val chatMsgEntityList: MutableList<ChatMsgEntity> = ArrayList() + liveDataQsRecordBean.value!!.attachments.forEach{ + //1 录音 + if(it.type==1){ + val chatMsgEntity = ChatMsgEntity() + chatMsgEntity.name = it.filename + chatMsgEntity.voiceUri = Constant.USER_DATA_ATTACHEMNT_PATH + chatMsgEntityList.add(chatMsgEntity) + } + } + listDataChatMsgEntityList.postValue(chatMsgEntityList) + } } \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/evaluationresult/SoundtListAdapter.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/evaluationresult/SoundtListAdapter.kt new file mode 100644 index 00000000..8b39ef35 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/evaluationresult/SoundtListAdapter.kt @@ -0,0 +1,241 @@ +package com.navinfo.omqs.ui.fragment.evaluationresult + +import android.graphics.drawable.AnimationDrawable +import android.media.AudioFormat +import android.media.AudioManager +import android.media.AudioTrack +import android.media.MediaPlayer +import android.text.TextUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import com.navinfo.omqs.R +import com.navinfo.omqs.bean.ChatMsgEntity +import com.navinfo.omqs.databinding.AdapterSoundListBinding +import com.navinfo.omqs.ui.other.BaseRecyclerViewAdapter +import com.navinfo.omqs.ui.other.BaseViewHolder +import java.io.* + +/** + * 语音 RecyclerView 适配器 + * + * 在 RecycleView 的 ViewHolder 中监听 ViewModel 的 LiveData,然后此时传递的 lifecycleOwner 是对应的 Fragment。由于 ViewHolder 的生命周期是比 Fragment 短的,所以当 ViewHolder 销毁时,由于 Fragment 的 Lifecycle 还没有结束,此时 ViewHolder 会发生内存泄露(监听的 LiveData 没有解绑) + * 这种场景下有两种解决办法: + *使用 LiveData 的 observeForever 然后在 ViewHolder 销毁前手动调用 removeObserver + *使用 LifecycleRegistry 给 ViewHolder 分发生命周期(这里使用了这个) + */ +class SoundtListAdapter( +) : BaseRecyclerViewAdapter<ChatMsgEntity>() { + + //媒体播放器 + private val mMediaPlayer = MediaPlayer() + + //媒体播放器 + private var md: MediaPlayer? = null + + private var mAudioTrack: AudioTrack? = null + + //录音结束后,右侧显示图片 + private var animView: View? = null + + //录音时动画效果 + private var animaV: AnimationDrawable? = null + + private var itemClick: OnItemClickListner? = null + + //最大宽度 + private val maxWidth = 0 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { + val viewBinding = + AdapterSoundListBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return BaseViewHolder(viewBinding) + } + + override fun onViewRecycled(holder: BaseViewHolder) { + super.onViewRecycled(holder) + + } + + override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { + val binding: AdapterSoundListBinding = + holder.viewBinding as AdapterSoundListBinding + val entity = data[position] + //tag 方便onclick里拿到数据 + holder.tag = entity.name.toString() + holder.viewBinding.tvTime.isSelected = entity.isDelete + holder.viewBinding.rlSoundContent.isSelected = entity.isDelete + holder.viewBinding.ivSoundAnim.setBackgroundResource(R.drawable.icon_sound_03) + if (itemClick != null) { + holder.viewBinding.rlSoundContent.setOnClickListener { + itemClick!!.onItemClick(it.findViewById<View>(R.id.rl_sound_content), position) + } + } + //mixWidth + if (!TextUtils.isEmpty(entity.name)) { + if (entity.name.indexOf(".pcm") > 0) { + val file: File = File(entity.voiceUri + entity.name) + if (file != null) { + val time = (file.length() / 16000).toInt() + val layoutParams: ViewGroup.LayoutParams = + holder.viewBinding.rlSoundContent.getLayoutParams() + layoutParams.width = 115 + time * 10 + layoutParams.width = + if (layoutParams.width > layoutParams.width) maxWidth else layoutParams.width + holder.viewBinding.rlSoundContent.setLayoutParams(layoutParams) + holder.viewBinding.tvTime.text = time.toString() + "\"" + } + } else { + try { + md = MediaPlayer() + md.reset() + md.setDataSource(entity.getVoiceUri() + entity.getName()) + md.prepare() + } catch (e: Exception) { + // TODO Auto-generated catch block + e.printStackTrace() + } + var time = + if (entity.getVoiceTimeLong() == null) md!!.duration.toString() + "" else entity.getVoiceTimeLong() + .toString() + "" + if (!TextUtils.isEmpty(time)) { + val i = md!!.duration / 1000 + time = i.toString() + "\"" + val layoutParams: ViewGroup.LayoutParams = + holder.viewBinding.rlSoundContent.getLayoutParams() + layoutParams.width = 115 + i * 10 + layoutParams.width = + if (layoutParams.width > layoutParams.width) maxWidth else layoutParams.width + holder.viewBinding.rlSoundContent.setLayoutParams(layoutParams) + } + holder.viewBinding.tvTime.text = time + md!!.release() + } + } + + + override fun getItemViewRes(position: Int): Int { + return R.layout.adapter_sound_list + } + + /** + * 播放某段录音 + * + * @param view 显示动画 + * @param index 录音在集合中索引 + */ + fun setPlayerIndex(view: View, index: Int) { + val imageV = view.findViewById<View>(R.id.iv_sound_anim) as ImageView + val width = view.width + if (animView != null) { + animaV?.stop() + animView!!.setBackgroundResource(R.drawable.icon_sound_03) + } + animView = imageV + val entity: ChatMsgEntity = data.get(index) + playMusic(entity.voiceUri + entity.name, imageV) + } + + /** + * 播放录音 + * + * @param name 录音名称 + * @Description + */ + private fun playMusic(name: String, imageV: ImageView) { + imageV.setBackgroundResource(R.drawable.sound_anim) + animaV = imageV.background as AnimationDrawable + animaV!!.start() + if (name.indexOf(".pcm") > 0) { + audioTrackPlay(name, imageV) + } else { + mediaPlayer(name, imageV) + } + } + + private fun mediaPlayer(name: String, imageV: ImageView) { + try { + if (mMediaPlayer.isPlaying) { + mMediaPlayer.stop() + } + mMediaPlayer.reset() + mMediaPlayer.setDataSource(name) + mMediaPlayer.prepare() + mMediaPlayer.start() + //播放结束 + mMediaPlayer.setOnCompletionListener { + animaV!!.stop() + imageV.setBackgroundResource(R.drawable.icon_sound_03) + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + private fun audioTrackPlay(name: String, imageV: ImageView) { + var dis: DataInputStream? = null + try { + //从音频文件中读取声音 + dis = DataInputStream(BufferedInputStream(FileInputStream(name))) + } catch (e: FileNotFoundException) { + e.printStackTrace() + } + //最小缓存区 + val bufferSizeInBytes = AudioTrack.getMinBufferSize( + 16000, + AudioFormat.CHANNEL_OUT_MONO, + AudioFormat.ENCODING_PCM_16BIT + ) + //创建AudioTrack对象 依次传入 :流类型、采样率(与采集的要一致)、音频通道(采集是IN 播放时OUT)、量化位数、最小缓冲区、模式 + if (mAudioTrack != null) { + mAudioTrack!!.stop() + mAudioTrack!!.release() + mAudioTrack = null + } + mAudioTrack = AudioTrack( + AudioManager.STREAM_MUSIC, + 16000, + AudioFormat.CHANNEL_OUT_MONO, + AudioFormat.ENCODING_PCM_16BIT, + bufferSizeInBytes, + AudioTrack.MODE_STREAM + ) + val data = ByteArray(bufferSizeInBytes) + mAudioTrack!!.play() //开始播放 + while (true) { + var i = 0 + try { + while (dis!!.available() > 0 && i < data.size) { + data[i] = dis.readByte() //录音时write Byte 那么读取时就该为readByte要相互对应 + i++ + } + } catch (e: IOException) { + // TODO Auto-generated catch block + e.printStackTrace() + } + mAudioTrack!!.write(data, 0, data.size) + if (i != bufferSizeInBytes) //表示读取完了 + { + break + } + } + mAudioTrack!!.stop() //停止播放 + mAudioTrack!!.release() //释放资源 + mAudioTrack = null + imageV.post { + animaV?.stop() + imageV.setBackgroundResource(R.drawable.icon_sound_03) + } + } + + fun setOnItemClickListener(clickListner: OnItemClickListner) { + itemClick = clickListner + } + + interface OnItemClickListner { + fun onItemClick(view: View?, postion: Int) + } +} + + diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/tasklist/TaskListAdapter.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/tasklist/TaskListAdapter.kt index 5195f2d3..53e75ae7 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/fragment/tasklist/TaskListAdapter.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/tasklist/TaskListAdapter.kt @@ -198,7 +198,7 @@ class TaskListAdapter( } override fun getItemViewRes(position: Int): Int { - return R.layout.adapter_offline_map_city + return R.layout.adapter_task_list } } diff --git a/app/src/main/java/com/navinfo/omqs/util/SoundMeter.java b/app/src/main/java/com/navinfo/omqs/util/SoundMeter.java new file mode 100644 index 00000000..44efd859 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/util/SoundMeter.java @@ -0,0 +1,161 @@ +package com.navinfo.omqs.util; + +import android.media.MediaRecorder; +import android.os.Environment; +import android.text.TextUtils; +import android.util.Log; +import java.io.File; +import java.io.IOException; + +/** + * 录音接口 + */ +public class SoundMeter { + static final private double EMA_FILTER = 0.6; + + private static final String TAG = "SoundMeter"; + private String mFilePath; + private MediaRecorder mRecorder = null; + private double mEMA = 0.0; + //监听 + private OnSoundMeterListener mListener; + //是否开启了语音录制 + private boolean isStartSound; + /** + * 开始录音 + * + * @param name 录音文件保存路径 + */ + public void start(final String name) { + mFilePath = name; + isStartSound = false; + //执行录音操作 + if (!Environment.getExternalStorageState().equals( + Environment.MEDIA_MOUNTED) || TextUtils.isEmpty(name)) { + if(mListener!=null) + mListener.onfaild("权限失败或者文件名称错误"); + return; + } + + if (mRecorder == null) { + mRecorder = new MediaRecorder(); + mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); + mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); + mRecorder.setOutputFile(name); + Log.w(TAG, "录音" + name); + + try { + mRecorder.prepare(); + mRecorder.start(); + mEMA = 0.0; + isStartSound = true; + } catch (IllegalStateException e) { + if(mListener!=null) + mListener.onfaild(e.getMessage()); + if (mRecorder != null) + mRecorder.release(); + //启动异常释放资源 + isStartSound = false; + mRecorder = null; + System.out.print(e.getMessage()); + } catch (IOException e) { + System.out.print(e.getMessage()); + if(mListener!=null) + mListener.onfaild(e.getMessage()); + //启动异常释放资源 + isStartSound = false; + if (mRecorder != null) + mRecorder.release(); + mRecorder = null; + }finally { + + } + } + } + + /** + * 结束录音接,释放录音对象 + */ + public void stop() { + isStartSound = false; + try { + if (mRecorder != null) { + mRecorder.stop(); + } + if(new File(mFilePath).exists()){ + if(mListener!=null) + mListener.onSuccess(mFilePath); + } + } catch (Exception e) { + if(mListener!=null) + mListener.onfaild(e.getMessage()); + } finally { + if (mRecorder != null) + mRecorder.release(); + mRecorder = null; + } + } + + /** + * 停止录音 + */ + public void pause() { + if (mRecorder != null) { + mRecorder.stop(); + } + } + + /** + * 开始录音 + */ + public void start() { + if (mRecorder != null) { + mRecorder.start(); + } + } + + /** + * 获取录音基准值 + * + * @return + */ + public double getAmplitude() { + if (mRecorder != null) + return (mRecorder.getMaxAmplitude() / 2700.0); + else + return 0; + + } + + /** + * 获取EMA基准值 + * + * @return + */ + public double getAmplitudeEMA() { + double amp = getAmplitude(); + mEMA = EMA_FILTER * amp + (1.0 - EMA_FILTER) * mEMA; + return mEMA; + } + + public OnSoundMeterListener getmListener() { + return mListener; + } + + public void setmListener(OnSoundMeterListener mListener) { + this.mListener = mListener; + } + + //是否开启了语音录制 + public boolean isStartSound(){ + return isStartSound; + } + + //录音监听 + public interface OnSoundMeterListener{ + public void onSuccess(String filePath); + public void onfaild(String message); + } + +} diff --git a/app/src/main/java/com/navinfo/omqs/util/SoundRecordeUtils.java b/app/src/main/java/com/navinfo/omqs/util/SoundRecordeUtils.java new file mode 100644 index 00000000..1a902cad --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/util/SoundRecordeUtils.java @@ -0,0 +1,129 @@ +package com.navinfo.omqs.util; + +import android.app.Activity; +import android.graphics.drawable.AnimationDrawable; +import android.graphics.drawable.BitmapDrawable; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.PopupWindow; +import android.widget.Toast; +import com.blankj.utilcode.util.ToastUtils; +import com.navinfo.omqs.Constant; +import com.navinfo.omqs.R; +import java.io.File; + +public class SoundRecordeUtils { + private static SoundRecordeUtils instance; + private SoundMeter mSensor; // 系统录音组件 + private PopupWindow pop; + private ImageView volume; + private SpeakMode mSpeakMode; + private Activity mActivity; + + public static SoundRecordeUtils getInstance(Activity context) { + if (instance == null) { + instance = new SoundRecordeUtils(context); + } + return instance; + } + + public SoundRecordeUtils(Activity mContext) { + this.mActivity = mContext; + mSpeakMode = new SpeakMode(mContext); + initVoicePop(); + } + + private void initVoicePop() { + //语音识别动画 + pop = new PopupWindow(); + pop.setWidth(ViewGroup.LayoutParams.MATCH_PARENT); + pop.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); + pop.setBackgroundDrawable(new BitmapDrawable()); + View view = View.inflate(mActivity, R.layout.cv_card_voice_rcd_hint_window, null); + pop.setContentView(view); + + pop.update(); + volume = (ImageView) view.findViewById(R.id.volume); + } + + //启动录音 + public String startSoundMeter(View v, SoundRecordeCallback soundRecordeCallback, boolean isRemind/*是否需要默认语音提醒开始和结束录音*/) { + //录音动画 + if (pop != null) + pop.showAtLocation(v, Gravity.CENTER, 0, 0); + volume.setBackgroundResource(R.drawable.pop_voice_img); + AnimationDrawable animation = (AnimationDrawable) volume.getBackground(); + animation.start(); + + final String name = DateTimeUtil.getTimeSSS() + ".m4a"; + if (mSensor == null) { + mSensor = new SoundMeter(); + } + mSensor.setmListener(new SoundMeter.OnSoundMeterListener() { + @Override + public void onSuccess(String filePath) { + if (isRemind) { + mSpeakMode.speakText("结束录音"); + } + if (soundRecordeCallback!=null) { + soundRecordeCallback.onSuccess(filePath, name); + } + } + + @Override + public void onfaild(String message) { + if (isRemind) { + ToastUtils.showLong("录制失败!"); + mSpeakMode.speakText("录制失败"); + } + if (soundRecordeCallback!=null) { + soundRecordeCallback.onfaild(message); + } + } + }); + //增加下目录创建,防止由于目录导致无法录制文件 + if (!new File(Constant.USER_DATA_ATTACHEMNT_PATH).exists()) { + new File(Constant.USER_DATA_ATTACHEMNT_PATH).mkdirs(); + } + if (mSensor.isStartSound()) { + ToastUtils.showLong("已自动结束上一段录音"); + mSpeakMode.speakText("已自动结束上一段录音"); + return null; + } + //启动定时器 + mSensor.start(Constant.USER_DATA_ATTACHEMNT_PATH + name); + if (isRemind) { + ToastUtils.showLong("开始录音"); + mSpeakMode.speakText("开始录音"); + } + return name; + } + + //判断是否启动了录音 + public boolean isStartSound(){ + if(mSensor!=null){ + return mSensor.isStartSound(); + } + + return false; + } + + //停止语音录制 + public void stopSoundMeter() { + //先重置标识,防止按钮抬起时触发语音结束 + + if (mSensor != null && mSensor.isStartSound()) { + mSensor.stop(); + } + + if (pop != null && pop.isShowing()) + pop.dismiss(); + } + + public interface SoundRecordeCallback{ + public void onSuccess(String filePath, String fileName); + public void onfaild(String message); + } +} diff --git a/app/src/main/java/com/navinfo/omqs/util/SpeakMode.java b/app/src/main/java/com/navinfo/omqs/util/SpeakMode.java new file mode 100644 index 00000000..1930e611 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/util/SpeakMode.java @@ -0,0 +1,149 @@ +package com.navinfo.omqs.util; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.speech.tts.TextToSpeech; +import android.util.Log; +import android.view.View; + +import com.navinfo.omqs.ui.dialog.FirstDialog; + +import java.util.HashMap; +import java.util.Locale; + +//语音类 +public class SpeakMode extends Activity implements TextToSpeech.OnInitListener{ + private Activity mActivity; + private TextToSpeech mTextToSpeech;//TTS对象 + private int status; + private int MY_DATA_CHECK_CODE = 0; + + private Handler mHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case 0x11: + try { + HashMap<String, String> params = new HashMap<String, String>(); + + params.put(TextToSpeech.Engine.KEY_PARAM_STREAM, "STREAM_NOTIFICATION");//设置播放类型(音频流类型) + + mTextToSpeech.speak(msg.obj + "", TextToSpeech.QUEUE_ADD, params);//将这个发音任务添加当前任务之后 + + //BaseToast.makeText(mActivity,msg.obj+"",Toast.LENGTH_LONG).show(); + + mTextToSpeech.playSilence(100, TextToSpeech.QUEUE_ADD, params);//间隔多长时间 + + } catch (Exception e) { + + } + break; + } + } + }; + + public SpeakMode(Activity activity) { + + mActivity = activity; + + if (mActivity != null && !mActivity.isFinishing()) + this.mTextToSpeech = new TextToSpeech(this.mActivity, this); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent checkIntent = new Intent(); + checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA); + startActivityForResult(checkIntent, MY_DATA_CHECK_CODE); + } + + public void setData(String json) { + + } + + @Override + public void onInit(int status) { + this.status = status; + if (this.mTextToSpeech != null) { + int result = this.mTextToSpeech.setLanguage(Locale.CHINESE); + if (result == TextToSpeech.LANG_MISSING_DATA + || result == TextToSpeech.LANG_NOT_SUPPORTED) { + if (mActivity != null && !mActivity.isFinishing()) { + FirstDialog firstDialog = new FirstDialog(mActivity); + firstDialog.setTitle("提示"); + firstDialog.setMessage("设备不支持语音播报,请先下载语音助手。"); + firstDialog.setConfirmListener(new FirstDialog.OnClickListener() { + @Override + public void onClick(Dialog dialog, int which) { + dialog.dismiss(); + } + }); + firstDialog.setNegativeView(View.GONE); + firstDialog.show(); + } + } + } + Log.i("TextToSpeechDemo", String.valueOf(status)); + } + + //读语音处理 + public void speakText(final String message) { + new Thread(new Runnable() { + @Override + public void run() { + + if (mTextToSpeech != null) { + + int result = mTextToSpeech.setLanguage(Locale.CHINESE); + + if (result == TextToSpeech.LANG_MISSING_DATA + || result == TextToSpeech.LANG_NOT_SUPPORTED) { + + } else { + if (mTextToSpeech != null && mTextToSpeech.isSpeaking()) { + + while (mTextToSpeech.isSpeaking()) { + + try { + //增加播报停止,解决不能播报最新内容问题 + mTextToSpeech.stop(); + + Thread.sleep(100); + + } catch (Exception e) { + + } + } + } + + Message msg = new Message(); + msg.what = 0x11; + msg.obj = message; + mHandler.sendMessage(msg); + + } + } + } + }).start(); + + } + + public void stopSpeek() { + try { + + if (this.mTextToSpeech != null && this.mTextToSpeech.isSpeaking()) { + this.mTextToSpeech.stop(); + } + } catch (Exception e) { + + } + } + +} diff --git a/app/src/main/res/color/font_blue_reg.xml b/app/src/main/res/color/font_blue_reg.xml new file mode 100644 index 00000000..99b71983 --- /dev/null +++ b/app/src/main/res/color/font_blue_reg.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_checked="true" android:color="#FF3030"></item> + <item android:state_selected="true" android:color="#FF3030"></item> + <item android:color="#1ABBFE"></item> +</selector> diff --git a/app/src/main/res/drawable-hdpi/rcd_cancel_icon.png b/app/src/main/res/drawable-hdpi/rcd_cancel_icon.png new file mode 100644 index 00000000..b1b2b06a Binary files /dev/null and b/app/src/main/res/drawable-hdpi/rcd_cancel_icon.png differ diff --git a/app/src/main/res/drawable-hdpi/voice_rcd_cancel_bg_focused.png b/app/src/main/res/drawable-hdpi/voice_rcd_cancel_bg_focused.png new file mode 100644 index 00000000..4190494f Binary files /dev/null and b/app/src/main/res/drawable-hdpi/voice_rcd_cancel_bg_focused.png differ diff --git a/app/src/main/res/drawable-hdpi/voice_rcd_hint_bg.9.png b/app/src/main/res/drawable-hdpi/voice_rcd_hint_bg.9.png new file mode 100644 index 00000000..aa325e17 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/voice_rcd_hint_bg.9.png differ diff --git a/app/src/main/res/drawable-hdpi/voice_to_short.png b/app/src/main/res/drawable-hdpi/voice_to_short.png new file mode 100644 index 00000000..ae7bc3e9 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/voice_to_short.png differ diff --git a/app/src/main/res/drawable-xhdpi/amp1.png b/app/src/main/res/drawable-xhdpi/amp1.png new file mode 100644 index 00000000..3a94df9b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/amp1.png differ diff --git a/app/src/main/res/drawable-xhdpi/amp2.png b/app/src/main/res/drawable-xhdpi/amp2.png new file mode 100644 index 00000000..f1b1adf7 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/amp2.png differ diff --git a/app/src/main/res/drawable-xhdpi/amp3.png b/app/src/main/res/drawable-xhdpi/amp3.png new file mode 100644 index 00000000..beb72b00 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/amp3.png differ diff --git a/app/src/main/res/drawable-xhdpi/amp4.png b/app/src/main/res/drawable-xhdpi/amp4.png new file mode 100644 index 00000000..8d41ae8c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/amp4.png differ diff --git a/app/src/main/res/drawable-xhdpi/amp5.png b/app/src/main/res/drawable-xhdpi/amp5.png new file mode 100644 index 00000000..6791bd7b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/amp5.png differ diff --git a/app/src/main/res/drawable-xhdpi/amp6.png b/app/src/main/res/drawable-xhdpi/amp6.png new file mode 100644 index 00000000..5afef7e2 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/amp6.png differ diff --git a/app/src/main/res/drawable-xhdpi/amp7.png b/app/src/main/res/drawable-xhdpi/amp7.png new file mode 100644 index 00000000..d2ad7fd3 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/amp7.png differ diff --git a/app/src/main/res/drawable-xhdpi/icon_sound_01.png b/app/src/main/res/drawable-xhdpi/icon_sound_01.png new file mode 100644 index 00000000..2862f172 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/icon_sound_01.png differ diff --git a/app/src/main/res/drawable-xhdpi/icon_sound_02.png b/app/src/main/res/drawable-xhdpi/icon_sound_02.png new file mode 100644 index 00000000..f9e67e63 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/icon_sound_02.png differ diff --git a/app/src/main/res/drawable-xhdpi/icon_sound_03.png b/app/src/main/res/drawable-xhdpi/icon_sound_03.png new file mode 100644 index 00000000..3e5eadb4 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/icon_sound_03.png differ diff --git a/app/src/main/res/drawable-xxhdpi/amp1.png b/app/src/main/res/drawable-xxhdpi/amp1.png new file mode 100644 index 00000000..3a94df9b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/amp1.png differ diff --git a/app/src/main/res/drawable-xxhdpi/amp2.png b/app/src/main/res/drawable-xxhdpi/amp2.png new file mode 100644 index 00000000..f1b1adf7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/amp2.png differ diff --git a/app/src/main/res/drawable-xxhdpi/amp3.png b/app/src/main/res/drawable-xxhdpi/amp3.png new file mode 100644 index 00000000..beb72b00 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/amp3.png differ diff --git a/app/src/main/res/drawable-xxhdpi/amp4.png b/app/src/main/res/drawable-xxhdpi/amp4.png new file mode 100644 index 00000000..8d41ae8c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/amp4.png differ diff --git a/app/src/main/res/drawable-xxhdpi/amp5.png b/app/src/main/res/drawable-xxhdpi/amp5.png new file mode 100644 index 00000000..6791bd7b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/amp5.png differ diff --git a/app/src/main/res/drawable-xxhdpi/amp6.png b/app/src/main/res/drawable-xxhdpi/amp6.png new file mode 100644 index 00000000..5afef7e2 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/amp6.png differ diff --git a/app/src/main/res/drawable-xxhdpi/amp7.png b/app/src/main/res/drawable-xxhdpi/amp7.png new file mode 100644 index 00000000..d2ad7fd3 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/amp7.png differ diff --git a/app/src/main/res/drawable-xxhdpi/icon_sound_01.png b/app/src/main/res/drawable-xxhdpi/icon_sound_01.png new file mode 100644 index 00000000..2862f172 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/icon_sound_01.png differ diff --git a/app/src/main/res/drawable-xxhdpi/icon_sound_02.png b/app/src/main/res/drawable-xxhdpi/icon_sound_02.png new file mode 100644 index 00000000..f9e67e63 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/icon_sound_02.png differ diff --git a/app/src/main/res/drawable-xxhdpi/icon_sound_03.png b/app/src/main/res/drawable-xxhdpi/icon_sound_03.png new file mode 100644 index 00000000..3e5eadb4 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/icon_sound_03.png differ diff --git a/app/src/main/res/drawable/bg_select_sound_xml.xml b/app/src/main/res/drawable/bg_select_sound_xml.xml new file mode 100644 index 00000000..46b9cf09 --- /dev/null +++ b/app/src/main/res/drawable/bg_select_sound_xml.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_checked="true" android:drawable="@drawable/icon_select_sound_press"/> + <item android:state_selected="true" android:drawable="@drawable/icon_select_sound_press"/> + <item android:drawable="@drawable/icon_select_sound_defaule"/> +</selector> \ No newline at end of file diff --git a/app/src/main/res/drawable/drawable_bg_blue_frame_black_bg_4_radius.xml b/app/src/main/res/drawable/drawable_bg_blue_frame_black_bg_4_radius.xml new file mode 100644 index 00000000..b722d9fb --- /dev/null +++ b/app/src/main/res/drawable/drawable_bg_blue_frame_black_bg_4_radius.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" > + <solid android:color="@color/black" /> + <stroke + android:width="1dp" + android:color="@color/deepskyblue" /> + <corners + android:bottomLeftRadius="5dp" + android:bottomRightRadius="5dp" + android:topLeftRadius="5dp" + android:topRightRadius="5dp" /> + <padding + android:bottom="1dp" + android:left="1dp" + android:right="1dp" + android:top="1dp"/> +</shape> \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_select_sound_defaule.9.png b/app/src/main/res/drawable/icon_select_sound_defaule.9.png new file mode 100644 index 00000000..61f976cd Binary files /dev/null and b/app/src/main/res/drawable/icon_select_sound_defaule.9.png differ diff --git a/app/src/main/res/drawable/icon_select_sound_press.9.png b/app/src/main/res/drawable/icon_select_sound_press.9.png new file mode 100644 index 00000000..a7d26589 Binary files /dev/null and b/app/src/main/res/drawable/icon_select_sound_press.9.png differ diff --git a/app/src/main/res/drawable/pop_voice_img.xml b/app/src/main/res/drawable/pop_voice_img.xml new file mode 100644 index 00000000..270b69d9 --- /dev/null +++ b/app/src/main/res/drawable/pop_voice_img.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false" + > + <item android:drawable="@drawable/amp7" android:duration="90" /> + <item android:drawable="@drawable/amp1" android:duration="90" /> + <item android:drawable="@drawable/amp2" android:duration="90" /> + <item android:drawable="@drawable/amp3" android:duration="90" /> + <item android:drawable="@drawable/amp4" android:duration="90" /> + <item android:drawable="@drawable/amp5" android:duration="90" /> + <item android:drawable="@drawable/amp6" android:duration="90" /> + <item android:drawable="@drawable/amp7" android:duration="90" /> + +</animation-list> diff --git a/app/src/main/res/drawable/sound_anim.xml b/app/src/main/res/drawable/sound_anim.xml new file mode 100644 index 00000000..d2fbaf94 --- /dev/null +++ b/app/src/main/res/drawable/sound_anim.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> + <item android:drawable="@drawable/icon_sound_03" android:duration="200" /> + <item android:drawable="@drawable/icon_sound_02" android:duration="200" /> + <item android:drawable="@drawable/icon_sound_01" android:duration="200" /> + +</animation-list> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 40393b4f..2c7894e8 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -63,7 +63,6 @@ android:layout_height="48dp" android:layout_marginRight="20dp" android:layout_marginBottom="120dp" - android:onClick="@{()->mainActivity.voiceOnclick()}" android:src="@drawable/baseline_keyboard_voice_24" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintRight_toRightOf="parent" /> diff --git a/app/src/main/res/layout/adapter_sound_list.xml b/app/src/main/res/layout/adapter_sound_list.xml new file mode 100644 index 00000000..752ae50d --- /dev/null +++ b/app/src/main/res/layout/adapter_sound_list.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout 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="@color/ivory" + android:paddingLeft="10dp" + android:paddingTop="5dp" + android:paddingRight="10dp" + android:paddingBottom="5dp" + tools:context="com.navinfo.omqs.ui.fragment.evaluationresult.SoundListAdapter"> + + <LinearLayout + android:id="@+id/rl_sound_content" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/bg_select_sound_xml" + android:gravity="center_vertical" + android:minWidth="50dp" + android:orientation="horizontal" + android:padding="5dp"> + + <TextView + android:id="@+id/tv_time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="10dp" + android:layout_marginRight="6dp" + android:layout_toRightOf="@id/tv_chatcontent" + android:gravity="left|center" + android:lineSpacingExtra="2dp" + android:text="" + android:textColor="@color/font_blue_reg" + android:textSize="15sp" /> + + <ImageView + android:id="@+id/iv_sound_anim" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/sound_anim" /> + + </LinearLayout> + +</LinearLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/cv_card_chatting_item_msg_text_left.xml b/app/src/main/res/layout/cv_card_chatting_item_msg_text_left.xml new file mode 100644 index 00000000..0d409ad7 --- /dev/null +++ b/app/src/main/res/layout/cv_card_chatting_item_msg_text_left.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:paddingTop="2dp" + android:paddingBottom="2dp" + > + + <LinearLayout + android:id="@+id/rl_sound_content" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="5dp" + android:minWidth="50dp" + android:orientation="horizontal" + android:gravity="center_vertical" + android:background="@drawable/bg_select_sound_xml" > + + <TextView + android:layout_marginLeft="10dp" + android:id="@+id/tv_time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="6dp" + android:layout_toRightOf="@id/tv_chatcontent" + android:gravity="left|center" + android:lineSpacingExtra="2dp" + android:text="" + android:textColor="@color/font_blue_reg" + android:textSize="15sp" /> + <ImageView + android:id="@+id/iv_sound_anim" + android:background="@drawable/sound_anim" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + </LinearLayout> + +</LinearLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/cv_card_voice_rcd_hint_window.xml b/app/src/main/res/layout/cv_card_voice_rcd_hint_window.xml new file mode 100644 index 00000000..013f836d --- /dev/null +++ b/app/src/main/res/layout/cv_card_voice_rcd_hint_window.xml @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:gravity="center" > + + <LinearLayout + android:id="@+id/voice_rcd_hint_rcding" + android:layout_width="wrap_content" + android:layout_height="140.0dip" + android:gravity="bottom|center" + android:orientation="horizontal" + > + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:background="@drawable/drawable_bg_blue_frame_black_bg_4_radius" + android:orientation="horizontal" + > + <ImageView + android:background="@drawable/pop_voice_img" + android:scaleType="center" + android:id="@+id/volume" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom" + /> + </LinearLayout> + + <LinearLayout + android:id="@+id/del_re" + android:layout_width="140.0dip" + android:layout_height="140.0dip" + android:layout_marginLeft="10.0dip" + android:background="@drawable/voice_rcd_cancel_bg_focused" + android:gravity="center" + android:orientation="vertical" + android:visibility="gone" > + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:text="取消" + android:textColor="#ffffff" + android:textSize="13.0dip" /> + + <ImageView + android:id="@+id/sc_img1" + android:layout_width="75.0dip" + android:layout_height="75.0dip" + android:layout_marginTop="12.0dip" + android:src="@drawable/rcd_cancel_icon" + + /> + </LinearLayout> + </LinearLayout> + + <LinearLayout + android:id="@+id/voice_rcd_hint_tooshort" + android:layout_width="140.0dip" + android:layout_height="140.0dip" + android:background="@drawable/voice_rcd_hint_bg" + android:gravity="center" + android:orientation="vertical" + android:visibility="gone" > + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/voice_to_short" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="15.0dip" + android:text="时间太短" + android:textColor="#ffffff" /> + </LinearLayout> + +</LinearLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_evaluation_result.xml b/app/src/main/res/layout/fragment_evaluation_result.xml index bd0896f0..88a6d93e 100644 --- a/app/src/main/res/layout/fragment_evaluation_result.xml +++ b/app/src/main/res/layout/fragment_evaluation_result.xml @@ -125,10 +125,16 @@ android:layout_margin="5dp" android:background="@color/gray_121" /> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="多媒体" /> + <androidx.recyclerview.widget.RecyclerView android:id="@+id/evaluation_voice_recyclerview" android:layout_width="match_parent" - android:layout_height="80dp" /> + android:layout_height="120dp" /> + </LinearLayout> </androidx.core.widget.NestedScrollView> diff --git a/collect-library/src/main/java/com/navinfo/collect/library/data/dao/impl/INiLocationDao.java b/collect-library/src/main/java/com/navinfo/collect/library/data/dao/impl/INiLocationDao.java index 4bc28536..89c6c185 100644 --- a/collect-library/src/main/java/com/navinfo/collect/library/data/dao/impl/INiLocationDao.java +++ b/collect-library/src/main/java/com/navinfo/collect/library/data/dao/impl/INiLocationDao.java @@ -43,4 +43,7 @@ public interface INiLocationDao { @Query("SELECT * FROM niLocation where tilex>=:minx and tilex<=:maxx and tiley>=:miny and tiley <=:maxy and time>=:startTime and time<=:endTime") List<NiLocation> timeTofindList(int minx, int maxx, int miny, int maxy,long startTime,long endTime); + + @Query("SELECT * FROM niLocation") + List<NiLocation> findAll(); } diff --git a/collect-library/src/main/java/com/navinfo/collect/library/data/entity/QsRecordBean.kt b/collect-library/src/main/java/com/navinfo/collect/library/data/entity/QsRecordBean.kt index a70856ef..d985c1af 100644 --- a/collect-library/src/main/java/com/navinfo/collect/library/data/entity/QsRecordBean.kt +++ b/collect-library/src/main/java/com/navinfo/collect/library/data/entity/QsRecordBean.kt @@ -1,6 +1,7 @@ package com.navinfo.collect.library.data.entity import com.navinfo.collect.library.utils.GeometryToolsKt +import com.navinfo.omqs.bean.Attachment import io.realm.RealmObject import io.realm.RealmSet import io.realm.annotations.PrimaryKey @@ -86,6 +87,8 @@ open class QsRecordBean @JvmOverloads constructor( */ var guideGeometry: String = "", + var attachments:RealmSet<Attachment>, + ) : RealmObject() { fun copy(): QsRecordBean { @@ -104,6 +107,7 @@ open class QsRecordBean @JvmOverloads constructor( confirmUserId = confirmUserId, t_lifecycle = t_lifecycle, t_status = t_status, + attachments = attachments, ) qs.geometry = geometry return qs diff --git a/collect-library/src/main/java/com/navinfo/collect/library/map/source/MapLifeNiLocationTileDataSource.java b/collect-library/src/main/java/com/navinfo/collect/library/map/source/MapLifeNiLocationTileDataSource.java index 52909ec7..fb672ff5 100644 --- a/collect-library/src/main/java/com/navinfo/collect/library/map/source/MapLifeNiLocationTileDataSource.java +++ b/collect-library/src/main/java/com/navinfo/collect/library/map/source/MapLifeNiLocationTileDataSource.java @@ -53,7 +53,8 @@ public class MapLifeNiLocationTileDataSource implements ITileDataSource { if(mEndTime!=0){ list = TraceDataBase.getDatabase(mCon, dbName).getNiLocationDao().timeTofindList(xStart, xEnd, yStart, yEnd,mStartTime,mEndTime); }else{ - list = TraceDataBase.getDatabase(mCon, dbName).getNiLocationDao().findList(xStart, xEnd, yStart, yEnd); + //list = TraceDataBase.getDatabase(mCon, dbName).getNiLocationDao().findList(xStart, xEnd, yStart, yEnd); + list = TraceDataBase.getDatabase(mCon, dbName).getNiLocationDao().findAll(); } Log.e("qj","query"+(list==null?0:list.size())+"==="+xStart+"==="+xEnd+"==="+yStart+"==="+yEnd);