diff --git a/.idea/gradle.xml b/.idea/gradle.xml index fed8fa3..7b46144 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -7,8 +7,7 @@ - - + diff --git a/app/build.gradle b/app/build.gradle index 24ff887..981e1fe 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 12e07c6..e62f29e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,10 +4,12 @@ + android:required="false" /> + + android:required="false" /> + diff --git a/app/src/main/java/com/navinfo/volvo/database/entity/GreetingMessage.kt b/app/src/main/java/com/navinfo/volvo/database/entity/GreetingMessage.kt index 4bca705..4b06675 100644 --- a/app/src/main/java/com/navinfo/volvo/database/entity/GreetingMessage.kt +++ b/app/src/main/java/com/navinfo/volvo/database/entity/GreetingMessage.kt @@ -38,9 +38,9 @@ data class GreetingMessage @JvmOverloads constructor( var sendType: String? = "", var del: String? = "", var version: String? = "", - /** - * 附件列表 - */ - var attachment: MutableList = mutableListOf(), +// /** +// * 附件列表 +// */ +// var attachment: MutableList = mutableListOf(), var read: Boolean = false, ) diff --git a/app/src/main/java/com/navinfo/volvo/http/DownloadManager.kt b/app/src/main/java/com/navinfo/volvo/http/DownloadManager.kt new file mode 100644 index 0000000..666b263 --- /dev/null +++ b/app/src/main/java/com/navinfo/volvo/http/DownloadManager.kt @@ -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 { + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/volvo/http/NavinfoVolvoService.kt b/app/src/main/java/com/navinfo/volvo/http/NavinfoVolvoService.kt index e4ea112..206d55a 100644 --- a/app/src/main/java/com/navinfo/volvo/http/NavinfoVolvoService.kt +++ b/app/src/main/java/com/navinfo/volvo/http/NavinfoVolvoService.kt @@ -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) + suspend fun insertCardByApp(@Body insertData: Map):DefaultResponse @POST("/navi/cardDelivery/updateCardByApp") - fun updateCardByApp(@Body updateData: MutableMap) + suspend fun updateCardByApp(@Body updateData: Map):DefaultResponse @POST("/navi/cardDelivery/queryCardListByApp") fun queryCardListByApp(@Body queryData: MutableMap) @POST("/navi/cardDelivery/deleteCardByApp") @@ -22,5 +20,8 @@ interface NavinfoVolvoService { @Multipart suspend fun uploadAttachment(@Part attachmentFile: MultipartBody.Part):DefaultResponse> @POST("/img/download") - fun downLoadAttachment(@Body downloadData: MutableMap):Call>> + suspend fun downLoadAttachment(@Body downloadData: Map):DefaultResponse + @Streaming + @GET + fun downloadFile(@Url url: String): Call } \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/volvo/ui/fragments/message/ObtainMessageFragment.kt b/app/src/main/java/com/navinfo/volvo/ui/fragments/message/ObtainMessageFragment.kt index e34f10d..b564afc 100644 --- a/app/src/main/java/com/navinfo/volvo/ui/fragments/message/ObtainMessageFragment.kt +++ b/app/src/main/java/com/navinfo/volvo/ui/fragments/message/ObtainMessageFragment.kt @@ -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("绑定车辆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() + 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 } \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/volvo/ui/fragments/message/ObtainMessageViewModel.kt b/app/src/main/java/com/navinfo/volvo/ui/fragments/message/ObtainMessageViewModel.kt index 82dc8a3..3f4eb0c 100644 --- a/app/src/main/java/com/navinfo/volvo/ui/fragments/message/ObtainMessageViewModel.kt +++ b/app/src/main/java/com/navinfo/volvo/ui/fragments/message/ObtainMessageViewModel.kt @@ -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 by lazy { MutableLiveData() } @@ -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? { +// for (attachment in attachementList) { +// if (attachment.attachmentType == AttachmentType.PIC) { +// return attachment +// } +// } +// return null +// } +// +// // 获取音频url +// fun getAudioAttachment(attachementList: List): 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) + 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) + 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) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/volvo/util/PhotoLoader.java b/app/src/main/java/com/navinfo/volvo/util/PhotoLoader.java new file mode 100644 index 0000000..6c69e64 --- /dev/null +++ b/app/src/main/java/com/navinfo/volvo/util/PhotoLoader.java @@ -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) { + + @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) { + + } + }); + } +} diff --git a/app/src/main/java/com/navinfo/volvo/utils/EasyMediaFile.kt b/app/src/main/java/com/navinfo/volvo/utils/EasyMediaFile.kt index e04f57d..b159971 100644 --- a/app/src/main/java/com/navinfo/volvo/utils/EasyMediaFile.kt +++ b/app/src/main/java/com/navinfo/volvo/utils/EasyMediaFile.kt @@ -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 { diff --git a/app/src/main/res/layout/fragment_obtain_message.xml b/app/src/main/res/layout/fragment_obtain_message.xml index 0a482c1..e90f9a4 100644 --- a/app/src/main/res/layout/fragment_obtain_message.xml +++ b/app/src/main/res/layout/fragment_obtain_message.xml @@ -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"> + android:layout_weight="1"> @@ -147,7 +149,6 @@ @@ -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"> - + + android:orientation="horizontal"> + + + @@ -283,7 +308,6 @@ @@ -379,4 +403,11 @@ android:layout_weight="1" android:text="确认提交"> + + \ No newline at end of file