diff --git a/app/build.gradle b/app/build.gradle
index 214f513..93a2907 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -121,6 +121,24 @@ dependencies {
// androidTestImplementation "com.google.dagger:hilt-android-testing:2.41"
// kaptAndroidTest "com.google.dagger:hilt-android-compiler:2.41"
+ // 显示错误提示 https://github.com/nhaarman/supertooltips
+ implementation 'com.nhaarman.supertooltips:library:3.0.0'
+
+ // 权限请求框架:https://github.com/getActivity/XXPermissions
+ implementation 'com.github.getActivity:XXPermissions:16.5'
+
+ // 相机库 https://natario1.github.io/CameraView/about/getting-started
+ implementation("com.otaliastudios:cameraview:2.7.2")
+ // 图片压缩算法 https://github.com/Curzibn/Luban
+ implementation 'top.zibin:Luban:1.1.8'
+ // Android工具类库 https://github.com/gycold/EasyAndroid
+ implementation 'io.github.gycold:easyandroid:2.0.7'
+ // 日志工具 https://github.com/elvishew/xLog/blob/master/README_ZH.md
+ implementation 'com.elvishew:xlog:1.10.1'
+ //加载图片的依赖包
+ implementation ("com.github.bumptech.glide:glide:4.11.0") {
+ exclude group: "com.android.support"
+ }
}
kapt {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ed361f2..0423065 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,6 +1,19 @@
+
+
+
+
+
+
+
+ android:usesCleartextTraffic="true">
+
+
+
-
-
+
+
+
\ 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
new file mode 100644
index 0000000..28424e2
--- /dev/null
+++ b/app/src/main/java/com/navinfo/volvo/RecorderLifecycleObserver.kt
@@ -0,0 +1,66 @@
+package com.navinfo.volvo
+
+import android.media.MediaRecorder
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import com.easytools.tools.DateUtils
+import com.elvishew.xlog.XLog
+import com.navinfo.volvo.utils.SystemConstant
+import java.util.*
+
+class RecorderLifecycleObserver: DefaultLifecycleObserver {
+ private var mediaRecorder: MediaRecorder? = null
+ private lateinit var recorderAudioPath: String
+
+ fun initAndStartRecorder() {
+ recorderAudioPath = "${SystemConstant.SoundFolder}/${DateUtils.date2Str(Date(), DateUtils.FORMAT_YMDHMS)}.m4a"
+ mediaRecorder = MediaRecorder().apply {
+ setAudioSource(MediaRecorder.AudioSource.MIC)
+ setOutputFormat(MediaRecorder.OutputFormat.DEFAULT)
+ setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
+ // 开始录音
+ setOutputFile(recorderAudioPath)
+ try {
+ prepare()
+ } catch (e: Exception) {
+ XLog.e("prepare() failed")
+ }
+ start()
+ }
+ }
+
+ fun stopAndReleaseRecorder(): String {
+ mediaRecorder?.stop()
+ mediaRecorder?.release()
+ mediaRecorder = null
+ return recorderAudioPath
+ }
+
+// override fun onCreate(owner: LifecycleOwner) {
+// super.onCreate(owner)
+//
+// }
+//
+// override fun onStart(owner: LifecycleOwner) {
+// super.onStart(owner)
+// }
+//
+// override fun onResume(owner: LifecycleOwner) {
+// super.onResume(owner)
+// }
+
+ override fun onPause(owner: LifecycleOwner) {
+ super.onPause(owner)
+ mediaRecorder?.pause()
+ }
+
+ override fun onStop(owner: LifecycleOwner) {
+ super.onStop(owner)
+ mediaRecorder?.stop()
+ }
+
+ override fun onDestroy(owner: LifecycleOwner) {
+ super.onDestroy(owner)
+ mediaRecorder?.release()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/volvo/http/DefaultResponse.kt b/app/src/main/java/com/navinfo/volvo/http/DefaultResponse.kt
new file mode 100644
index 0000000..891d756
--- /dev/null
+++ b/app/src/main/java/com/navinfo/volvo/http/DefaultResponse.kt
@@ -0,0 +1,7 @@
+package com.navinfo.volvo.http
+
+class DefaultResponse {
+ var code: Int = 0
+ var message: String = ""
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/volvo/http/NavinfoVolvoCall.kt b/app/src/main/java/com/navinfo/volvo/http/NavinfoVolvoCall.kt
new file mode 100644
index 0000000..3e1bf0c
--- /dev/null
+++ b/app/src/main/java/com/navinfo/volvo/http/NavinfoVolvoCall.kt
@@ -0,0 +1,30 @@
+package com.navinfo.volvo.http
+
+import com.navinfo.volvo.db.dao.entity.Attachment
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+import retrofit2.create
+import java.io.File
+
+class NavinfoVolvoCall {
+ companion object {
+ private val service by lazy {
+ Retrofit.Builder().baseUrl("http://ec2-52-81-73-5.cn-north-1.compute.amazonaws.com.cn:8088/")
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(NavinfoVolvoService::class.java)
+ }
+
+ private var instance: NavinfoVolvoCall? = null
+ get() {
+ if (field == null) {
+ field = NavinfoVolvoCall()
+ }
+ return field
+ }
+
+ fun getApi(): NavinfoVolvoService {
+ return service
+ }
+ }
+}
\ 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
new file mode 100644
index 0000000..e4ea112
--- /dev/null
+++ b/app/src/main/java/com/navinfo/volvo/http/NavinfoVolvoService.kt
@@ -0,0 +1,26 @@
+package com.navinfo.volvo.http
+
+import okhttp3.MultipartBody
+import okhttp3.RequestBody
+import retrofit2.Call
+import retrofit2.http.Body
+import retrofit2.http.Multipart
+import retrofit2.http.POST
+import retrofit2.http.Part
+import java.io.File
+
+interface NavinfoVolvoService {
+ @POST("/navi/cardDelivery/insertCardByApp")
+ fun insertCardByApp(@Body insertData: MutableMap)
+ @POST("/navi/cardDelivery/updateCardByApp")
+ fun updateCardByApp(@Body updateData: MutableMap)
+ @POST("/navi/cardDelivery/queryCardListByApp")
+ fun queryCardListByApp(@Body queryData: MutableMap)
+ @POST("/navi/cardDelivery/deleteCardByApp")
+ fun deleteCardByApp(@Body deleteData: MutableMap)
+ @POST("/img/upload")
+ @Multipart
+ suspend fun uploadAttachment(@Part attachmentFile: MultipartBody.Part):DefaultResponse>
+ @POST("/img/download")
+ fun downLoadAttachment(@Body downloadData: MutableMap):Call>>
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/volvo/ui/MainActivity.kt b/app/src/main/java/com/navinfo/volvo/ui/MainActivity.kt
index 0b439bd..ef554b7 100644
--- a/app/src/main/java/com/navinfo/volvo/ui/MainActivity.kt
+++ b/app/src/main/java/com/navinfo/volvo/ui/MainActivity.kt
@@ -1,13 +1,18 @@
package com.navinfo.volvo.ui
+import android.content.DialogInterface
+import android.content.Intent
+import android.content.DialogInterface
import android.os.Bundle
import android.view.View
+import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import com.google.android.material.bottomnavigation.BottomNavigationView
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.navinfo.volvo.R
import com.navinfo.volvo.databinding.ActivityMainBinding
import dagger.hilt.android.AndroidEntryPoint
@@ -23,6 +28,38 @@ class MainActivity : AppCompatActivity() {
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setupNavigation()
+
+ XXPermissions.with(this)
+ // 申请单个权限
+ .permission(Permission.WRITE_EXTERNAL_STORAGE)
+ .permission(Permission.READ_EXTERNAL_STORAGE)
+ // 设置权限请求拦截器(局部设置)
+ //.interceptor(new PermissionInterceptor())
+ // 设置不触发错误检测机制(局部设置)
+ //.unchecked()
+ .request(object : OnPermissionCallback {
+
+ override fun onGranted(permissions: MutableList, all: Boolean) {
+ if (!all) {
+ Toast.makeText(this@MainActivity, "获取部分权限成功,但部分权限未正常授予", Toast.LENGTH_SHORT).show()
+ return
+ }
+ // 在SD卡创建项目目录
+ createRootFolder()
+ }
+
+ override fun onDenied(permissions: MutableList, never: Boolean) {
+ if (never) {
+ Toast.makeText(this@MainActivity, "永久拒绝授权,请手动授权文件读写权限", Toast.LENGTH_SHORT).show()
+ // 如果是被永久拒绝就跳转到应用权限系统设置页面
+ XXPermissions.startPermissionActivity(this@MainActivity, permissions)
+ } else {
+ onSDCardDenied()
+ showRationaleForSDCard(permissions)
+ }
+ }
+ })
+
}
private fun setupNavigation() {
@@ -57,4 +94,73 @@ class MainActivity : AppCompatActivity() {
override fun onSupportNavigateUp() =
findNavController(R.id.nav_host_fragment_activity_main).navigateUp()
+
+ // @NeedsPermission(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
+ fun createRootFolder() {
+ // 在SD卡创建项目目录
+ val sdCardPath = getExternalFilesDir(null)
+// SystemConstant.ROOT_PATH = "${sdCardPath}/${SystemConstant.FolderName}"
+ SystemConstant.ROOT_PATH = sdCardPath!!.absolutePath
+ SystemConstant.LogFolder = "${sdCardPath!!.absolutePath}/log"
+ FileUtils.createOrExistsDir(SystemConstant.LogFolder)
+ SystemConstant.CameraFolder = "${sdCardPath!!.absolutePath}/camera"
+ FileUtils.createOrExistsDir(SystemConstant.CameraFolder)
+ SystemConstant.SoundFolder = "${sdCardPath!!.absolutePath}/sound"
+ FileUtils.createOrExistsDir(SystemConstant.SoundFolder)
+ xLogInit(SystemConstant.LogFolder)
+ }
+
+ fun xLogInit(logFolder: String) {
+ val config = LogConfiguration.Builder()
+ .logLevel(
+ if (BuildConfig.DEBUG)
+ LogLevel.ALL // 指定日志级别,低于该级别的日志将不会被打印,默认为 LogLevel.ALL
+ else LogLevel.NONE
+ )
+ .tag("Volvo") // 指定 TAG,默认为 "X-LOG"
+ .enableThreadInfo() // 允许打印线程信息,默认禁止
+ .enableStackTrace(2) // 允许打印深度为 2 的调用栈信息,默认禁止
+ .enableBorder() // 允许打印日志边框,默认禁止
+ .addInterceptor(
+ BlacklistTagsFilterInterceptor( // 添加黑名单 TAG 过滤器
+ "blacklist1", "blacklist2", "blacklist3"
+ )
+ )
+ .build()
+
+ val androidPrinter: Printer = AndroidPrinter(true) // 通过 android.util.Log 打印日志的打印器
+
+ val consolePrinter: Printer = ConsolePrinter() // 通过 System.out 打印日志到控制台的打印器
+
+ val filePrinter: Printer = FilePrinter.Builder("${SystemConstant.ROOT_PATH}/Logs") // 指定保存日志文件的路径
+ .fileNameGenerator(DateFileNameGenerator()) // 指定日志文件名生成器,默认为 ChangelessFileNameGenerator("log")
+ .backupStrategy(NeverBackupStrategy()) // 指定日志文件备份策略,默认为 FileSizeBackupStrategy(1024 * 1024)
+ .build()
+
+ XLog.init( // 初始化 XLog
+ config, // 指定日志配置,如果不指定,会默认使用 new LogConfiguration.Builder().build()
+ androidPrinter, // 添加任意多的打印器。如果没有添加任何打印器,会默认使用 AndroidPrinter(Android)/ConsolePrinter(java)
+ consolePrinter,
+ filePrinter
+ )
+ }
+
+ // @OnShowRationale(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
+ fun showRationaleForSDCard(permissions: MutableList) {
+// showRationaleDialog(R.string.permission_camera_rationale, request)
+// Toast.makeText(context, "当前操作需要您授权相机权限!", Toast.LENGTH_SHORT).show()
+ MaterialAlertDialogBuilder(this)
+ .setTitle("提示")
+ .setMessage("当前操作需要您授权读写SD卡权限!")
+ .setPositiveButton("确定", DialogInterface.OnClickListener { dialogInterface, i ->
+ dialogInterface.dismiss()
+ XXPermissions.startPermissionActivity(this@MainActivity, permissions)
+ })
+ .show()
+ }
+
+ // @OnPermissionDenied(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
+ fun onSDCardDenied() {
+ Toast.makeText(this, "当前操作需要您授权读写SD卡权限!", Toast.LENGTH_SHORT).show()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/volvo/ui/camera/CameraFragment.kt b/app/src/main/java/com/navinfo/volvo/ui/camera/CameraFragment.kt
new file mode 100644
index 0000000..d4d5bed
--- /dev/null
+++ b/app/src/main/java/com/navinfo/volvo/ui/camera/CameraFragment.kt
@@ -0,0 +1,112 @@
+package com.navinfo.volvo.ui.camera
+
+import android.os.Bundle
+import android.text.TextUtils
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.get
+import androidx.navigation.Navigation
+import com.easytools.tools.DateUtils
+import com.easytools.tools.FileUtils
+import com.elvishew.xlog.XLog
+import com.navinfo.volvo.databinding.FragmentCameraBinding
+import com.navinfo.volvo.ui.message.ObtainMessageViewModel
+import com.navinfo.volvo.utils.SystemConstant
+import com.otaliastudios.cameraview.CameraListener
+import com.otaliastudios.cameraview.CameraView
+import com.otaliastudios.cameraview.FileCallback
+import com.otaliastudios.cameraview.PictureResult
+import top.zibin.luban.Luban
+import top.zibin.luban.OnCompressListener
+import java.io.File
+import java.util.*
+
+
+class CameraFragment : Fragment() {
+
+ private var _binding: FragmentCameraBinding? = null
+
+ // This property is only valid between onCreateView and
+ // onDestroyView.
+ private val binding get() = _binding!!
+// private val exportFolderPath by lazy {
+// "${SystemConstant.ROOT_PATH}/exportPic/"
+// }
+
+ private val cameraLifeCycleObserver: CameraLifeCycleObserver by lazy {
+ CameraLifeCycleObserver()
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+// lifecycle.addObserver(cameraLifeCycleObserver)
+
+ val cameraViewModel =
+ ViewModelProvider(this).get(CameraViewModel::class.java)
+
+ _binding = FragmentCameraBinding.inflate(inflater, container, false)
+ val root: View = binding.root
+
+ val cameraView: CameraView = binding.camera
+ cameraView.setLifecycleOwner(this)
+ cameraView.addCameraListener(object:CameraListener() { // 添加拍照回调
+ override fun onPictureTaken(result: PictureResult) {
+ super.onPictureTaken(result)
+// FileUtils.createOrExistsDir(cameraFolderPath)
+ val resultFile = File("${SystemConstant.CameraFolder}/${DateUtils.date2Str(Date(), DateUtils.FORMAT_YMDHMS)}.jpg")
+ result.toFile(resultFile, object: FileCallback {
+ override fun onFileReady(resultFile: File?) {
+ // 压缩图片文件
+ Luban.with(context)
+ .load(mutableListOf(resultFile) as List?)
+ .ignoreBy(200)
+ .setTargetDir("${SystemConstant.CameraFolder}")
+ .filter { path ->
+ !(TextUtils.isEmpty(path) || path.lowercase(Locale.getDefault())
+ .endsWith(".gif"))
+ }
+ .setCompressListener(object : OnCompressListener {
+ override fun onStart() {
+ XLog.d("开始压缩图片")
+ }
+
+ override fun onSuccess(file: File?) {
+ XLog.d("压缩图片成功:${file?.absolutePath}")
+ // 删除源文件
+ if (!resultFile!!.absolutePath.equals(file!!.absolutePath)) {
+ resultFile!!.delete()
+ }
+ // 跳转回原Fragment,展示拍摄的照片
+ ViewModelProvider(requireActivity()).get(ObtainMessageViewModel::class.java).updateMessagePic(file!!.absolutePath)
+ // 跳转回原界面
+ Navigation.findNavController(root).popBackStack()
+ }
+
+ override fun onError(e: Throwable) {
+ XLog.d("压缩图片失败:${e.message}")
+ }
+ }).launch()
+ }
+ })
+ }
+ })
+ // 点击拍照
+ binding.imgStartCamera.setOnClickListener {
+ cameraView.takePicture()
+ }
+
+ return root
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+// lifecycle.removeObserver(cameraLifeCycleObserver)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/volvo/ui/camera/CameraLifeCycleObserver.kt b/app/src/main/java/com/navinfo/volvo/ui/camera/CameraLifeCycleObserver.kt
new file mode 100644
index 0000000..f567158
--- /dev/null
+++ b/app/src/main/java/com/navinfo/volvo/ui/camera/CameraLifeCycleObserver.kt
@@ -0,0 +1,31 @@
+package com.navinfo.volvo.ui.camera
+
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+
+class CameraLifeCycleObserver: DefaultLifecycleObserver {
+
+ override fun onCreate(owner: LifecycleOwner) {
+ super.onCreate(owner)
+ }
+
+ override fun onStart(owner: LifecycleOwner) {
+ super.onStart(owner)
+ }
+
+ override fun onResume(owner: LifecycleOwner) {
+ super.onResume(owner)
+ }
+
+ override fun onPause(owner: LifecycleOwner) {
+ super.onPause(owner)
+ }
+
+ override fun onStop(owner: LifecycleOwner) {
+ super.onStop(owner)
+ }
+
+ override fun onDestroy(owner: LifecycleOwner) {
+ super.onDestroy(owner)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/volvo/ui/camera/CameraViewModel.kt b/app/src/main/java/com/navinfo/volvo/ui/camera/CameraViewModel.kt
new file mode 100644
index 0000000..681bbb6
--- /dev/null
+++ b/app/src/main/java/com/navinfo/volvo/ui/camera/CameraViewModel.kt
@@ -0,0 +1,13 @@
+package com.navinfo.volvo.ui.camera
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+class CameraViewModel : ViewModel() {
+
+ private val _text = MutableLiveData().apply {
+ value = "This is dashboard Fragment"
+ }
+ val text: LiveData = _text
+}
\ 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 d0c23e3..e95182d 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,21 +1,57 @@
package com.navinfo.volvo.ui.fragments.message
+import android.content.DialogInterface
import android.os.Bundle
+import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
+import android.view.View.*
import android.view.ViewGroup
+import android.widget.AdapterView
+import android.widget.AdapterView.OnItemSelectedListener
+import android.widget.ArrayAdapter
+import android.widget.Toast
+import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
+import androidx.navigation.Navigation
+import com.bumptech.glide.Glide
+import com.easytools.tools.DateUtils
+import com.easytools.tools.ToastUtils
+import com.elvishew.xlog.XLog
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
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.db.dao.entity.AttachmentType
+import com.navinfo.volvo.db.dao.entity.Message
import com.navinfo.volvo.ui.markRequiredInRed
+import com.navinfo.volvo.utils.EasyMediaFile
+import com.navinfo.volvo.utils.SystemConstant
+import com.nhaarman.supertooltips.ToolTip
+import top.zibin.luban.Luban
+import top.zibin.luban.OnCompressListener
+import java.io.File
+import java.util.*
+
+//@RuntimePermissions
class ObtainMessageFragment: Fragment() {
private var _binding: FragmentObtainMessageBinding? = null
private val obtainMessageViewModel by lazy {
ViewModelProvider(requireActivity()).get(ObtainMessageViewModel::class.java)
}
+ private val photoHelper by lazy {
+ EasyMediaFile().setCrop(true)
+ }
+ private val recorderLifecycleObserver by lazy {
+ RecorderLifecycleObserver()
+ }
// This property is only valid between onCreateView and
// onDestroyView.
@@ -29,29 +65,91 @@ class ObtainMessageFragment: Fragment() {
_binding = FragmentObtainMessageBinding.inflate(inflater, container, false)
val root: View = binding.root
- obtainMessageViewModel.getMessageLiveData()?.observe(
+ obtainMessageViewModel.setCurrentMessage(Message())
+
+ obtainMessageViewModel?.getMessageLiveData()?.observe(
viewLifecycleOwner, Observer {
// 初始化界面显示内容
- if(it.title!=null)
- binding.tvMessageTitle.setText(it.title)
- if (it.sendDate!=null) {
+ if(it.title?.isNotEmpty() == true)
+ binding.tvMessageTitle?.setText(it.title)
+ if (it.sendDate?.isNotEmpty() == true) {
binding.btnSendTime.text = it.sendDate
}
+ 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 (attachment.attachmentType == AttachmentType.AUDIO) {
+ binding.tvAudioName.text = attachment.pathUrl.replace("\\", "/").substringAfterLast("/")
+ hasAudio = true
+ }
+ }
+ }
+ binding.layerPhotoResult.visibility = if (hasPhoto) VISIBLE else GONE
+ binding.layerGetPhoto.visibility = if (hasPhoto) GONE else VISIBLE
+ binding.layerAudioResult.visibility = if (hasAudio) VISIBLE else GONE
+ binding.layerGetAudio.visibility = if (hasAudio) GONE else VISIBLE
}
)
+ lifecycle.addObserver(recorderLifecycleObserver)
initView()
return root
}
- private fun initView() {
+ fun initView() {
// 设置问候信息提示的红色星号
binding.tiLayoutTitle.markRequiredInRed()
+ binding.tvMessageTitle.addTextChangedListener {
+ obtainMessageViewModel.updateMessageTitle(it.toString())
+ }
+
+ binding.imgPhotoDelete.setOnClickListener {
+ obtainMessageViewModel.updateMessagePic(null)
+ }
+
+ binding.imgAudioDelete.setOnClickListener {
+ obtainMessageViewModel.updateMessageAudio(null)
+ }
+
+ val sendToArray = mutableListOf("绑定车辆1(LYVXFEFEXNL754427)")
+ binding.edtSendTo.adapter = ArrayAdapter(context!!,
+ android.R.layout.simple_dropdown_item_1line, android.R.id.text1, sendToArray)
+ binding.edtSendTo.onItemSelectedListener = object: OnItemSelectedListener {
+ override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
+ obtainMessageViewModel.getMessageLiveData().value?.toId = sendToArray[p2]
+ }
+
+ override fun onNothingSelected(p0: AdapterView<*>?) {
+ }
+
+ }
+
// 设置点击按钮选择发送时间
binding.btnSendTime.setOnClickListener {
val dialog = DateTimePickerFragment.newInstance().mode(0)
dialog.listener = object : DateTimePickerFragment.OnClickListener {
override fun onClickListener(selectTime: String) {
- obtainMessageViewModel.updateMessageSendTime(selectTime)
+ val sendDate = DateUtils.str2Date(selectTime, "yyyy-MM-dd HH:mm")
+ if (sendDate <= Date()) {
+ obtainMessageViewModel.updateMessageSendTime("现在")
+ } else {
+ obtainMessageViewModel.updateMessageSendTime(selectTime)
+ }
}
}
@@ -59,8 +157,197 @@ class ObtainMessageFragment: Fragment() {
}
// 点击按钮选择拍照
- binding.edtSendTo.setOnClickListener {
+ binding.btnStartCamera.setOnClickListener {
+ // 启动相机
+ XXPermissions.with(this)
+ // 申请单个权限
+ .permission(Permission.CAMERA)
+ .request(object : OnPermissionCallback {
+ override fun onGranted(permissions: MutableList, all: Boolean) {
+ if (!all) {
+ Toast.makeText(activity, "获取部分权限成功,但部分权限未正常授予", Toast.LENGTH_SHORT).show()
+ return
+ }
+ // 开始启动拍照界面
+ photoHelper.setCrop(true).takePhoto(activity!!)
+ }
+
+ override fun onDenied(permissions: MutableList, never: Boolean) {
+ if (never) {
+ Toast.makeText(activity, "永久拒绝授权,请手动授权拍照权限", Toast.LENGTH_SHORT).show()
+ // 如果是被永久拒绝就跳转到应用权限系统设置页面
+ XXPermissions.startPermissionActivity(context!!, permissions)
+ } else {
+ onCameraDenied()
+ showRationaleForCamera(permissions)
+ }
+ }
+ })
+// startCamera(it)
+ }
+
+ binding.btnStartPhoto.setOnClickListener {
+ photoHelper.setCrop(true).selectPhoto(activity!!)
+ }
+
+ // 用户选择录音文件
+ binding.btnSelectSound.setOnClickListener {
+ photoHelper.setCrop(false).selectAudio(activity!!)
+ }
+
+ // 开始录音
+ binding.btnStartRecord.setOnClickListener {
+ // 申请权限
+ XXPermissions.with(this)
+ // 申请单个权限
+ .permission(Permission.RECORD_AUDIO)
+ .request(object : OnPermissionCallback {
+ override fun onGranted(permissions: MutableList, all: Boolean) {
+ if (!all) {
+ 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)
+ }
+ } else{
+ it.isSelected = true
+
+ recorderLifecycleObserver.initAndStartRecorder()
+ }
+ }
+
+ override fun onDenied(permissions: MutableList, never: Boolean) {
+ if (never) {
+ Toast.makeText(activity, "永久拒绝授权,请手动授权拍照权限", Toast.LENGTH_SHORT).show()
+ // 如果是被永久拒绝就跳转到应用权限系统设置页面
+ XXPermissions.startPermissionActivity(context!!, permissions)
+ } else {
+ onCameraDenied()
+ showRationaleForCamera(permissions)
+ }
+ }
+ })
+ }
+
+ // 获取照片文件和音频文件
+ photoHelper.setCallback {
+ if (it.exists()) {
+ val fileName = it.name.lowercase()
+ if (fileName.endsWith(".jpg")||fileName.endsWith(".jpeg")||fileName.endsWith(".png")) {
+ // 获取选中的图片,自动压缩图片质量
+ // 压缩图片文件
+ Luban.with(context)
+ .load(mutableListOf(it) as List?)
+ .ignoreBy(200)
+ .setTargetDir("${SystemConstant.CameraFolder}")
+ .filter { path ->
+ !(TextUtils.isEmpty(path) || path.lowercase(Locale.getDefault())
+ .endsWith(".gif"))
+ }
+ .setCompressListener(object : OnCompressListener {
+ override fun onStart() {
+ XLog.d("开始压缩图片/${it.absolutePath}")
+ }
+
+ override fun onSuccess(file: File?) {
+ XLog.d("压缩图片成功:${file?.absolutePath}")
+ // 删除源文件
+ if (!it.absolutePath.equals(file?.absolutePath)) {
+ it?.delete()
+ }
+ // 跳转回原Fragment,展示拍摄的照片
+ ViewModelProvider(requireActivity()).get(ObtainMessageViewModel::class.java).updateMessagePic(file!!.absolutePath)
+ }
+
+ override fun onError(e: Throwable) {
+ XLog.d("压缩图片失败:${e.message}")
+ }
+ }).launch()
+ } else if (fileName.endsWith(".mp3")||fileName.endsWith(".wav")||fileName.endsWith(".amr")||fileName.endsWith(".m4a")) {
+ ToastUtils.showToast(it.absolutePath)
+ obtainMessageViewModel.updateMessageAudio(it.absolutePath)
+ }
+ }
+
+ }
+ photoHelper.setError {
+ ToastUtils.showToast(it.message)
+ }
+
+ binding.btnObtainMessageBack.setOnClickListener {
+ Navigation.findNavController(it).popBackStack()
+ }
+
+ binding.btnObtainMessageConfirm.setOnClickListener {
+ // 检查当前输入数据
+ val messageData = obtainMessageViewModel.getMessageLiveData().value
+ if (messageData?.title?.isEmpty() == true) {
+ val toolTipRelativeLayout =
+ binding.ttTitle
+ val toolTip = ToolTip()
+ .withText("请输入问候信息")
+ .withColor(com.navinfo.volvo.R.color.purple_200)
+ .withShadow()
+ .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
+ }
+ }
+ if (!hasPic) {
+ val toolTipRelativeLayout =
+ binding.ttPic
+ val toolTip = ToolTip()
+ .withText("需要提供照片文件")
+ .withColor(com.navinfo.volvo.R.color.purple_200)
+ .withShadow()
+ .withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
+ toolTipRelativeLayout.showToolTipForView(toolTip, binding.tvUploadPic)
+ }
+ if (!hasAudio) {
+ val toolTipRelativeLayout =
+ binding.ttAudio
+ val toolTip = ToolTip()
+ .withText("需要提供音频文件")
+ .withColor(com.navinfo.volvo.R.color.purple_200)
+ .withShadow()
+ .withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
+ toolTipRelativeLayout.showToolTipForView(toolTip, binding.tvUploadPic)
+ }
+
+ if (messageData?.fromId?.isEmpty()==true) {
+ val toolTipRelativeLayout =
+ binding.ttSendFrom
+ val toolTip = ToolTip()
+ .withText("请输入您的名称")
+ .withColor(com.navinfo.volvo.R.color.purple_200)
+ .withShadow()
+ .withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
+ toolTipRelativeLayout.showToolTipForView(toolTip, binding.edtSendFrom)
+ }
+ if (messageData?.toId?.isEmpty()==true) {
+ val toolTipRelativeLayout =
+ binding.ttSendTo
+ val toolTip = ToolTip()
+ .withText("请选择要发送的车辆")
+ .withColor(com.navinfo.volvo.R.color.purple_200)
+ .withShadow()
+ .withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
+ toolTipRelativeLayout.showToolTipForView(toolTip, binding.edtSendTo)
+ }
}
}
@@ -68,4 +355,45 @@ class ObtainMessageFragment: Fragment() {
super.onDestroyView()
_binding = null
}
+
+ fun startCamera(it: View) {
+ Navigation.findNavController(binding.root).navigate(com.navinfo.volvo.R.id.nav_2_camera)
+ }
+
+ fun showRationaleForCamera(permissions: MutableList) {
+// showRationaleDialog(R.string.permission_camera_rationale, request)
+// Toast.makeText(context, "当前操作需要您授权相机权限!", Toast.LENGTH_SHORT).show()
+ MaterialAlertDialogBuilder(context!!)
+ .setTitle("提示")
+ .setMessage("当前操作需要您授权拍摄权限!")
+ .setPositiveButton("确定", DialogInterface.OnClickListener { dialogInterface, i ->
+ dialogInterface.dismiss()
+ XXPermissions.startPermissionActivity(activity!!, permissions)
+ })
+ .show()
+ }
+
+ // @OnPermissionDenied(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
+ fun onCameraDenied() {
+ ToastUtils.showToast("当前操作需要您授权拍摄权限!")
+ }
+
+ fun showRationaleForRecorder(permissions: MutableList) {
+ MaterialAlertDialogBuilder(context!!)
+ .setTitle("提示")
+ .setMessage("当前操作需要您授权录音权限!")
+ .setPositiveButton("确定", DialogInterface.OnClickListener { dialogInterface, i ->
+ dialogInterface.dismiss()
+ XXPermissions.startPermissionActivity(activity!!, permissions)
+ })
+ .show()
+ }
+ fun onRecorderDenied() {
+ ToastUtils.showToast("当前操作需要您授权录音权限!")
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ lifecycle.removeObserver(recorderLifecycleObserver)
+ }
}
\ 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 b2343d5..84be5dd 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,9 +1,19 @@
package com.navinfo.volvo.ui.fragments.message
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import com.navinfo.volvo.model.Message
-import com.navinfo.volvo.model.AttachmentType
+import androidx.lifecycle.*
+import com.easytools.tools.ToastUtils
+import com.elvishew.xlog.XLog
+import com.navinfo.volvo.db.dao.entity.Attachment
+import com.navinfo.volvo.db.dao.entity.AttachmentType
+import com.navinfo.volvo.db.dao.entity.Message
+import com.navinfo.volvo.http.NavinfoVolvoCall
+import kotlinx.coroutines.launch
+import okhttp3.MediaType
+import okhttp3.MultipartBody
+import okhttp3.RequestBody
+import java.io.File
+import java.util.*
+
class ObtainMessageViewModel: ViewModel() {
private val msgLiveData: MutableLiveData by lazy {
@@ -25,22 +35,41 @@ class ObtainMessageViewModel: ViewModel() {
}
// 更新消息附件中的照片文件
- fun updateMessagePic(picUrl: String) {
+ fun updateMessagePic(picUrl: String?) {
+ var hasPic = false
+
for (attachment in this.msgLiveData.value!!.attachment) {
if (attachment.attachmentType == AttachmentType.PIC) {
- attachment.pathUrl = picUrl
+ 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) {
+ fun updateMessageAudio(audioUrl: String?) {
+ var hasAudio = false
for (attachment in this.msgLiveData.value!!.attachment) {
if (attachment.attachmentType == AttachmentType.AUDIO) {
- attachment.pathUrl = audioUrl
+ 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.postValue(this.msgLiveData.value)
}
@@ -61,4 +90,25 @@ class ObtainMessageViewModel: ViewModel() {
this.msgLiveData.value?.sendDate = sendTime
this.msgLiveData.postValue(this.msgLiveData.value)
}
+
+ fun uploadAttachment(attachmentFile: File) {
+ // 启用协程调用网络请求
+ viewModelScope.launch {
+ try {
+ val requestFile: RequestBody =
+ RequestBody.create(MediaType.parse("multipart/form-data"), attachmentFile)
+ val body = MultipartBody.Part.createFormData("picture", attachmentFile.getName(), requestFile)
+ val result = NavinfoVolvoCall.getApi().uploadAttachment(body)
+ XLog.d(result.code)
+ if (result.code == 200) { // 请求成功
+ // 获取上传后的结果
+ } else {
+ ToastUtils.showToast(result.message)
+ }
+ } catch (e: Exception) {
+ ToastUtils.showToast(e.message)
+ XLog.d(e.message)
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/volvo/ui/message/MessageActivity.kt b/app/src/main/java/com/navinfo/volvo/ui/message/MessageActivity.kt
new file mode 100644
index 0000000..cbb84f9
--- /dev/null
+++ b/app/src/main/java/com/navinfo/volvo/ui/message/MessageActivity.kt
@@ -0,0 +1,39 @@
+package com.navinfo.volvo.ui.message
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import androidx.navigation.Navigation
+import androidx.navigation.findNavController
+import androidx.navigation.ui.AppBarConfiguration
+import androidx.navigation.ui.navigateUp
+import androidx.navigation.ui.setupActionBarWithNavController
+import com.navinfo.volvo.R
+import com.navinfo.volvo.databinding.ActivityMessageBinding
+
+class MessageActivity : AppCompatActivity() {
+
+ private lateinit var appBarConfiguration: AppBarConfiguration
+ private lateinit var binding: ActivityMessageBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ binding = ActivityMessageBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ setSupportActionBar(binding.toolbar)
+ binding.toolbar.setOnClickListener {
+ Navigation.findNavController(it).popBackStack()
+ }
+
+ val navController = findNavController(R.id.nav_host_fragment_message)
+ appBarConfiguration = AppBarConfiguration(navController.graph)
+ setupActionBarWithNavController(navController, appBarConfiguration)
+ }
+
+ override fun onSupportNavigateUp(): Boolean {
+ val navController = findNavController(R.id.nav_host_fragment_message)
+ return navController.navigateUp(appBarConfiguration)
+ || super.onSupportNavigateUp()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/volvo/utils/EasyMediaFile.kt b/app/src/main/java/com/navinfo/volvo/utils/EasyMediaFile.kt
new file mode 100644
index 0000000..e04f57d
--- /dev/null
+++ b/app/src/main/java/com/navinfo/volvo/utils/EasyMediaFile.kt
@@ -0,0 +1,648 @@
+package com.navinfo.volvo.utils
+
+import android.app.Activity
+import android.app.Fragment
+import android.content.*
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.net.Uri
+import android.os.*
+import android.os.Environment.*
+import android.provider.MediaStore
+import java.io.File
+import java.util.*
+
+
+/**
+ * 创建日期:2018/8/21 0021on 下午 4:40
+ * 描述:多媒体选择工具类
+ * @author:Vincent
+ */
+class EasyMediaFile {
+ /**
+ * 设置图片选择结果回调
+ */
+ private var callback: ((file: File) -> Unit)? = null
+ private var isCrop: Boolean = false
+ private var error: ((error: Exception) -> Unit)? = null
+
+ /**
+ * 视频录制/音频录制/拍照/剪切后图片的存放位置(参考file_provider_paths.xml中的路径)
+ */
+ private var mFilePath: File? = null
+
+ private val mainHandler = Handler(Looper.getMainLooper())
+
+ fun setError(error: ((error: Exception) -> Unit)?): EasyMediaFile {
+ this.error = error
+ return this
+ }
+
+ fun setCallback(callback: ((file: File) -> Unit)): EasyMediaFile {
+ this.callback = callback
+ return this
+ }
+
+ fun setCrop(isCrop: Boolean): EasyMediaFile {
+ this.isCrop = isCrop
+ return this
+ }
+
+ /**
+ * 修改图片的存储路径(默认的图片存储路径是SD卡上 Android/data/应用包名/时间戳.jpg)
+ *
+ * @param imgPath 图片的存储路径(包括文件名和后缀)
+ */
+ fun setFilePath(imgPath: String?): EasyMediaFile {
+ if (imgPath.isNullOrEmpty()) {
+ this.mFilePath = null
+ } else {
+ this.mFilePath = File(imgPath)
+ this.mFilePath?.parentFile?.mkdirs()
+ }
+ return this
+ }
+
+
+ /**
+ * 选择文件
+ * 支持图片、音频、视频
+ */
+// fun selectFile(activity: Activity) {
+// isCrop = false
+// val intent = Intent(Intent.ACTION_PICK, null).apply {
+// setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "*/*")
+// setDataAndType(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, "*/*")
+// setDataAndType(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "*/*")
+// }
+// if (Looper.myLooper() == Looper.getMainLooper()) {
+// selectFileInternal(intent, activity, -1)
+// } else {
+// mainHandler.post { selectFileInternal(intent, activity, -1) }
+// }
+// }
+
+ /**
+ * 选择视频
+ */
+ fun selectVideo(activity: Activity) {
+ isCrop = false
+ val intent = Intent(Intent.ACTION_PICK, null).apply {
+ setDataAndType(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/*")
+ }
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ selectFileInternal(intent, activity, 2)
+ } else {
+ mainHandler.post { selectFileInternal(intent, activity, 2) }
+ }
+ }
+
+ /**
+ * 选择音频
+ */
+ fun selectAudio(activity: Activity) {
+ isCrop = false
+ val intent = Intent(Intent.ACTION_PICK, null).apply {
+ setDataAndType(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, "audio/*")
+ }
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ selectFileInternal(intent, activity, 1)
+ } else {
+ mainHandler.post { selectFileInternal(intent, activity, 1) }
+ }
+ }
+
+ /**
+ * 选择图片
+ */
+ fun selectPhoto(activity: Activity) {
+ val intent = Intent(Intent.ACTION_PICK, null).apply {
+ setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*")
+ }
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ selectFileInternal(intent, activity, 0)
+ } else {
+ mainHandler.post { selectFileInternal(intent, activity, 0) }
+ }
+ }
+
+
+ /**
+ * 选择文件
+ */
+ private fun selectFileInternal(intent: Intent, activity: Activity, type: Int) {
+ val resolveInfoList = activity.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
+ if (resolveInfoList.isEmpty()) {
+ error?.invoke(IllegalStateException("No Activity found to handle Intent "))
+ } else {
+ PhotoFragment.findOrCreate(activity).start(intent, PhotoFragment.REQ_SELECT_FILE) { requestCode: Int, data: Intent? ->
+ if (requestCode != PhotoFragment.REQ_SELECT_FILE) {
+ return@start
+ }
+ data ?: return@start
+ data.data ?: return@start
+ try {
+ val inputFile = if (type != -1) {
+ uriToFile(activity, data.data!!, type)
+ } else {
+ if (data.data!!.path!!.contains(".")) {
+ File(data.data!!.path!!)
+ } else {
+ when {
+ data.data!!.path!!.contains("images") -> {
+ uriToFile(activity, data.data!!, 0)
+ }
+ data.data!!.path!!.contains("video") -> {
+ uriToFile(activity, data.data!!, 2)
+ }
+ else -> {
+ uriToFile(activity, data.data!!, 1)
+ }
+ }
+ }
+ }
+ if (isCrop) {//裁剪
+ zoomPhoto(inputFile, mFilePath
+ ?: File(generateFilePath(activity)), activity)
+ } else {//不裁剪
+ callback?.invoke(inputFile)
+ }
+ } catch (e: Exception) {
+ error?.invoke(e)
+ }
+ }
+ }
+
+ }
+
+ private fun uriToFile(activity: Activity, uri: Uri): File {
+
+ // 首先使用系统提供的CursorLoader进行file获取
+ val context = activity.application
+ val projection = arrayOf(MediaStore.Images.Media.DATA)
+ var path: String
+ try {
+ CursorLoader(context, uri, projection, null, null, null)
+ .loadInBackground().apply {
+ getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
+ val index = getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
+ moveToFirst()
+ path = getString(index)
+ close()
+
+ }
+ return File(path)
+ } catch (e: Exception) {
+ // 当没获取到。再使用别的方式进行获取
+ val scheme = uri.scheme
+ path = uri.path ?: throw RuntimeException("Could not find path in this uri:[$uri]")
+ when (scheme) {
+ "file" -> {
+ val cr = context.contentResolver
+ val buff = StringBuffer()
+ buff.append("(").append(MediaStore.Images.ImageColumns.DATA).append("=").append("'$path'").append(")")
+ cr.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, arrayOf(MediaStore.Images.ImageColumns._ID,
+ MediaStore.Images.ImageColumns.DATA), buff.toString(), null, null).apply {
+ this ?: throw RuntimeException("cursor is null")
+ var dataIdx: Int
+ while (!this.isAfterLast) {
+ dataIdx = this.getColumnIndex(MediaStore.Images.ImageColumns.DATA)
+ path = this.getString(dataIdx)
+ this.moveToNext()
+ }
+ close()
+ }
+
+ return File(path)
+ }
+ "content" -> {
+
+ context.contentResolver.query(uri, arrayOf(MediaStore.Images.Media.DATA), null, null, null).apply {
+ this ?: throw RuntimeException("cursor is null")
+ if (this.moveToFirst()) {
+ val columnIndex = this.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
+ path = this.getString(columnIndex)
+ }
+ close()
+ }
+ return File(path)
+
+ }
+ else -> {
+ throw IllegalArgumentException("Could not find file by this uri:$uri")
+ }
+ }
+
+ }
+ }
+
+ /**
+ * 拍照获取
+ */
+ fun takePhoto(activity: Activity) {
+ val imgFile = if (isCrop) {
+ File(generateFilePath(activity))
+ } else {
+ mFilePath ?: File(generateFilePath(activity))
+ }
+
+ val imgUri = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+ Uri.fromFile(imgFile)
+ } else {
+ //兼容android7.0 使用共享文件的形式
+ val contentValues = ContentValues(1)
+ contentValues.put(MediaStore.Images.Media.DATA, imgFile.absolutePath)
+ activity.application.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
+ }
+
+ val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUri)
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ takeFileInternal(imgFile, intent, activity)
+ } else {
+ mainHandler.post { takeFileInternal(imgFile, intent, activity) }
+ }
+ }
+
+ /**
+ * 音频录制
+ */
+ fun takeAudio(activity: Activity) {
+ isCrop = false
+ val imgFile = mFilePath ?: File(generateFilePath(activity, 1))
+ val imgUri = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+ Uri.fromFile(imgFile)
+ } else {
+ //兼容android7.0 使用共享文件的形式
+ val contentValues = ContentValues(1)
+ contentValues.put(MediaStore.Audio.Media.DATA, imgFile.absolutePath)
+ activity.application.contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, contentValues)
+ }
+
+ val intent = Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION)
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUri)
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ takeFileInternal(imgFile, intent, activity, 1)
+ } else {
+ mainHandler.post { takeFileInternal(imgFile, intent, activity, 1) }
+ }
+
+ }
+
+ /**
+ * 视频录制
+ */
+ fun takeVideo(activity: Activity) {
+ isCrop = false
+ val imgFile = mFilePath ?: File(generateFilePath(activity, 2))
+ val imgUri = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+ Uri.fromFile(imgFile)
+ } else {
+ //兼容android7.0 使用共享文件的形式
+ val contentValues = ContentValues(1)
+ contentValues.put(MediaStore.Video.Media.DATA, imgFile.absolutePath)
+ activity.application.contentResolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentValues)
+ }
+
+ val intent = Intent(MediaStore.ACTION_VIDEO_CAPTURE).apply {
+ putExtra(MediaStore.EXTRA_OUTPUT, imgUri)
+ // 默认录制时间10秒 部分手机该设置无效
+// putExtra(MediaStore.EXTRA_DURATION_LIMIT, 10000)
+ }
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ takeFileInternal(imgFile, intent, activity, 2)
+ } else {
+ mainHandler.post { takeFileInternal(imgFile, intent, activity, 2) }
+ }
+
+ }
+
+ /**
+ * 拍照或选择
+ */
+ fun getImage(activity: Activity) {
+
+ val imgFile = if (isCrop) {
+ File(generateFilePath(activity))
+ } else {
+ mFilePath ?: File(generateFilePath(activity))
+ }
+
+ val imgUri = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+ Uri.fromFile(imgFile)
+ } else {
+ //兼容android7.0 使用共享文件的形式
+ val contentValues = ContentValues(1)
+ contentValues.put(MediaStore.Images.Media.DATA, imgFile.absolutePath)
+ activity.application.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
+ }
+
+
+ val cameraIntents = ArrayList()
+ val captureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
+ val packageManager = activity.packageManager
+ val camList = packageManager.queryIntentActivities(captureIntent, 0)
+ for (res in camList) {
+ val packageName = res.activityInfo.packageName
+ val intent = Intent(captureIntent)
+ intent.component = ComponentName(res.activityInfo.packageName, res.activityInfo.name)
+ intent.setPackage(packageName)
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUri)
+ cameraIntents.add(intent)
+ }
+ val intent = Intent.createChooser(createPickMore(), "请选择").also {
+ it.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toTypedArray())
+
+ }
+
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ takeFileInternal(imgFile, intent, activity)
+ } else {
+ mainHandler.post { takeFileInternal(imgFile, intent, activity) }
+ }
+ }
+
+ /**
+ * 音频录制或选择
+ */
+ fun getAudio(activity: Activity) {
+ isCrop = false
+ val imgFile = mFilePath ?: File(generateFilePath(activity))
+
+ val imgUri = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+ Uri.fromFile(imgFile)
+ } else {
+ //兼容android7.0 使用共享文件的形式
+ val contentValues = ContentValues(1)
+ contentValues.put(MediaStore.Audio.Media.DATA, imgFile.absolutePath)
+ activity.application.contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, contentValues)
+ }
+ val cameraIntents = ArrayList()
+ val captureIntent = Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION)
+ val packageManager = activity.packageManager
+ val camList = packageManager.queryIntentActivities(captureIntent, 0)
+ for (res in camList) {
+ val packageName = res.activityInfo.packageName
+ val intent = Intent(captureIntent)
+ intent.component = ComponentName(res.activityInfo.packageName, res.activityInfo.name)
+ intent.setPackage(packageName)
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUri)
+ cameraIntents.add(intent)
+ }
+ val intent = Intent.createChooser(createPickMore("audio/*"), "请选择").also {
+ it.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toTypedArray())
+
+ }
+
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ takeFileInternal(imgFile, intent, activity, 1)
+ } else {
+ mainHandler.post { takeFileInternal(imgFile, intent, activity, 1) }
+ }
+ }
+
+ /**
+ * 视频拍摄或选择
+ */
+ fun getVideo(activity: Activity) {
+ isCrop = false
+ val imgFile = mFilePath ?: File(generateFilePath(activity, 2))
+
+ val imgUri = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+ Uri.fromFile(imgFile)
+ } else {
+ //兼容android7.0 使用共享文件的形式
+ val contentValues = ContentValues(1)
+ contentValues.put(MediaStore.Video.Media.DATA, imgFile.absolutePath)
+ activity.application.contentResolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentValues)
+ }
+ val cameraIntents = ArrayList()
+ val captureIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
+ // 某些手机此设置是不生效的,需要自行封装解决
+// captureIntent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 10000)
+ val packageManager = activity.packageManager
+ val camList = packageManager.queryIntentActivities(captureIntent, 0)
+ for (res in camList) {
+ val packageName = res.activityInfo.packageName
+ val intent = Intent(captureIntent)
+ intent.component = ComponentName(res.activityInfo.packageName, res.activityInfo.name)
+ intent.setPackage(packageName)
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUri)
+ cameraIntents.add(intent)
+ }
+ val intent = Intent.createChooser(createPickMore("video/*"), "请选择").also {
+ it.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toTypedArray())
+
+ }
+
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ takeFileInternal(imgFile, intent, activity, 2)
+ } else {
+ mainHandler.post { takeFileInternal(imgFile, intent, activity, 2) }
+ }
+ }
+
+ /**
+ * 向系统发出指令
+ */
+ private fun takeFileInternal(takePhotoPath: File, intent: Intent, activity: Activity, type: Int = 0) {
+ val fragment = PhotoFragment.findOrCreate(activity)
+ fragment.start(intent, PhotoFragment.REQ_TAKE_FILE) { requestCode: Int, data: Intent? ->
+ if (requestCode == PhotoFragment.REQ_TAKE_FILE) {
+ if (data?.data != null) {
+ mFilePath = when (type) {
+ 0 -> {
+ uriToFile(activity, data.data!!)
+ }
+ else -> uriToFile(activity, data.data!!, type)
+ }
+
+ if (isCrop) {
+ zoomPhoto(takePhotoPath, mFilePath
+ ?: File(generateFilePath(activity)), activity)
+ } else {
+ callback?.invoke(mFilePath!!)
+ mFilePath = null
+ }
+ return@start
+ }
+ if (isCrop) {
+ zoomPhoto(takePhotoPath, mFilePath
+ ?: File(generateFilePath(activity)), activity)
+ } else {
+ callback?.invoke(takePhotoPath)
+ }
+ }
+ }
+ }
+
+ private fun uriToFile(activity: Activity, data: Uri, type: Int): File {
+ val cursor = activity.managedQuery(data, arrayOf(if (type == 1) MediaStore.Audio.Media.DATA else MediaStore.Video.Media.DATA), null,
+ null, null)
+ val path = if (cursor == null) {
+ data.path
+ } else {
+ val index = cursor.getColumnIndexOrThrow(if (type == 1) MediaStore.Audio.Media.DATA else MediaStore.Video.Media.DATA)
+ cursor.moveToFirst()
+ cursor.getString(index)
+ }
+ // 手动关掉报错如下
+// Caused by: android.database.StaleDataException: Attempted to access a cursor after it has been closed.
+// cursor.close()
+ return File(path!!)
+ }
+
+ /***
+ * 图片裁剪
+ */
+ private fun zoomPhoto(inputFile: File?, outputFile: File, activity: Activity) {
+ try {
+ val intent = Intent("com.android.camera.action.CROP")
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ intent.setDataAndType(getImageContentUri(activity, inputFile), "image/*")
+ } else {
+ intent.setDataAndType(Uri.fromFile(inputFile), "image/*")
+ }
+ intent.putExtra("crop", "true")
+
+ // 是否返回uri
+ intent.putExtra("return-data", false)
+ intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString())
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ val imgFile = File("${Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES)}/${outputFile.name}")
+ // 通过 MediaStore API 插入file 为了拿到系统裁剪要保存到的uri(因为App没有权限不能访问公共存储空间,需要通过 MediaStore API来操作)
+ val values = ContentValues()
+ values.put(MediaStore.Images.Media.DATA, imgFile.getAbsolutePath());
+ values.put(MediaStore.Images.Media.DISPLAY_NAME, outputFile.name);
+ values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
+ val uri = activity.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(outputFile))
+ zoomPhotoInternal(outputFile, intent, activity)
+ }else {
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(outputFile))
+ zoomPhotoInternal(outputFile, intent, activity)
+ }
+ } catch (e: Exception) {
+ error?.invoke(e)
+ }
+
+ }
+
+ private fun zoomPhotoInternal(outputFile: File, intent: Intent, activity: Activity) {
+ PhotoFragment.findOrCreate(activity).start(intent, PhotoFragment.REQ_ZOOM_PHOTO) { requestCode: Int, data: Intent? ->
+ if (requestCode == PhotoFragment.REQ_ZOOM_PHOTO) {
+ data ?: return@start
+ callback?.invoke(outputFile)
+ }
+ }
+ }
+
+ /**构建文件多选Intent*/
+ private fun createPickMore(fileType: String = "image/*"): Intent {
+ val pictureChooseIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI).apply {
+ type = fileType
+ }
+ pictureChooseIntent.putExtra(Intent.EXTRA_LOCAL_ONLY, true)
+ /**临时授权app访问URI代表的文件所有权*/
+ pictureChooseIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ return pictureChooseIntent
+ }
+ /**
+ * 产生图片的路径,带文件夹和文件名,文件名为当前毫秒数
+ */
+ private fun generateFilePath(activity: Activity, fileType: Int = 0): String {
+ val file = when (fileType) {
+ // 音频路径
+ 1 -> "${SystemConstant.SoundFolder}" + File.separator + System.currentTimeMillis().toString() + ".m4a"
+ // 视频路径
+ 2 -> "${SystemConstant.CameraFolder}" + File.separator + System.currentTimeMillis().toString() + ".mp4"
+ // 图片路径
+ else -> "${SystemConstant.CameraFolder}" + File.separator + System.currentTimeMillis().toString() + ".jpg"
+ }
+ File(file).parentFile.mkdirs()
+ return file
+ }
+ /**
+ * 获取SD下的应用目录
+ */
+ private fun getExternalStoragePath(activity: Activity): String {
+ val sb = "${activity.getExternalFilesDir(null)}/tmp"
+ return sb
+ }
+ /**
+ * 安卓7.0裁剪根据文件路径获取uri
+ */
+ private fun getImageContentUri(context: Context, imageFile: File?): Uri? {
+ val filePath = imageFile?.absolutePath
+ val cursor = context.contentResolver.query(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ arrayOf(MediaStore.Images.Media._ID),
+ MediaStore.Images.Media.DATA + "=? ",
+ arrayOf(filePath), null)
+ cursor.use { _ ->
+ return if (cursor != null && cursor.moveToFirst()) {
+ val id = cursor.getInt(cursor
+ .getColumnIndex(MediaStore.MediaColumns._ID))
+ val baseUri = Uri.parse("content://media/external/images/media")
+ Uri.withAppendedPath(baseUri, "" + id)
+ } else {
+ imageFile?.let {
+ if (it.exists()) {
+ val values = ContentValues()
+ values.put(MediaStore.Images.Media.DATA, filePath)
+ context.contentResolver.insert(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
+ } else {
+ null
+ }
+ }
+ }
+ }
+ }
+ /**
+ * 用于获取图片的Fragment
+ */
+ class PhotoFragment : Fragment() {
+ /**
+ * Fragment处理照片后返回接口
+ */
+ private var callback: ((requestCode: Int, intent: Intent?) -> Unit)? = null
+ /**
+ * 开启系统相册
+ * 裁剪图片、打开相册选择单张图片、拍照
+ */
+ fun start(intent: Intent, requestCode: Int, callback: ((requestCode: Int, intent: Intent?) -> Unit)) {
+ this.callback = callback
+ startActivityForResult(intent, requestCode)
+ }
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ if (resultCode == Activity.RESULT_OK) {
+ callback?.invoke(requestCode, data)
+ }
+ }
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ retainInstance = true
+ }
+ companion object {
+ const val REQ_TAKE_FILE = 10001
+ const val REQ_SELECT_FILE = 10002
+ const val REQ_ZOOM_PHOTO = 10003
+ private const val TAG = "EasyPhoto:PhotoFragment"
+ @JvmStatic
+ fun findOrCreate(activity: Activity): PhotoFragment {
+ var fragment: PhotoFragment? = activity.fragmentManager.findFragmentByTag(TAG) as PhotoFragment?
+ if (fragment == null) {
+ fragment = PhotoFragment()
+ activity.fragmentManager.beginTransaction()
+ .add(fragment, TAG)
+ .commitAllowingStateLoss()
+ activity.fragmentManager.executePendingTransactions()
+ }
+ return fragment
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/volvo/utils/SystemConstant.kt b/app/src/main/java/com/navinfo/volvo/utils/SystemConstant.kt
new file mode 100644
index 0000000..0cc1857
--- /dev/null
+++ b/app/src/main/java/com/navinfo/volvo/utils/SystemConstant.kt
@@ -0,0 +1,10 @@
+package com.navinfo.volvo.utils
+
+class SystemConstant {
+ companion object {
+ lateinit var ROOT_PATH: String
+ lateinit var CameraFolder: String
+ lateinit var SoundFolder: String
+ lateinit var LogFolder: String
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_baseline_camera_alt_24.xml b/app/src/main/res/drawable/ic_baseline_camera_alt_24.xml
new file mode 100644
index 0000000..47483ef
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_camera_alt_24.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_delete_24.xml b/app/src/main/res/drawable/ic_baseline_delete_24.xml
new file mode 100644
index 0000000..de011dd
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_delete_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_volume_down_24.xml b/app/src/main/res/drawable/ic_baseline_volume_down_24.xml
new file mode 100644
index 0000000..f3ae1ba
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_volume_down_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_volume_mute_24.xml b/app/src/main/res/drawable/ic_baseline_volume_mute_24.xml
new file mode 100644
index 0000000..16a576c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_volume_mute_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_volume_up_24.xml b/app/src/main/res/drawable/ic_baseline_volume_up_24.xml
new file mode 100644
index 0000000..2551246
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_volume_up_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_message.xml b/app/src/main/res/layout/activity_message.xml
new file mode 100644
index 0000000..540fa58
--- /dev/null
+++ b/app/src/main/res/layout/activity_message.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/adapter_message.xml b/app/src/main/res/layout/adapter_message.xml
index aa728db..0137030 100644
--- a/app/src/main/res/layout/adapter_message.xml
+++ b/app/src/main/res/layout/adapter_message.xml
@@ -9,7 +9,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
- android:src="@mipmap/ic_launcher"
+ android:src="@mipmap/volvo_logo_small"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
diff --git a/app/src/main/res/layout/content_message.xml b/app/src/main/res/layout/content_message.xml
new file mode 100644
index 0000000..66d305d
--- /dev/null
+++ b/app/src/main/res/layout/content_message.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_camera.xml b/app/src/main/res/layout/fragment_camera.xml
new file mode 100644
index 0000000..d839372
--- /dev/null
+++ b/app/src/main/res/layout/fragment_camera.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml
index ffa373e..201541e 100644
--- a/app/src/main/res/layout/fragment_dashboard.xml
+++ b/app/src/main/res/layout/fragment_dashboard.xml
@@ -1,11 +1,10 @@
-
+ tools:context="com.navinfo.volvo.ui.dashboard.CameraFragment">
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_obtain_message.xml b/app/src/main/res/layout/fragment_obtain_message.xml
index ba4b01e..0a482c1 100644
--- a/app/src/main/res/layout/fragment_obtain_message.xml
+++ b/app/src/main/res/layout/fragment_obtain_message.xml
@@ -1,20 +1,19 @@
-
-
+ tools:context=".ui.message.ObtainMessageFragment">
-
+ app:layout_constraintTop_toTopOf="parent"
+ android:overScrollMode="never"
+ android:scrollbars="none">
@@ -25,10 +24,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/default_widget_padding"
- android:hint="问候信息"
app:counterEnabled="true"
app:counterMaxLength="10"
app:errorEnabled="true"
+ android:hint="请输入问候信息"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
@@ -41,13 +40,19 @@
tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck" />
+
+
+ android:text="问候附件"
+ app:layout_constraintTop_toBottomOf="@id/tt_title">
-
-
-
-
-
-
+
+
+
+
+
+ android:gravity="center"
+ android:visibility="gone"
+ android:orientation="horizontal">
+
+
+
+
+
+
+
+
+
-
-
+ android:gravity="center"
+ android:orientation="horizontal">
+
+
+
+
-
-
-
+ android:gravity="center"
+ android:visibility="gone"
+ android:orientation="horizontal">
+
+
+
+
+
+
+
+ android:background="@drawable/selector_bg_4_round_corner">
+
-
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+
+
+
+
+
+
+
+
+ android:padding="@dimen/default_widget_padding">
-
+
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-xxhdpi/volvo_logo_small.png b/app/src/main/res/mipmap-xxhdpi/volvo_logo_small.png
new file mode 100644
index 0000000..12d53b9
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/volvo_logo_small.png differ
diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml
new file mode 100644
index 0000000..6806c11
--- /dev/null
+++ b/app/src/main/res/navigation/nav_graph.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-land/dimens.xml b/app/src/main/res/values-land/dimens.xml
new file mode 100644
index 0000000..22d7f00
--- /dev/null
+++ b/app/src/main/res/values-land/dimens.xml
@@ -0,0 +1,3 @@
+
+ 48dp
+
\ No newline at end of file
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index d241e8c..4dfdfbe 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -1,6 +1,6 @@
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index a9ac825..aaa9818 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,6 +1,6 @@
-
+
+
+
+
+
+
\ No newline at end of file