diff --git a/app/build.gradle b/app/build.gradle index ede1fcd..eb4707e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -144,7 +144,7 @@ dependencies { implementation 'com.github.JagarYousef:ChatVoicePlayer:1.1.0' // 图片查看器 https://github.com/XiaoGe-1996/ImageViewer implementation 'com.github.XiaoGe-1996:ImageViewer:v1.0.0' - + implementation 'com.github.majidarabi:AndroidFilePicker:0.2.1' // 数据存储 Preferences DataStore 键值对 implementation "androidx.datastore:datastore-preferences:1.0.0" // 数据存储 Proto DataStore 对象 diff --git a/app/release/app-release.apk b/app/release/app-release.apk new file mode 100644 index 0000000..380cd07 Binary files /dev/null and b/app/release/app-release.apk differ diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json new file mode 100644 index 0000000..d3e5748 --- /dev/null +++ b/app/release/output-metadata.json @@ -0,0 +1,20 @@ +{ + "version": 3, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "com.navinfo.volvo", + "variantName": "release", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "versionCode": 1, + "versionName": "1.0", + "outputFile": "app-release.apk" + } + ], + "elementType": "File" +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/volvo/RecorderLifecycleObserver.kt b/app/src/main/java/com/navinfo/volvo/RecorderLifecycleObserver.kt index 28424e2..1eea4cc 100644 --- a/app/src/main/java/com/navinfo/volvo/RecorderLifecycleObserver.kt +++ b/app/src/main/java/com/navinfo/volvo/RecorderLifecycleObserver.kt @@ -10,7 +10,7 @@ import java.util.* class RecorderLifecycleObserver: DefaultLifecycleObserver { private var mediaRecorder: MediaRecorder? = null - private lateinit var recorderAudioPath: String + private var recorderAudioPath: String = "${SystemConstant.SoundFolder}/${DateUtils.date2Str(Date(), DateUtils.FORMAT_YMDHMS)}.m4a" fun initAndStartRecorder() { recorderAudioPath = "${SystemConstant.SoundFolder}/${DateUtils.date2Str(Date(), DateUtils.FORMAT_YMDHMS)}.m4a" @@ -22,17 +22,22 @@ class RecorderLifecycleObserver: DefaultLifecycleObserver { setOutputFile(recorderAudioPath) try { prepare() + start() } catch (e: Exception) { XLog.e("prepare() failed") } - start() } } fun stopAndReleaseRecorder(): String { - mediaRecorder?.stop() - mediaRecorder?.release() - mediaRecorder = null + try { + mediaRecorder?.stop() + } catch (exception: Exception) { + XLog.e(exception.message) + } finally { + mediaRecorder?.release() + mediaRecorder = null + } return recorderAudioPath } 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 4b06675..be3297f 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 @@ -4,8 +4,6 @@ import android.os.Parcelable import androidx.room.Entity import androidx.room.PrimaryKey import androidx.room.TypeConverters -import kotlinx.android.parcel.Parcelize -import org.jetbrains.annotations.NotNull @Entity(tableName = "GreetingMessage") @TypeConverters(AttachmentConverters::class) @@ -37,7 +35,7 @@ data class GreetingMessage @JvmOverloads constructor( var sendVins: String? = "", var sendType: String? = "", var del: String? = "", - var version: String? = "", + var version: String? = "1", // /** // * 附件列表 // */ diff --git a/app/src/main/java/com/navinfo/volvo/model/VolvoModel.kt b/app/src/main/java/com/navinfo/volvo/model/VolvoModel.kt new file mode 100644 index 0000000..7228905 --- /dev/null +++ b/app/src/main/java/com/navinfo/volvo/model/VolvoModel.kt @@ -0,0 +1,3 @@ +package com.navinfo.volvo.model + +data class VolvoModel(val version: String, val model: String, val num:String) 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 aa33d44..0c66505 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 @@ -13,6 +13,7 @@ import android.widget.AdapterView import android.widget.AdapterView.OnItemSelectedListener import android.widget.ArrayAdapter import android.widget.Toast +import androidx.core.content.ContextCompat import androidx.core.widget.addTextChangedListener import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels @@ -29,6 +30,13 @@ import com.easytools.tools.ResourceUtils import com.easytools.tools.ThreadPoolUtils.runOnUiThread import com.easytools.tools.ToastUtils import com.elvishew.xlog.XLog +import com.github.file_picker.FileType +import com.github.file_picker.ListDirection +import com.github.file_picker.adapter.FilePickerAdapter +import com.github.file_picker.data.model.Media +import com.github.file_picker.extension.showFilePicker +import com.github.file_picker.listener.OnItemClickListener +import com.github.file_picker.listener.OnSubmitClickListener import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.gredicer.datetimepicker.DateTimePickerFragment import com.hjq.permissions.OnPermissionCallback @@ -41,6 +49,7 @@ 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.model.VolvoModel import com.navinfo.volvo.ui.markRequiredInRed import com.navinfo.volvo.util.PhotoLoader import com.navinfo.volvo.utils.EasyMediaFile @@ -55,6 +64,7 @@ import top.zibin.luban.OnCompressListener import java.io.File import java.io.FileInputStream import java.util.* +import kotlin.streams.toList @AndroidEntryPoint @@ -70,6 +80,7 @@ class ObtainMessageFragment : Fragment() { private val dateSendFormat = "yyyy-MM-dd HH:mm:ss" private val dateShowFormat = "yyyy-MM-dd HH:mm" + private var startRecordTime = System.currentTimeMillis() // This property is only valid between onCreateView and // onDestroyView. @@ -155,12 +166,11 @@ class ObtainMessageFragment : Fragment() { binding.layerAudioResult.visibility = if (hasAudio) VISIBLE else GONE binding.layerGetAudio.visibility = if (hasAudio) GONE else VISIBLE - // binding.llAudioPlay.visibility = if (hasAudio) VISIBLE else GONE + if (!hasAudio) { + binding.llAudioPlay.visibility = GONE + } } ) - - - lifecycle.addObserver(recorderLifecycleObserver) initView() return root @@ -184,15 +194,12 @@ class ObtainMessageFragment : Fragment() { binding.imgAudioDelete.setOnClickListener { obtainMessageViewModel.updateMessageAudio("") } - - val sendToArray = mutableListOf("LYVXFEFEXNL754427") - binding.edtSendTo.adapter = ArrayAdapter( - context!!, - android.R.layout.simple_dropdown_item_1line, android.R.id.text1, sendToArray - ) - binding.edtSendTo.onItemSelectedListener = object : OnItemSelectedListener { + val sendToArray = mutableListOf(VolvoModel("XC60", "智雅", "LYVXFEFEXNL754427")) + binding.edtSendTo.adapter = ArrayAdapter(requireContext(), + android.R.layout.simple_dropdown_item_1line, android.R.id.text1, sendToArray.stream().map { it -> "${it.version} ${it.model} ${it.num}" }.toList()) + binding.edtSendTo.onItemSelectedListener = object: OnItemSelectedListener { override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) { - obtainMessageViewModel.getMessageLiveData().value?.toWho = sendToArray[p2] + obtainMessageViewModel.getMessageLiveData().value?.toWho = sendToArray[p2].num } override fun onNothingSelected(p0: AdapterView<*>?) { @@ -242,14 +249,14 @@ class ObtainMessageFragment : Fragment() { return } // 开始启动拍照界面 - photoHelper.setCrop(true).takePhoto(activity!!) + photoHelper.setCrop(true).takePhoto(requireActivity()) } override fun onDenied(permissions: MutableList, never: Boolean) { if (never) { Toast.makeText(activity, "永久拒绝授权,请手动授权拍照权限", Toast.LENGTH_SHORT).show() // 如果是被永久拒绝就跳转到应用权限系统设置页面 - XXPermissions.startPermissionActivity(context!!, permissions) + XXPermissions.startPermissionActivity(requireContext(), permissions) } else { onCameraDenied() showRationaleForCamera(permissions) @@ -260,13 +267,56 @@ class ObtainMessageFragment : Fragment() { } binding.btnStartPhoto.setOnClickListener { - photoHelper.setCrop(true).selectPhoto(activity!!) + photoHelper.setCrop(true).selectPhoto(requireActivity()) } // 用户选择录音文件 binding.btnSelectSound.setOnClickListener { - photoHelper.setCrop(false).selectAudio(activity!!) -// SingleAudioPicker.showPicker(context!!) { +// photoHelper.setCrop(false).selectAudio(requireActivity()) + + showFilePicker( + submitText = "确认", + fileType = FileType.AUDIO, + title = "选择音频文件", + cancellable = true, + listDirection = ListDirection.RTL, + accentColor = ContextCompat.getColor(requireContext(), R.color.purple_700), + titleTextColor = ContextCompat.getColor(requireContext(), R.color.purple_700), + onSubmitClickListener = object : OnSubmitClickListener { + override fun onClick(files: List) { + // Do something here with selected files + val audioFile = files.get(0).file + if (!audioFile.parentFile.parentFile.absolutePath.equals(SystemConstant.SoundFolder)) { + val copyResult = FileIOUtils.writeFileFromIS(File(SystemConstant.SoundFolder, audioFile.name), FileInputStream(audioFile)) + XLog.e("拷贝结果:"+copyResult) + if (!copyResult) { + ToastUtils.showToast("无法访问该文件,请重新选择其他文件") + return + } + obtainMessageViewModel.updateMessageAudio(File(SystemConstant.SoundFolder, audioFile.name).absolutePath) + } else { + obtainMessageViewModel.updateMessageAudio(audioFile.absolutePath) + } + } + }, + onItemClickListener = object : OnItemClickListener { + override fun onClick(media: Media, position: Int, adapter: FilePickerAdapter) { + if (!media.file.isDirectory) { + if (!media.file.name.endsWith(".m4a")) { + ToastUtils.showToast("只能选择.m4a文件") + return + } + if (media.file.length()>2*1000*1000) { + ToastUtils.showToast("文件不能超过2M!") + return + } + adapter.setSelected(position) + } + } + } + ) + +// SingleAudioPicker.showPicker(requireContext()) { // val audioFile = File(it.contentUri.path) // ToastUtils.showToast(audioFile.absolutePath) // if (!audioFile.parentFile.parentFile.absolutePath.equals(SystemConstant.SoundFolder)) { @@ -295,12 +345,16 @@ class ObtainMessageFragment : Fragment() { MotionEvent.ACTION_DOWN -> { // 申请权限 recorderLifecycleObserver.initAndStartRecorder() - ToastUtils.showToast("开始录音!") + startRecordTime = System.currentTimeMillis() false } MotionEvent.ACTION_UP -> { - val recorderAudioPath = + if (System.currentTimeMillis() - startRecordTime<2000) { + ToastUtils.showToast("录音时间太短!") recorderLifecycleObserver.stopAndReleaseRecorder() + return + } + val recorderAudioPath = recorderLifecycleObserver.stopAndReleaseRecorder() if (File(recorderAudioPath).exists()) { obtainMessageViewModel.updateMessageAudio(recorderAudioPath) } @@ -316,7 +370,7 @@ class ObtainMessageFragment : Fragment() { if (never) { Toast.makeText(activity, "永久拒绝授权,请手动授权拍照权限", Toast.LENGTH_SHORT).show() // 如果是被永久拒绝就跳转到应用权限系统设置页面 - XXPermissions.startPermissionActivity(context!!, permissions) + XXPermissions.startPermissionActivity(requireContext(), permissions) } else { onCameraDenied() showRationaleForCamera(permissions) @@ -531,7 +585,7 @@ class ObtainMessageFragment : Fragment() { localAttachmentList.add(audioAttachment) } if (localAttachmentList.isNotEmpty()) { - MaterialAlertDialogBuilder(context!!) + MaterialAlertDialogBuilder(requireContext()) .setTitle("提示") .setMessage("当前照片及音频内容需首先上传,是否尝试上传?") .setPositiveButton( @@ -558,10 +612,13 @@ class ObtainMessageFragment : Fragment() { 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提示用户并自动设置发送时间 + cal.set(Calendar.MINUTE, cal.get(Calendar.MINUTE)+1) + if (sendDate.time < cal.time.time) { // 发送时间设置小于当前时间1分钟后,Toast提示用户并自动设置发送时间 messageData?.sendDate = DateUtils.date2Str(cal.time, dateSendFormat) ToastUtils.showToast("自动调整发送时间为1分钟后发送") + messageData.version = "1" // 立即发送 + } else { + messageData.version = "0" // 预约发送 } // 开始网络提交数据 @@ -610,12 +667,12 @@ class ObtainMessageFragment : Fragment() { fun showRationaleForCamera(permissions: MutableList) { // showRationaleDialog(R.string.permission_camera_rationale, request) // Toast.makeText(context, "当前操作需要您授权相机权限!", Toast.LENGTH_SHORT).show() - MaterialAlertDialogBuilder(context!!) + MaterialAlertDialogBuilder(requireContext()) .setTitle("提示") .setMessage("当前操作需要您授权拍摄权限!") .setPositiveButton("确定", DialogInterface.OnClickListener { dialogInterface, i -> dialogInterface.dismiss() - XXPermissions.startPermissionActivity(activity!!, permissions) + XXPermissions.startPermissionActivity(requireActivity(), permissions) }) .show() } @@ -626,16 +683,15 @@ class ObtainMessageFragment : Fragment() { } fun showRationaleForRecorder(permissions: MutableList) { - MaterialAlertDialogBuilder(context!!) + MaterialAlertDialogBuilder(requireContext()) .setTitle("提示") .setMessage("当前操作需要您授权录音权限!") .setPositiveButton("确定", DialogInterface.OnClickListener { dialogInterface, i -> dialogInterface.dismiss() - XXPermissions.startPermissionActivity(activity!!, permissions) + XXPermissions.startPermissionActivity(requireActivity(), permissions) }) .show() } - fun onRecorderDenied() { ToastUtils.showToast("当前操作需要您授权录音权限!") } 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 49a8e7d..5586849 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 @@ -177,6 +177,7 @@ class ObtainMessageViewModel @Inject constructor( } } else { ToastUtils.showToast(result.msg) + XLog.d(result.msg) } } catch (e: Exception) { ToastUtils.showToast(e.message) @@ -250,7 +251,8 @@ class ObtainMessageViewModel @Inject constructor( "mediaUrl" to message?.mediaUrl, "who" to message?.who, "toWho" to message?.toWho, - "sendDate" to message?.sendDate + "sendDate" to message?.sendDate, + "version" to message?.version ) val result = NavinfoVolvoCall.getApi().insertCardByApp(insertData as Map) @@ -283,7 +285,8 @@ class ObtainMessageViewModel @Inject constructor( "mediaUrl" to message?.mediaUrl, "who" to message?.who, "toWho" to message?.toWho, - "sendDate" to message?.sendDate + "sendDate" to message?.sendDate, + "version" to message?.version ) val result = NavinfoVolvoCall.getApi().updateCardByApp(updateData as Map) diff --git a/app/src/main/res/layout/fragment_obtain_message.xml b/app/src/main/res/layout/fragment_obtain_message.xml index da43056..6808b3c 100644 --- a/app/src/main/res/layout/fragment_obtain_message.xml +++ b/app/src/main/res/layout/fragment_obtain_message.xml @@ -373,7 +373,7 @@ android:layout_height="wrap_content" style="@style/Widget.Material3.Button.ElevatedButton" app:icon="@drawable/ic_baseline_access_time_24" - android:text="现在" + android:text="选择时间" android:padding="@dimen/default_widget_padding">