Merge branch 'master' of gitlab.navinfo.com:vivo/navinfovivo

 Conflicts:
	app/build.gradle
	app/src/main/java/com/navinfo/volvo/database/entity/GreetingMessage.kt
	app/src/main/java/com/navinfo/volvo/ui/fragments/message/ObtainMessageViewModel.kt
This commit is contained in:
squallzhjch 2023-01-06 13:48:58 +08:00
commit 0da95021ce
11 changed files with 672 additions and 146 deletions

3
.idea/gradle.xml generated
View File

@ -7,8 +7,7 @@
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="$USER_HOME$/.gradle/wrapper/dists/gradle-7.2-all/260hg96vuh6ex27h9vo47iv4d/gradle-7.2" />
<option name="gradleJvm" value="JDK" />
<option name="gradleJvm" value="11" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />

View File

@ -149,8 +149,10 @@ dependencies {
implementation("com.github.bumptech.glide:glide:4.11.0") {
exclude group: "com.android.support"
}
// https://github.com/JagarYousef/ChatVoicePlayer
implementation 'com.github.JagarYousef:ChatVoicePlayer:1.1.0'
// https://github.com/XiaoGe-1996/ImageViewer
implementation 'com.github.XiaoGe-1996:ImageViewer:v1.0.0'
}
kapt {

View File

@ -4,10 +4,12 @@
<!-- 拍照 -->
<uses-permission
android:name="android.permission.CAMERA"
android:required="false" /> <!-- 网络请求 -->
android:required="false" />
<!-- 网络请求 -->
<uses-permission
android:name="android.permission.INTERNET"
android:required="false" /> <!-- 录音 -->
android:required="false" />
<!-- 录音 -->
<uses-permission
android:name="android.permission.RECORD_AUDIO"
android:required="false" /> <!-- 读写文件 -->

View File

@ -38,9 +38,9 @@ data class GreetingMessage @JvmOverloads constructor(
var sendType: String? = "",
var del: String? = "",
var version: String? = "",
/**
* 附件列表
*/
var attachment: MutableList<Attachment> = mutableListOf(),
// /**
// * 附件列表
// */
// var attachment: MutableList<Attachment> = mutableListOf(),
var read: Boolean = false,
)

View File

@ -0,0 +1,86 @@
package com.navinfo.volvo.http
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import okhttp3.ResponseBody
import retrofit2.Retrofit
import java.io.File
import java.io.IOException
object DownloadManager {
suspend fun download(url: String, file: File): Flow<DownloadState> {
return flow {
val retrofit = Retrofit.Builder()
.baseUrl(UrlUtils.getBaseUrl(url))
.build()
val response = retrofit.create(NavinfoVolvoService::class.java).downloadFile(url).execute()
if (response.isSuccessful) {
saveToFile(response.body()!!, file) {
emit(DownloadState.InProgress(it))
}
emit(DownloadState.Success(file))
} else {
emit(DownloadState.Error(IOException(response.toString())))
}
}.catch {
emit(DownloadState.Error(it))
}.flowOn(Dispatchers.IO)
}
private inline fun saveToFile(responseBody: ResponseBody, file: File, progressListener: (Int) -> Unit) {
val total = responseBody.contentLength()
var bytesCopied = 0
var emittedProgress = 0
file.outputStream().use { output ->
val input = responseBody.byteStream()
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
var bytes = input.read(buffer)
while (bytes >= 0) {
output.write(buffer, 0, bytes)
bytesCopied += bytes
bytes = input.read(buffer)
val progress = (bytesCopied * 100 / total).toInt()
if (progress - emittedProgress > 0) {
progressListener(progress)
emittedProgress = progress
}
}
}
}
}
sealed class DownloadState {
data class InProgress(val progress: Int) : DownloadState()
data class Success(val file: File) : DownloadState()
data class Error(val throwable: Throwable) : DownloadState()
}
interface DownloadCallback {
fun progress(progress: Int)
fun error(throwable: Throwable)
fun success(file: File)
}
object UrlUtils {
/**
* 从url分割出BaseUrl
*/
fun getBaseUrl(url: String): String {
var mutableUrl = url
var head = ""
var index = mutableUrl.indexOf("://")
if (index != -1) {
head = mutableUrl.substring(0, index + 3)
mutableUrl = mutableUrl.substring(index + 3)
}
index = mutableUrl.indexOf("/")
if (index != -1) {
mutableUrl = mutableUrl.substring(0, index + 1)
}
return head + mutableUrl
}
}

View File

@ -2,18 +2,16 @@ package com.navinfo.volvo.http
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.ResponseBody
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
import retrofit2.http.*
import java.io.File
interface NavinfoVolvoService {
@POST("/navi/cardDelivery/insertCardByApp")
fun insertCardByApp(@Body insertData: MutableMap<String, String>)
suspend fun insertCardByApp(@Body insertData: Map<String, String>):DefaultResponse<String>
@POST("/navi/cardDelivery/updateCardByApp")
fun updateCardByApp(@Body updateData: MutableMap<String, String>)
suspend fun updateCardByApp(@Body updateData: Map<String, String>):DefaultResponse<String>
@POST("/navi/cardDelivery/queryCardListByApp")
fun queryCardListByApp(@Body queryData: MutableMap<String, String>)
@POST("/navi/cardDelivery/deleteCardByApp")
@ -22,5 +20,8 @@ interface NavinfoVolvoService {
@Multipart
suspend fun uploadAttachment(@Part attachmentFile: MultipartBody.Part):DefaultResponse<MutableMap<String, String>>
@POST("/img/download")
fun downLoadAttachment(@Body downloadData: MutableMap<String, String>):Call<DefaultResponse<MutableMap<String, String>>>
suspend fun downLoadAttachment(@Body downloadData: Map<String, String>):DefaultResponse<String>
@Streaming
@GET
fun downloadFile(@Url url: String): Call<ResponseBody>
}

View File

@ -1,9 +1,12 @@
package com.navinfo.volvo.ui.fragments.message
import android.content.DialogInterface
import android.graphics.Paint
import android.net.Uri
import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.View.*
import android.view.ViewGroup
@ -13,11 +16,18 @@ import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.Navigation
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.easytools.tools.DateUtils
import com.easytools.tools.DeviceUtils
import com.easytools.tools.DisplayUtils
import com.easytools.tools.FileIOUtils
import com.easytools.tools.FileUtils
import com.easytools.tools.ResourceUtils
import com.easytools.tools.ToastUtils
import com.elvishew.xlog.XLog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -25,17 +35,25 @@ import com.gredicer.datetimepicker.DateTimePickerFragment
import com.hjq.permissions.OnPermissionCallback
import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
import com.navinfo.volvo.R
import com.navinfo.volvo.RecorderLifecycleObserver
import com.navinfo.volvo.databinding.FragmentObtainMessageBinding
import com.navinfo.volvo.database.entity.Attachment
import com.navinfo.volvo.database.entity.AttachmentType
import com.navinfo.volvo.database.entity.GreetingMessage
import com.navinfo.volvo.databinding.FragmentObtainMessageBinding
import com.navinfo.volvo.http.DownloadCallback
import com.navinfo.volvo.ui.markRequiredInRed
import com.navinfo.volvo.util.PhotoLoader
import com.navinfo.volvo.utils.EasyMediaFile
import com.navinfo.volvo.utils.SystemConstant
import com.nhaarman.supertooltips.ToolTip
import indi.liyi.viewer.Utils
import indi.liyi.viewer.ViewData
import top.zibin.luban.Luban
import top.zibin.luban.OnCompressListener
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.util.*
@ -52,6 +70,9 @@ class ObtainMessageFragment: Fragment() {
RecorderLifecycleObserver()
}
private val dateSendFormat = "yyyy-MM-dd HH:mm:ss"
private val dateShowFormat = "yyyy-MM-dd HH:mm"
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
@ -72,37 +93,63 @@ class ObtainMessageFragment: Fragment() {
if(it.name?.isNotEmpty() == true)
binding.tvMessageTitle?.setText(it.name)
if (it.sendDate?.isNotEmpty() == true) {
binding.btnSendTime.text = it.sendDate
// 获取当前发送时间,如果早于当前时间,则显示现在
val sendDate = DateUtils.str2Date(it.sendDate, dateSendFormat)
if (sendDate<=Date()) {
binding.btnSendTime.text = "现在"
} else {
binding.btnSendTime.text = it.sendDate
}
} else { // 如果发送时间此时为空,自动设置发送时间为当前时间
it.sendDate = DateUtils.date2Str(Date(), dateSendFormat)
}
var hasPhoto = false
var hasAudio = false
if (it.attachment.isNotEmpty()) {
// 展示照片文件或录音文件
for (attachment in it.attachment) {
if (attachment.attachmentType == AttachmentType.PIC) {
Glide.with(context!!)
.asBitmap().fitCenter()
.load(attachment.pathUrl)
.into(binding.imgMessageAttachment)
// 显示名称
binding.tvPhotoName.text = attachment.pathUrl.replace("\\", "/").substringAfterLast("/")
hasPhoto = true
// 如果当前attachment文件是本地文件开始尝试网络上传
if (!attachment.pathUrl.startsWith("http")) {
obtainMessageViewModel.uploadAttachment(File(attachment.pathUrl))
}
if (it.imageUrl!=null&&it.imageUrl?.isNotEmpty() == true) {
hasPhoto = true
// Glide.with(this@ObtainMessageFragment)
// .asBitmap().fitCenter()
// .load(it.imageUrl)
// .diskCacheStrategy(DiskCacheStrategy.ALL)
// .into(binding.imgMessageAttachment)
// 如果当前attachment文件是本地文件开始尝试网络上传
val str = it.imageUrl?.replace("\\", "/")
binding.tvPhotoName.getPaint().setFlags(Paint.UNDERLINE_TEXT_FLAG )
if (!str!!.startsWith("http")) {
obtainMessageViewModel.uploadAttachment(File(it.imageUrl), AttachmentType.PIC)
binding.tvPhotoName.text = str.substringAfterLast("/", "picture.jpg")
} else {
if (str.contains("?")) {
binding.tvPhotoName.text = str.substring(str.lastIndexOf("/")+1, str.indexOf("?"))
} else {
binding.tvPhotoName.text = str.substringAfterLast("/")
}
if (attachment.attachmentType == AttachmentType.AUDIO) {
binding.tvAudioName.text = attachment.pathUrl.replace("\\", "/").substringAfterLast("/")
hasAudio = true
}
}
if (it.mediaUrl!=null&&it.mediaUrl?.isNotEmpty() == true) {
hasAudio = true
binding.tvAudioName.getPaint().setFlags(Paint.UNDERLINE_TEXT_FLAG )
// 如果当前attachment文件是本地文件开始尝试网络上传
val str = it.mediaUrl?.replace("\\", "/")
if (!str!!.startsWith("http")) {
obtainMessageViewModel.uploadAttachment(File(it.mediaUrl),AttachmentType.AUDIO)
binding.tvAudioName.text = str.substringAfterLast("/", "audio.m4a")
} else {
if (str.contains("?")) {
binding.tvAudioName.text = str.substring(str.lastIndexOf("/")+1, str.indexOf("?"))
} else {
binding.tvAudioName.text = str.substringAfterLast("/")
}
}
}
binding.layerPhotoResult.visibility = if (hasPhoto) VISIBLE else GONE
binding.layerGetPhoto.visibility = if (hasPhoto) GONE else VISIBLE
// binding.imgMessageAttachment.visibility = if (hasPhoto) VISIBLE else GONE
binding.layerAudioResult.visibility = if (hasAudio) VISIBLE else GONE
binding.layerGetAudio.visibility = if (hasAudio) GONE else VISIBLE
// binding.llAudioPlay.visibility = if (hasAudio) VISIBLE else GONE
}
)
lifecycle.addObserver(recorderLifecycleObserver)
@ -113,16 +160,20 @@ class ObtainMessageFragment: Fragment() {
fun initView() {
// 设置问候信息提示的红色星号
binding.tiLayoutTitle.markRequiredInRed()
binding.tvMessageTitle.addTextChangedListener {
obtainMessageViewModel.updateMessageTitle(it.toString())
}
binding.tvMessageTitle.addTextChangedListener(afterTextChanged = {
obtainMessageViewModel.getMessageLiveData().value?.name = it.toString()
})
binding.edtSendFrom.addTextChangedListener (afterTextChanged = {
obtainMessageViewModel.getMessageLiveData().value?.who = it.toString()
})
binding.imgPhotoDelete.setOnClickListener {
obtainMessageViewModel.updateMessagePic(null)
obtainMessageViewModel.updateMessagePic("")
}
binding.imgAudioDelete.setOnClickListener {
obtainMessageViewModel.updateMessageAudio(null)
obtainMessageViewModel.updateMessageAudio("")
}
val sendToArray = mutableListOf<String>("绑定车辆1(LYVXFEFEXNL754427)")
@ -143,11 +194,11 @@ class ObtainMessageFragment: Fragment() {
val dialog = DateTimePickerFragment.newInstance().mode(0)
dialog.listener = object : DateTimePickerFragment.OnClickListener {
override fun onClickListener(selectTime: String) {
val sendDate = DateUtils.str2Date(selectTime, "yyyy-MM-dd HH:mm")
val sendDate = DateUtils.str2Date(selectTime, dateShowFormat)
if (sendDate <= Date()) {
obtainMessageViewModel.updateMessageSendTime("现在")
obtainMessageViewModel.updateMessageSendTime(DateUtils.date2Str(Date(), dateSendFormat))
} else {
obtainMessageViewModel.updateMessageSendTime(selectTime)
obtainMessageViewModel.updateMessageSendTime(DateUtils.date2Str(sendDate, dateSendFormat))
}
}
@ -193,12 +244,22 @@ class ObtainMessageFragment: Fragment() {
// 用户选择录音文件
binding.btnSelectSound.setOnClickListener {
photoHelper.setCrop(false).selectAudio(activity!!)
// SingleAudioPicker.showPicker(context!!) {
// val audioFile = File(it.contentUri.path)
// ToastUtils.showToast(audioFile.absolutePath)
// if (!audioFile.parentFile.parentFile.absolutePath.equals(SystemConstant.SoundFolder)) {
// val copyResult = FileIOUtils.writeFileFromIS(File(SystemConstant.SoundFolder, audioFile.name), FileInputStream(audioFile))
// XLog.e("拷贝结果:"+copyResult)
// obtainMessageViewModel.updateMessageAudio(File(SystemConstant.SoundFolder, audioFile.name).absolutePath)
// } else {
// obtainMessageViewModel.updateMessageAudio(audioFile.absolutePath)
// }
// }
}
// 开始录音
binding.btnStartRecord.setOnClickListener {
binding.btnStartRecord.setOnTouchListener { view, motionEvent ->
// 申请权限
XXPermissions.with(this)
XXPermissions.with(this@ObtainMessageFragment)
// 申请单个权限
.permission(Permission.RECORD_AUDIO)
.request(object : OnPermissionCallback {
@ -207,17 +268,23 @@ class ObtainMessageFragment: Fragment() {
Toast.makeText(activity, "获取部分权限成功,但部分权限未正常授予", Toast.LENGTH_SHORT).show()
return
}
if (it.isSelected) {
it.isSelected = false
val recorderAudioPath = recorderLifecycleObserver.stopAndReleaseRecorder()
if (File(recorderAudioPath).exists()) {
obtainMessageViewModel.updateMessageAudio(recorderAudioPath)
when(motionEvent.action) {
MotionEvent.ACTION_DOWN-> {
// 申请权限
recorderLifecycleObserver.initAndStartRecorder()
ToastUtils.showToast("开始录音!")
false
}
MotionEvent.ACTION_UP -> {
val recorderAudioPath = recorderLifecycleObserver.stopAndReleaseRecorder()
if (File(recorderAudioPath).exists()) {
obtainMessageViewModel.updateMessageAudio(recorderAudioPath)
}
false
}
else -> {
false
}
} else{
it.isSelected = true
recorderLifecycleObserver.initAndStartRecorder()
}
}
@ -232,6 +299,7 @@ class ObtainMessageFragment: Fragment() {
}
}
})
false
}
// 获取照片文件和音频文件
@ -260,8 +328,16 @@ class ObtainMessageFragment: Fragment() {
if (!it.absolutePath.equals(file?.absolutePath)) {
it?.delete()
}
// 跳转回原Fragment展示拍摄的照片
ViewModelProvider(requireActivity()).get(ObtainMessageViewModel::class.java).updateMessagePic(file!!.absolutePath)
// 如果当前文件不在camera缓存文件夹下则移动该文件
if (!file!!.parentFile.absolutePath.equals(SystemConstant.CameraFolder)) {
val copyResult = FileIOUtils.writeFileFromIS(File(SystemConstant.CameraFolder, fileName), FileInputStream(file))
XLog.e("拷贝结果:"+copyResult)
// 跳转回原Fragment展示拍摄的照片
ViewModelProvider(requireActivity()).get(ObtainMessageViewModel::class.java).updateMessagePic(File(SystemConstant.CameraFolder, fileName).absolutePath)
} else {
// 跳转回原Fragment展示拍摄的照片
ViewModelProvider(requireActivity()).get(ObtainMessageViewModel::class.java).updateMessagePic(file!!.absolutePath)
}
}
override fun onError(e: Throwable) {
@ -270,7 +346,13 @@ class ObtainMessageFragment: Fragment() {
}).launch()
} else if (fileName.endsWith(".mp3")||fileName.endsWith(".wav")||fileName.endsWith(".amr")||fileName.endsWith(".m4a")) {
ToastUtils.showToast(it.absolutePath)
obtainMessageViewModel.updateMessageAudio(it.absolutePath)
if (!it.parentFile.parentFile.absolutePath.equals(SystemConstant.SoundFolder)) {
val copyResult = FileIOUtils.writeFileFromIS(File(SystemConstant.SoundFolder, fileName), FileInputStream(it))
XLog.e("拷贝结果:"+copyResult)
obtainMessageViewModel.updateMessageAudio(File(SystemConstant.SoundFolder, fileName).absolutePath)
} else {
obtainMessageViewModel.updateMessageAudio(it.absolutePath)
}
}
}
@ -279,11 +361,37 @@ class ObtainMessageFragment: Fragment() {
ToastUtils.showToast(it.message)
}
binding.tvAudioName.setOnClickListener {
binding.llAudioPlay.visibility = if (binding.llAudioPlay.visibility == VISIBLE) GONE else VISIBLE
// 判断当前播放的文件是否在缓存文件夹内,如果不在首先下载该文件
val fileUrl = obtainMessageViewModel.getMessageLiveData().value!!.mediaUrl!!
val localFile = obtainMessageViewModel.getLocalFileFromNetUrl(fileUrl, AttachmentType.AUDIO)
if (!localFile.exists()) {
obtainMessageViewModel.downLoadFile(fileUrl, localFile, object: DownloadCallback {
override fun progress(progress: Int) {
}
override fun error(throwable: Throwable) {
}
override fun success(file: File) {
binding.voicePlayerView.setAudio(localFile.absolutePath)
}
})
} else {
binding.voicePlayerView.setAudio(localFile.absolutePath)
}
}
binding.btnObtainMessageBack.setOnClickListener {
Navigation.findNavController(it).popBackStack()
}
binding.btnObtainMessageConfirm.setOnClickListener {
var checkResult = true
val toolTipBackColor = ResourceUtils.getColor(R.color.teal_200)
val toolTipTextColor = ResourceUtils.getColor(R.color.black)
// 检查当前输入数据
val messageData = obtainMessageViewModel.getMessageLiveData().value
if (messageData?.name?.isEmpty() == true) {
@ -291,40 +399,49 @@ class ObtainMessageFragment: Fragment() {
binding.ttTitle
val toolTip = ToolTip()
.withText("请输入问候信息")
.withColor(com.navinfo.volvo.R.color.purple_200)
.withShadow()
.withColor(toolTipBackColor)
.withTextColor(toolTipTextColor)
.withoutShadow()
.withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
toolTipRelativeLayout.showToolTipForView(toolTip, binding.tiLayoutTitle)
}
var hasPic = false
var hasAudio = false
for (attachment in messageData?.attachment!!) {
if (attachment.attachmentType == AttachmentType.PIC) {
hasPic = true
}
if (attachment.attachmentType == AttachmentType.AUDIO) {
hasAudio = true
checkResult = false
} else {
if (messageData?.name!!.length>10) {
val toolTipRelativeLayout =
binding.ttTitle
val toolTip = ToolTip()
.withText("问候信息长度不能超过10")
.withColor(toolTipBackColor)
.withTextColor(toolTipTextColor)
.withoutShadow()
.withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
toolTipRelativeLayout.showToolTipForView(toolTip, binding.tiLayoutTitle)
checkResult = false
}
}
if (!hasPic) {
if (messageData?.imageUrl?.isEmpty() == true) {
val toolTipRelativeLayout =
binding.ttPic
val toolTip = ToolTip()
.withText("需要提供照片文件")
.withColor(com.navinfo.volvo.R.color.purple_200)
.withShadow()
.withColor(toolTipBackColor)
.withTextColor(toolTipTextColor)
.withoutShadow()
.withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
toolTipRelativeLayout.showToolTipForView(toolTip, binding.tvUploadPic)
checkResult = false
}
if (!hasAudio) {
if (messageData?.mediaUrl?.isEmpty() == true) {
val toolTipRelativeLayout =
binding.ttAudio
val toolTip = ToolTip()
.withText("需要提供音频文件")
.withColor(com.navinfo.volvo.R.color.purple_200)
.withShadow()
.withColor(toolTipBackColor)
.withTextColor(toolTipTextColor)
.withoutShadow()
.withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
toolTipRelativeLayout.showToolTipForView(toolTip, binding.tvUploadPic)
checkResult = false
}
if (messageData?.who?.isEmpty()==true) {
@ -332,21 +449,86 @@ class ObtainMessageFragment: Fragment() {
binding.ttSendFrom
val toolTip = ToolTip()
.withText("请输入您的名称")
.withColor(com.navinfo.volvo.R.color.purple_200)
.withShadow()
.withColor(toolTipBackColor)
.withTextColor(toolTipTextColor)
.withoutShadow()
.withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
toolTipRelativeLayout.showToolTipForView(toolTip, binding.edtSendFrom)
checkResult = false
}
if (messageData?.toWho?.isEmpty()==true) {
val toolTipRelativeLayout =
binding.ttSendTo
val toolTip = ToolTip()
.withText("请选择要发送的车辆")
.withColor(com.navinfo.volvo.R.color.purple_200)
.withShadow()
.withColor(toolTipBackColor)
.withTextColor(toolTipTextColor)
.withoutShadow()
.withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
toolTipRelativeLayout.showToolTipForView(toolTip, binding.edtSendTo)
checkResult = false
}
if (checkResult) { // 检查通过
// 检查attachment是否为本地数据如果是本地则弹出对话框尝试上传
val localAttachmentList = mutableListOf<Attachment>()
if (messageData?.imageUrl?.startsWith("http") == false) {
val imageAttachment = Attachment("", messageData.imageUrl!!, AttachmentType.PIC)
localAttachmentList.add(imageAttachment)
}
if (messageData?.mediaUrl?.startsWith("http") == false) {
val audioAttachment = Attachment("", messageData.mediaUrl!!, AttachmentType.AUDIO)
localAttachmentList.add(audioAttachment)
}
if (localAttachmentList.isNotEmpty()) {
MaterialAlertDialogBuilder(context!!)
.setTitle("提示")
.setMessage("当前照片及音频内容需首先上传,是否尝试上传?")
.setPositiveButton("确定", DialogInterface.OnClickListener { dialogInterface, i ->
dialogInterface.dismiss()
for (attachment in localAttachmentList) {
obtainMessageViewModel.uploadAttachment(File(attachment.pathUrl), attachment.attachmentType)
}
})
.setNegativeButton("取消", DialogInterface.OnClickListener {
dialogInterface, i -> dialogInterface.dismiss()
})
.show()
return@setOnClickListener
}
// 检查发送时间
val sendDate = DateUtils.str2Date(messageData?.sendDate, dateSendFormat)
val cal = Calendar.getInstance()
cal.time = Date()
cal.set(Calendar.MINUTE, cal.get(Calendar.MINUTE)+1)
if (cal.time.time < sendDate.time) { // 发送时间设置小于当前时间1分钟前Toast提示用户并自动设置发送时间
messageData?.sendDate = DateUtils.date2Str(cal.time, dateSendFormat)
ToastUtils.showToast("自动调整发送时间为1分钟后发送")
}
// 开始网络提交数据
if (obtainMessageViewModel.getMessageLiveData().value?.id==0L) { // 如果网络id为空则调用更新操作
obtainMessageViewModel.insertCardByApp()
} else {
obtainMessageViewModel.updateCardByApp()
}
}
}
// 点击照片名称
binding.tvPhotoName.setOnClickListener {
val viewData = ViewData()
viewData.imageSrc = obtainMessageViewModel.getMessageLiveData().value!!.imageUrl
viewData.targetX = Utils.dp2px(context, 10F).toFloat()
viewData.targetWidth = DisplayUtils.getScreenWidthPixels(activity) - Utils.dp2px(context, 20F)
viewData.targetHeight = Utils.dp2px(context, 200F)
val viewDataList = listOf(viewData)
binding.imageViewer.overlayStatusBar(true) // ImageViewer 是否会占据 StatusBar 的空间
.viewData(viewDataList) // 图片数据
.imageLoader(PhotoLoader()) // 设置图片加载方式
.showIndex(true) // 是否显示图片索引默认为true
.watch(0) // 开启浏览
}
}
@ -395,4 +577,6 @@ class ObtainMessageFragment: Fragment() {
super.onDestroy()
lifecycle.removeObserver(recorderLifecycleObserver)
}
companion object
}

View File

@ -1,22 +1,30 @@
package com.navinfo.volvo.ui.fragments.message
import androidx.lifecycle.*
import com.easytools.tools.FileIOUtils
import com.easytools.tools.FileUtils
import com.easytools.tools.ToastUtils
import com.elvishew.xlog.XLog
import com.navinfo.volvo.database.entity.Attachment
import com.navinfo.volvo.database.entity.AttachmentType
import com.navinfo.volvo.database.entity.GreetingMessage
import com.navinfo.volvo.http.DownloadCallback
import com.navinfo.volvo.http.DownloadManager
import com.navinfo.volvo.http.DownloadState
import com.navinfo.volvo.http.NavinfoVolvoCall
import com.navinfo.volvo.utils.SystemConstant
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody
import java.io.File
import java.io.FileInputStream
import java.util.*
import javax.inject.Inject
class ObtainMessageViewModel @Inject constructor() : ViewModel() {
class ObtainMessageViewModel: ViewModel() {
private val msgLiveData: MutableLiveData<GreetingMessage> by lazy {
MutableLiveData<GreetingMessage>()
}
@ -37,52 +45,42 @@ class ObtainMessageViewModel @Inject constructor() : ViewModel() {
// 更新消息附件中的照片文件
fun updateMessagePic(picUrl: String?) {
var hasPic = false
// var hasPic = false
for (attachment in this.msgLiveData.value!!.attachment) {
if (attachment.attachmentType == AttachmentType.PIC) {
if (picUrl == null || picUrl.isEmpty()) {
this.msgLiveData.value!!.attachment.remove(attachment)
} else {
attachment.pathUrl = picUrl
}
hasPic = true
}
}
if (!hasPic && picUrl != null) {
this.msgLiveData.value!!.attachment.add(
Attachment(
UUID.randomUUID().toString(),
picUrl,
AttachmentType.PIC
)
)
}
this.msgLiveData.value?.imageUrl = picUrl
// for (attachment in this.msgLiveData.value!!.attachment) {
// if (attachment.attachmentType == AttachmentType.PIC) {
// if (picUrl==null||picUrl.isEmpty()) {
// this.msgLiveData.value!!.attachment.remove(attachment)
// } else {
// attachment.pathUrl = picUrl
// }
// hasPic = true
// }
// }
// if (!hasPic&&picUrl!=null) {
// this.msgLiveData.value!!.attachment.add(Attachment(UUID.randomUUID().toString(), picUrl, AttachmentType.PIC))
// }
this.msgLiveData.postValue(this.msgLiveData.value)
}
// 更新消息附件中的录音文件
fun updateMessageAudio(audioUrl: String?) {
var hasAudio = false
for (attachment in this.msgLiveData.value!!.attachment) {
if (attachment.attachmentType == AttachmentType.AUDIO) {
if (audioUrl == null || audioUrl.isEmpty()) {
this.msgLiveData.value!!.attachment.remove(attachment)
} else {
attachment.pathUrl = audioUrl
}
hasAudio = true
}
}
if (!hasAudio && audioUrl != null) {
this.msgLiveData.value!!.attachment.add(
Attachment(
UUID.randomUUID().toString(),
audioUrl,
AttachmentType.AUDIO
)
)
}
// var hasAudio = false
// for (attachment in this.msgLiveData.value!!.attachment) {
// if (attachment.attachmentType == AttachmentType.AUDIO) {
// if (audioUrl==null||audioUrl.isEmpty()) {
// this.msgLiveData.value!!.attachment.remove(attachment)
// } else {
// attachment.pathUrl = audioUrl
// }
// hasAudio = true
// }
// }
// if (!hasAudio&&audioUrl!=null) {
// this.msgLiveData.value!!.attachment.add(Attachment(UUID.randomUUID().toString(), audioUrl, AttachmentType.AUDIO))
// }
this.msgLiveData.value?.mediaUrl = audioUrl
this.msgLiveData.postValue(this.msgLiveData.value)
}
@ -104,21 +102,59 @@ class ObtainMessageViewModel @Inject constructor() : ViewModel() {
this.msgLiveData.postValue(this.msgLiveData.value)
}
fun uploadAttachment(attachmentFile: File) {
// // 获取照片url
// fun getImageAttachment(attachementList: List<Attachment>): Attachment? {
// for (attachment in attachementList) {
// if (attachment.attachmentType == AttachmentType.PIC) {
// return attachment
// }
// }
// return null
// }
//
// // 获取音频url
// fun getAudioAttachment(attachementList: List<Attachment>): Attachment? {
// for (attachment in attachementList) {
// if (attachment.attachmentType == AttachmentType.AUDIO) {
// return attachment
// }
// }
// return null
// }
// 上传附件文件
fun uploadAttachment(attachmentFile: File, attachmentType: AttachmentType) {
// 启用协程调用网络请求
viewModelScope.launch {
try {
val requestFile: RequestBody =
RequestBody.create("multipart/form-data".toMediaTypeOrNull(), attachmentFile)
val body = MultipartBody.Part.createFormData(
"picture",
attachmentFile.getName(),
requestFile
)
val body = MultipartBody.Part.createFormData("picture", attachmentFile.getName(), requestFile)
val result = NavinfoVolvoCall.getApi().uploadAttachment(body)
XLog.d(result.code)
if (result.code == 200) { // 请求成功
// 获取上传后的结果
val fileKey = result.data?.get("fileKey")
val newFileName = fileKey!!.substringAfterLast("/")
// 修改缓存文件名
if (attachmentType == AttachmentType.PIC) { // 修改当前文件在缓存文件夹的名称
val destFile = File(SystemConstant.CameraFolder, newFileName)
if (destFile.exists()) {
FileUtils.deleteFile(destFile)
}
val copyResult = FileIOUtils.writeFileFromIS(destFile, FileInputStream(attachmentFile))
XLog.e("拷贝结果:"+copyResult)
} else {
val destFile = File(SystemConstant.SoundFolder, newFileName)
if (destFile.exists()) {
FileUtils.deleteFile(destFile)
}
val copyResult = FileIOUtils.writeFileFromIS(destFile, FileInputStream(attachmentFile))
XLog.e("拷贝结果:"+copyResult)
}
if (fileKey!=null) {
downloadAttachment(fileKey, attachmentType)
}
} else {
ToastUtils.showToast(result.msg)
}
@ -128,4 +164,139 @@ class ObtainMessageViewModel @Inject constructor() : ViewModel() {
}
}
}
// 下载附件文件
fun downloadAttachment(fileKey: String, attachmentType: AttachmentType) {
// 启用协程调用网络请求
viewModelScope.launch {
try {
val downloadParam = mapOf("fileKey" to fileKey)
val result = NavinfoVolvoCall.getApi().downLoadAttachment(downloadParam)
XLog.d(result.code)
if (result.code == 200) { // 请求成功
// 获取上传后的结果
val imageUrl = result.data
if (imageUrl!=null) {
XLog.d("downloadAttachment-imageUrl:${imageUrl}")
// 获取到图片的网络地址
if (attachmentType == AttachmentType.PIC) {
updateMessagePic(imageUrl)
} else {
updateMessageAudio(imageUrl)
}
}
} else {
ToastUtils.showToast(result.msg)
}
} catch (e: Exception) {
ToastUtils.showToast(e.message)
XLog.d(e.message)
}
}
}
fun downLoadFile(url: String, destFile: File, downloadCallback: DownloadCallback){
viewModelScope.launch {
DownloadManager.download(
url,
destFile
).collect {
when (it) {
is DownloadState.InProgress -> {
XLog.d("~~~", "download in progress: ${it.progress}.")
downloadCallback.progress(it.progress)
}
is DownloadState.Success -> {
XLog.d("~~~", "download finished.")
downloadCallback.success(it.file)
}
is DownloadState.Error -> {
XLog.d("~~~", "download error: ${it.throwable}.")
downloadCallback.error(it.throwable)
}
}
}
}
}
fun insertCardByApp() {
viewModelScope.launch {
try {
// TODO 首先保存数据到本地
val message = msgLiveData.value
val insertData = mapOf(
"name" to message?.name,
"imageUrl" to message?.imageUrl,
"mediaUrl" to message?.mediaUrl,
"who" to message?.who,
"toWho" to message?.toWho,
"sendDate" to message?.sendDate
)
val result = NavinfoVolvoCall.getApi().insertCardByApp(insertData as Map<String, String>)
XLog.d("insertCardByApp:${result.code}")
if (result.code == 200) { // 请求成功
// 获取上传后的结果
val netId = result.data
message?.id = netId!!.toLong()
ToastUtils.showToast("保存成功")
// TODO 尝试更新本地数据
} else {
ToastUtils.showToast(result.msg)
}
} catch (e: Exception) {
ToastUtils.showToast(e.message)
XLog.d(e.message)
}
}
}
fun updateCardByApp() {
viewModelScope.launch {
try {
val message = msgLiveData.value
val updateData = mapOf(
"id" to message?.id,
"name" to message?.name,
"imageUrl" to message?.imageUrl,
"mediaUrl" to message?.mediaUrl,
"who" to message?.who,
"toWho" to message?.toWho,
"sendDate" to message?.sendDate
)
val result = NavinfoVolvoCall.getApi().updateCardByApp(updateData as Map<String, String>)
XLog.d("updateCardByApp:${result.code}")
if (result.code == 200) { // 请求成功
// 数据更新成功
ToastUtils.showToast("更新成功")
// 尝试保存数据到本地
} else {
ToastUtils.showToast(result.msg)
}
} catch (e: Exception) {
ToastUtils.showToast(e.message)
XLog.d(e.message)
}
}
}
/**
* 根据网络地址获取本地的缓存文件路径
* */
fun getLocalFileFromNetUrl(url: String, attachmentType: AttachmentType):File {
if (url.startsWith("http")) {
val folder = when(attachmentType) {
AttachmentType.PIC-> SystemConstant.CameraFolder
else -> SystemConstant.SoundFolder
}
var name = if (url.contains("?")) {
url.substring(url.lastIndexOf("/")+1, url.indexOf("?"))
} else {
url.substringAfterLast("/")
}
return File(folder, name)
} else {
return File(url)
}
}
}

View File

@ -0,0 +1,50 @@
package com.navinfo.volvo.util;
import android.graphics.drawable.Drawable;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomViewTarget;
import com.bumptech.glide.request.transition.Transition;
import indi.liyi.viewer.ImageLoader;
public class PhotoLoader extends ImageLoader {
@Override
public void displayImage(final Object src, ImageView imageView, final LoadCallback callback) {
Glide.with(imageView.getContext())
.load(src)
.into(new CustomViewTarget<ImageView, Drawable>(imageView) {
@Override
protected void onResourceLoading(@Nullable Drawable placeholder) {
super.onResourceLoading(placeholder);
if(callback!=null){
callback.onLoadStarted(placeholder);
}
}
@Override
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
if(callback!=null) {
callback.onLoadSucceed(resource);
}
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
if(callback!=null) {
callback.onLoadFailed(errorDrawable);
}
}
@Override
protected void onResourceCleared(@Nullable Drawable placeholder) {
}
});
}
}

View File

@ -103,7 +103,7 @@ class EasyMediaFile {
fun selectAudio(activity: Activity) {
isCrop = false
val intent = Intent(Intent.ACTION_PICK, null).apply {
setDataAndType(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, "audio/*")
setDataAndType(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, "*/*")
}
if (Looper.myLooper() == Looper.getMainLooper()) {
selectFileInternal(intent, activity, 1)
@ -131,7 +131,7 @@ class EasyMediaFile {
* 选择文件
*/
private fun selectFileInternal(intent: Intent, activity: Activity, type: Int) {
val resolveInfoList = activity.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
var resolveInfoList = activity.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
if (resolveInfoList.isEmpty()) {
error?.invoke(IllegalStateException("No Activity found to handle Intent "))
} else {

View File

@ -27,7 +27,7 @@
app:counterEnabled="true"
app:counterMaxLength="10"
app:errorEnabled="true"
android:hint="请输入问候信息"
android:hint="问候信息"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
@ -121,9 +121,11 @@
android:orientation="horizontal">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tv_photo_name"
android:layout_width="wrap_content"
android:textColor="@android:color/holo_blue_dark"
android:padding="@dimen/default_widget_padding"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text=""></com.google.android.material.textview.MaterialTextView>
android:layout_weight="1"></com.google.android.material.textview.MaterialTextView>
<Space
android:layout_width="@dimen/default_widget_padding"
android:layout_height="wrap_content"></Space>
@ -147,7 +149,6 @@
<com.nhaarman.supertooltips.ToolTipRelativeLayout
android:id="@+id/tt_pic"
app:layout_constraintTop_toBottomOf="@id/ti_layout_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
@ -156,6 +157,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_marginVertical="@dimen/default_widget_padding"
android:orientation="horizontal">
<TextView
@ -180,7 +182,7 @@
android:layout_height="wrap_content"
style="@style/Widget.Material3.Button.ElevatedButton"
app:icon="@drawable/ic_baseline_fiber_manual_record_24"
android:text="录"
android:text="长按录音"
android:padding="@dimen/default_widget_padding"></com.google.android.material.button.MaterialButton>
<Space
android:layout_width="@dimen/default_widget_padding"
@ -204,8 +206,11 @@
android:orientation="horizontal">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tv_audio_name"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textColor="@android:color/holo_blue_dark"
android:padding="@dimen/default_widget_padding"
android:layout_weight="1"
android:text=""></com.google.android.material.textview.MaterialTextView>
<Space
android:layout_width="@dimen/default_widget_padding"
@ -219,16 +224,36 @@
</LinearLayout>
</LinearLayout>
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/img_sound_play"
android:layout_width="wrap_content"
<!--增加音频播放按钮-->
<LinearLayout
android:id="@+id/ll_audio_play"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@drawable/ic_baseline_volume_up_24"
android:layout_gravity="center"
android:visibility="gone"
android:layout_gravity="center"></com.google.android.material.imageview.ShapeableImageView>
android:orientation="horizontal">
<me.jagar.chatvoiceplayerlibrary.VoicePlayerView
android:id="@+id/voicePlayerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:enableVisualizer="true"
app:visualizationPlayedColor="@color/teal_200"
app:visualizationNotPlayedColor="@color/teal_700"
app:playPauseBackgroundColor="@color/teal_700"
app:timingBackgroundColor="@color/purple_200"
app:seekBarProgressColor="@color/purple_500"
app:showShareButton="false"
app:shareCornerRadius="100"
app:playPauseCornerRadius="100"
app:showTiming="true"
app:viewCornerRadius="100"
app:viewBackground="@android:color/transparent"
app:progressTimeColor="@color/purple_500"
app:seekBarThumbColor="@color/teal_200"/>
</LinearLayout>
<com.nhaarman.supertooltips.ToolTipRelativeLayout
android:id="@+id/tt_audio"
app:layout_constraintTop_toBottomOf="@id/ti_layout_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
@ -283,7 +308,6 @@
<com.nhaarman.supertooltips.ToolTipRelativeLayout
android:id="@+id/tt_send_from"
app:layout_constraintTop_toBottomOf="@id/ti_layout_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
@ -379,4 +403,11 @@
android:layout_weight="1"
android:text="确认提交"></com.google.android.material.button.MaterialButton>
</LinearLayout>
<indi.liyi.viewer.ImageViewer
android:id="@+id/imageViewer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
app:ivr_dragMode="agile" />
</androidx.constraintlayout.widget.ConstraintLayout>