fix: 完善消息详情界面

This commit is contained in:
xiaoyan 2023-01-03 16:07:11 +08:00
commit cf712a6b86
37 changed files with 1682 additions and 329 deletions

View File

@ -83,14 +83,21 @@ dependencies {
implementation 'com.google.code.gson:gson:2.10' implementation 'com.google.code.gson:gson:2.10'
implementation 'com.yanzhenjie.recyclerview:x:1.3.2' implementation 'com.yanzhenjie.recyclerview:x:1.3.2'
// https://github.com/permissions-dispatcher/PermissionsDispatcher // https://github.com/getActivity/XXPermissions
implementation "com.github.permissions-dispatcher:permissionsdispatcher:4.9.2" implementation 'com.github.getActivity:XXPermissions:16.5'
annotationProcessor "com.github.permissions-dispatcher:permissionsdispatcher-processor:4.9.2"
// https://natario1.github.io/CameraView/about/getting-started // https://natario1.github.io/CameraView/about/getting-started
implementation("com.otaliastudios:cameraview:2.7.2") implementation("com.otaliastudios:cameraview:2.7.2")
// https://github.com/Curzibn/Luban // https://github.com/Curzibn/Luban
implementation 'top.zibin:Luban:1.1.8' implementation 'top.zibin:Luban:1.1.8'
// Android工具类库 https://github.com/l123456789jy/Lazy // Android工具类库 https://github.com/gycold/EasyAndroid
implementation 'com.github.lazylibrary:lazylibrary:1.0.2' 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"
}
// https://github.com/nhaarman/supertooltips
implementation 'com.nhaarman.supertooltips:library:3.0.0'
} }

View File

@ -1,13 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<!--拍照--> <!-- 拍照 -->
<uses-permission android:name="android.permission.CAMERA" android:required="false"/> <uses-permission
<!--网络请求--> android:name="android.permission.CAMERA"
<uses-permission android:name="android.permission.INTERNET" android:required="false"/> android:required="false" /> <!-- 网络请求 -->
<!--录音--> <uses-permission
<uses-permission android:name="android.permission.RECORD_AUDIO" android:required="false"/> android:name="android.permission.INTERNET"
<!--读写文件--> android:required="false" /> <!-- 录音 -->
<uses-permission
android:name="android.permission.RECORD_AUDIO"
android:required="false" /> <!-- 读写文件 -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@ -18,11 +21,21 @@
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.NavinfoVolvo" android:theme="@style/Theme.NavinfoVolvo"
tools:targetApi="31"> tools:targetApi="31">
<activity
android:name=".ui.message.MessageActivity"
android:exported="false"
android:label="@string/title_activity_second"
android:theme="@style/Theme.NavinfoVolvo.NoActionBar">
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
<activity <activity
android:name="com.navinfo.volvo.MainActivity" android:name="com.navinfo.volvo.MainActivity"
android:exported="true" android:exported="true"
@ -32,11 +45,12 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity>
<meta-data <meta-data
android:name="android.app.lib_name" android:name="android.app.lib_name"
android:value="" /> android:value="" />
</activity> <meta-data android:name="ScopedStorage" android:value="true" />
</application> </application>
</manifest> </manifest>

View File

@ -1,24 +1,42 @@
package com.navinfo.volvo package com.navinfo.volvo
import android.Manifest
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View
import android.widget.Toast import android.widget.Toast
import com.google.android.material.bottomnavigation.BottomNavigationView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
import com.easytools.tools.FileUtils
import com.elvishew.xlog.LogConfiguration
import com.elvishew.xlog.LogLevel
import com.elvishew.xlog.XLog
import com.elvishew.xlog.interceptor.BlacklistTagsFilterInterceptor
import com.elvishew.xlog.printer.AndroidPrinter
import com.elvishew.xlog.printer.ConsolePrinter
import com.elvishew.xlog.printer.Printer
import com.elvishew.xlog.printer.file.FilePrinter
import com.elvishew.xlog.printer.file.backup.NeverBackupStrategy
import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.hjq.permissions.OnPermissionCallback
import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
import com.navinfo.volvo.databinding.ActivityMainBinding import com.navinfo.volvo.databinding.ActivityMainBinding
import permissions.dispatcher.* import com.navinfo.volvo.ui.message.MessageActivity
import com.navinfo.volvo.utils.SystemConstant
//@RuntimePermissions
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
@NeedsPermission(Manifest.permission.CAMERA) public override fun onCreate(savedInstanceState: Bundle?) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
@ -34,12 +52,101 @@ class MainActivity : AppCompatActivity() {
R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications, R.id.navigation_obtain_message R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications, R.id.navigation_obtain_message
) )
) )
// setupActionBarWithNavController(navController, appBarConfiguration) setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController) navView.setupWithNavController(navController)
findViewById<View>(R.id.fab_new_message).apply {
this.setOnClickListener {
// 跳转到Message的Fragment
val messageIntent:Intent = Intent(this@MainActivity, MessageActivity::class.java)
startActivity(messageIntent)
// findNavController(R.id.layer_main_child_fragment).navigate(R.id.navigation_obtain_message)
}
} }
@OnShowRationale(Manifest.permission.CAMERA) XXPermissions.with(this)
fun showRationaleForCamera(request: PermissionRequest) { // 申请单个权限
.permission(Permission.WRITE_EXTERNAL_STORAGE)
.permission(Permission.READ_EXTERNAL_STORAGE)
// 设置权限请求拦截器(局部设置)
//.interceptor(new PermissionInterceptor())
// 设置不触发错误检测机制(局部设置)
//.unchecked()
.request(object : OnPermissionCallback {
override fun onGranted(permissions: MutableList<String>, all: Boolean) {
if (!all) {
Toast.makeText(this@MainActivity, "获取部分权限成功,但部分权限未正常授予", Toast.LENGTH_SHORT).show()
return
}
// 在SD卡创建项目目录
createRootFolder()
}
override fun onDenied(permissions: MutableList<String>, never: Boolean) {
if (never) {
Toast.makeText(this@MainActivity, "永久拒绝授权,请手动授权文件读写权限", Toast.LENGTH_SHORT).show()
// 如果是被永久拒绝就跳转到应用权限系统设置页面
XXPermissions.startPermissionActivity(this@MainActivity, permissions)
} else {
onSDCardDenied()
showRationaleForSDCard(permissions)
}
}
})
}
// @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<String>) {
// showRationaleDialog(R.string.permission_camera_rationale, request) // showRationaleDialog(R.string.permission_camera_rationale, request)
// Toast.makeText(context, "当前操作需要您授权相机权限!", Toast.LENGTH_SHORT).show() // Toast.makeText(context, "当前操作需要您授权相机权限!", Toast.LENGTH_SHORT).show()
MaterialAlertDialogBuilder(this) MaterialAlertDialogBuilder(this)
@ -47,19 +154,13 @@ class MainActivity : AppCompatActivity() {
.setMessage("当前操作需要您授权读写SD卡权限") .setMessage("当前操作需要您授权读写SD卡权限")
.setPositiveButton("确定", DialogInterface.OnClickListener { dialogInterface, i -> .setPositiveButton("确定", DialogInterface.OnClickListener { dialogInterface, i ->
dialogInterface.dismiss() dialogInterface.dismiss()
// 在SD卡创建项目目录 XXPermissions.startPermissionActivity(this@MainActivity, permissions)
}) })
.show() .show()
} }
@OnPermissionDenied(Manifest.permission.CAMERA) // @OnPermissionDenied(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
fun onCameraDenied() { fun onSDCardDenied() {
Toast.makeText(this, "当前操作需要您授权读写SD卡权限", Toast.LENGTH_SHORT).show() Toast.makeText(this, "当前操作需要您授权读写SD卡权限", Toast.LENGTH_SHORT).show()
} }
@OnNeverAskAgain(Manifest.permission.CAMERA)
fun onCameraNeverAskAgain() {
Toast.makeText(this, "您已永久拒绝授权读写SD卡权限", Toast.LENGTH_SHORT).show()
}
} }

View File

@ -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()
}
}

View File

@ -7,9 +7,17 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider 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.databinding.FragmentCameraBinding
import com.navinfo.volvo.ui.message.ObtainMessageViewModel
import com.navinfo.volvo.utils.SystemConstant
import com.otaliastudios.cameraview.CameraListener import com.otaliastudios.cameraview.CameraListener
import com.otaliastudios.cameraview.CameraView import com.otaliastudios.cameraview.CameraView
import com.otaliastudios.cameraview.FileCallback
import com.otaliastudios.cameraview.PictureResult import com.otaliastudios.cameraview.PictureResult
import top.zibin.luban.Luban import top.zibin.luban.Luban
import top.zibin.luban.OnCompressListener import top.zibin.luban.OnCompressListener
@ -24,6 +32,9 @@ class CameraFragment : Fragment() {
// This property is only valid between onCreateView and // This property is only valid between onCreateView and
// onDestroyView. // onDestroyView.
private val binding get() = _binding!! private val binding get() = _binding!!
// private val exportFolderPath by lazy {
// "${SystemConstant.ROOT_PATH}/exportPic/"
// }
private val cameraLifeCycleObserver: CameraLifeCycleObserver by lazy { private val cameraLifeCycleObserver: CameraLifeCycleObserver by lazy {
CameraLifeCycleObserver() CameraLifeCycleObserver()
@ -34,7 +45,7 @@ class CameraFragment : Fragment() {
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
lifecycle.addObserver(cameraLifeCycleObserver) // lifecycle.addObserver(cameraLifeCycleObserver)
val cameraViewModel = val cameraViewModel =
ViewModelProvider(this).get(CameraViewModel::class.java) ViewModelProvider(this).get(CameraViewModel::class.java)
@ -47,41 +58,55 @@ class CameraFragment : Fragment() {
cameraView.addCameraListener(object:CameraListener() { // 添加拍照回调 cameraView.addCameraListener(object:CameraListener() { // 添加拍照回调
override fun onPictureTaken(result: PictureResult) { override fun onPictureTaken(result: PictureResult) {
super.onPictureTaken(result) super.onPictureTaken(result)
result.toFile() // 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) Luban.with(context)
.load<Any>(photos) .load<Any>(mutableListOf(resultFile) as List<Any>?)
.ignoreBy(100) .ignoreBy(200)
.setTargetDir(getPath()) .setTargetDir("${SystemConstant.CameraFolder}")
.filter { path -> .filter { path ->
!(TextUtils.isEmpty(path) || path.lowercase(Locale.getDefault()) !(TextUtils.isEmpty(path) || path.lowercase(Locale.getDefault())
.endsWith(".gif")) .endsWith(".gif"))
} }
.setCompressListener(object : OnCompressListener { .setCompressListener(object : OnCompressListener {
override fun onStart() { override fun onStart() {
// TODO 压缩开始前调用,可以在方法内启动 loading UI XLog.d("开始压缩图片")
} }
override fun onSuccess(file: File?) { override fun onSuccess(file: File?) {
// TODO 压缩成功后调用,返回压缩后的图片文件 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) { override fun onError(e: Throwable) {
// TODO 当压缩过程出现问题时调用 XLog.d("压缩图片失败:${e.message}")
} }
}).launch() }).launch()
} }
}) })
}
})
// 点击拍照 // 点击拍照
binding.imgStartCamera.setOnClickListener { binding.imgStartCamera.setOnClickListener {
cameraView.takePicture() cameraView.takePicture()
} }
return root return root
} }
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
_binding = null _binding = null
lifecycle.removeObserver(cameraLifeCycleObserver) // lifecycle.removeObserver(cameraLifeCycleObserver)
} }
} }

View File

@ -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()
}
}

View File

@ -1,27 +1,56 @@
package com.navinfo.volvo.ui.message package com.navinfo.volvo.ui.message
import android.Manifest
import android.content.DialogInterface import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.*
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.AdapterView.OnItemSelectedListener
import android.widget.ArrayAdapter
import android.widget.Toast import android.widget.Toast
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.navigation.Navigation
import com.easytools.tools.DateUtils
import com.easytools.tools.ToastUtils
import com.elvishew.xlog.XLog
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.gredicer.datetimepicker.DateTimePickerFragment 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.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.ui.markRequiredInRed
import permissions.dispatcher.* 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.* import java.util.*
//@RuntimePermissions
class ObtainMessageFragment: Fragment() { class ObtainMessageFragment: Fragment() {
private var _binding: FragmentObtainMessageBinding? = null private var _binding: FragmentObtainMessageBinding? = null
private val obtainMessageViewModel by lazy { private val obtainMessageViewModel by lazy {
ViewModelProvider(requireActivity()).get(ObtainMessageViewModel::class.java) 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 // This property is only valid between onCreateView and
// onDestroyView. // onDestroyView.
@ -35,16 +64,43 @@ class ObtainMessageFragment: Fragment() {
_binding = FragmentObtainMessageBinding.inflate(inflater, container, false) _binding = FragmentObtainMessageBinding.inflate(inflater, container, false)
val root: View = binding.root val root: View = binding.root
obtainMessageViewModel.setCurrentMessage(Message())
obtainMessageViewModel?.getMessageLiveData()?.observe( obtainMessageViewModel?.getMessageLiveData()?.observe(
viewLifecycleOwner, Observer { viewLifecycleOwner, Observer {
// 初始化界面显示内容 // 初始化界面显示内容
if(it.title!=null) if(it.title?.isNotEmpty() == true)
binding.tvMessageTitle?.setText(it.title) binding.tvMessageTitle?.setText(it.title)
if (it.sendDate!=null) { if (it.sendDate?.isNotEmpty() == true) {
binding.btnSendTime.setText(it.sendDate) 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
}
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() initView()
return root return root
} }
@ -52,13 +108,43 @@ class ObtainMessageFragment: Fragment() {
fun initView() { fun initView() {
// 设置问候信息提示的红色星号 // 设置问候信息提示的红色星号
binding.tiLayoutTitle.markRequiredInRed() 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<String>("绑定车辆1(LYVXFEFEXNL754427)")
binding.edtSendTo.adapter = ArrayAdapter<String>(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 { binding.btnSendTime.setOnClickListener {
val dialog = DateTimePickerFragment.newInstance().mode(0) val dialog = DateTimePickerFragment.newInstance().mode(0)
dialog.listener = object : DateTimePickerFragment.OnClickListener { dialog.listener = object : DateTimePickerFragment.OnClickListener {
override fun onClickListener(selectTime: String) { override fun onClickListener(selectTime: String) {
val sendDate = DateUtils.str2Date(selectTime, "yyyy-MM-dd HH:mm")
if (sendDate <= Date()) {
obtainMessageViewModel.updateMessageSendTime("现在")
} else {
obtainMessageViewModel.updateMessageSendTime(selectTime) obtainMessageViewModel.updateMessageSendTime(selectTime)
} }
}
} }
dialog.show(parentFragmentManager, "SelectSendTime") dialog.show(parentFragmentManager, "SelectSendTime")
@ -67,7 +153,195 @@ class ObtainMessageFragment: Fragment() {
// 点击按钮选择拍照 // 点击按钮选择拍照
binding.btnStartCamera.setOnClickListener { binding.btnStartCamera.setOnClickListener {
// 启动相机 // 启动相机
startCamera() XXPermissions.with(this)
// 申请单个权限
.permission(Permission.CAMERA)
.request(object : OnPermissionCallback {
override fun onGranted(permissions: MutableList<String>, all: Boolean) {
if (!all) {
Toast.makeText(activity, "获取部分权限成功,但部分权限未正常授予", Toast.LENGTH_SHORT).show()
return
}
// 开始启动拍照界面
photoHelper.setCrop(true).takePhoto(activity!!)
}
override fun onDenied(permissions: MutableList<String>, 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<String>, 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<String>, 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<Any>(mutableListOf(it) as List<Any>?)
.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)
}
} }
} }
@ -76,32 +350,44 @@ class ObtainMessageFragment: Fragment() {
_binding = null _binding = null
} }
@NeedsPermission(Manifest.permission.CAMERA) fun startCamera(it: View) {
fun startCamera() { Navigation.findNavController(binding.root).navigate(com.navinfo.volvo.R.id.nav_2_camera)
} }
@OnShowRationale(Manifest.permission.CAMERA) fun showRationaleForCamera(permissions: MutableList<String>) {
fun showRationaleForCamera(request: PermissionRequest) {
// showRationaleDialog(R.string.permission_camera_rationale, request) // showRationaleDialog(R.string.permission_camera_rationale, request)
// Toast.makeText(context, "当前操作需要您授权相机权限!", Toast.LENGTH_SHORT).show() // Toast.makeText(context, "当前操作需要您授权相机权限!", Toast.LENGTH_SHORT).show()
MaterialAlertDialogBuilder(context!!) MaterialAlertDialogBuilder(context!!)
.setTitle("提示") .setTitle("提示")
.setMessage("当前操作需要您授权相机权限!") .setMessage("当前操作需要您授权拍摄权限!")
.setPositiveButton("确定", DialogInterface.OnClickListener { dialogInterface, i -> .setPositiveButton("确定", DialogInterface.OnClickListener { dialogInterface, i ->
startCamera()
dialogInterface.dismiss() dialogInterface.dismiss()
XXPermissions.startPermissionActivity(activity!!, permissions)
}) })
.show() .show()
} }
@OnPermissionDenied(Manifest.permission.CAMERA) // @OnPermissionDenied(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
fun onCameraDenied() { fun onCameraDenied() {
Toast.makeText(context, "当前操作需要您授权相机权限!", Toast.LENGTH_SHORT).show() ToastUtils.showToast("当前操作需要您授权拍摄权限!")
} }
@OnNeverAskAgain(Manifest.permission.CAMERA) fun showRationaleForRecorder(permissions: MutableList<String>) {
fun onCameraNeverAskAgain() { MaterialAlertDialogBuilder(context!!)
Toast.makeText(context, "您已永久拒绝授权相机权限!", Toast.LENGTH_SHORT).show() .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)
} }
} }

View File

@ -4,8 +4,10 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.liveData import androidx.lifecycle.liveData
import com.navinfo.volvo.db.dao.entity.Attachment
import com.navinfo.volvo.db.dao.entity.Message import com.navinfo.volvo.db.dao.entity.Message
import com.navinfo.volvo.db.dao.entity.AttachmentType import com.navinfo.volvo.db.dao.entity.AttachmentType
import java.util.UUID
class ObtainMessageViewModel: ViewModel() { class ObtainMessageViewModel: ViewModel() {
private val msgLiveData: MutableLiveData<Message> by lazy { private val msgLiveData: MutableLiveData<Message> by lazy {
@ -27,21 +29,40 @@ class ObtainMessageViewModel: ViewModel() {
} }
// 更新消息附件中的照片文件 // 更新消息附件中的照片文件
fun updateMessagePic(picUrl: String) { fun updateMessagePic(picUrl: String?) {
var hasPic = false
for (attachment in this.msgLiveData.value!!.attachment) { for (attachment in this.msgLiveData.value!!.attachment) {
if (attachment.attachmentType == AttachmentType.PIC) { if (attachment.attachmentType == AttachmentType.PIC) {
if (picUrl==null||picUrl.isEmpty()) {
this.msgLiveData.value!!.attachment.remove(attachment)
} else {
attachment.pathUrl = picUrl 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) this.msgLiveData.postValue(this.msgLiveData.value)
} }
// 更新消息附件中的录音文件 // 更新消息附件中的录音文件
fun updateMessageAudio(audioUrl: String) { fun updateMessageAudio(audioUrl: String?) {
var hasAudio = false
for (attachment in this.msgLiveData.value!!.attachment) { for (attachment in this.msgLiveData.value!!.attachment) {
if (attachment.attachmentType == AttachmentType.AUDIO) { if (attachment.attachmentType == AttachmentType.AUDIO) {
if (audioUrl==null||audioUrl.isEmpty()) {
this.msgLiveData.value!!.attachment.remove(attachment)
} else {
attachment.pathUrl = audioUrl 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) this.msgLiveData.postValue(this.msgLiveData.value)
} }

View File

@ -1,187 +0,0 @@
package com.navinfo.volvo.ui.widget
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import android.view.*
import android.widget.Scroller
import androidx.core.view.forEach
import androidx.recyclerview.widget.RecyclerView
import java.lang.Math.abs
class SlideRecyclerView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr) {
//系统最小移动距离
private val mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
//最小有效速度
private val mMinVelocity = 600
//增加手势控制,双击快速完成侧滑
private var isDoubleClick = false
private var mGestureDetector: GestureDetector =
GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
override fun onDoubleTap(e: MotionEvent?): Boolean {
e?.let { event ->
getSelectItem(event)
mItem?.let {
val deleteWith = it.getChildAt(it.childCount - 1).width
//触发移动至完全展开deleteWidth
if (it.scrollX == 0) {
mScroller.startScroll(0, 0, deleteWith, 0)
} else {
mScroller.startScroll(it.scrollX, 0, -it.scrollX, 0)
}
isDoubleClick = true
invalidate()
return true
}
}
//不进行拦截,只作为工具判断下双击
return false
}
})
//使用速度控制器,增加侧滑速度判定滑动成功,
//VelocityTracker 由native实现需要及时释放内存
private var mVelocityTracker: VelocityTracker? = null
//流畅滑动
private var mScroller = Scroller(context)
//当前选中item
private var mItem: ViewGroup? = null
//上次按下的横坐标
private var mLastX = 0f
//当前RecyclerView被上层ViewGroup分发到事件所有事件都会通过dispatchTouchEvent给到
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
mGestureDetector.onTouchEvent(ev)
return super.dispatchTouchEvent(ev)
}
//viewGroup对子控件的事件拦截一旦拦截后续事件序列不会再调用onInterceptTouchEvent
override fun onInterceptTouchEvent(e: MotionEvent?): Boolean {
e?.let {
when (e.action) {
MotionEvent.ACTION_DOWN -> {
getSelectItem(e)
mLastX = e.x
}
MotionEvent.ACTION_MOVE -> {
//移动控件
return moveItem(e)
}
// MotionEvent.ACTION_UP -> {
// stopMove(e)
// }
}
}
return super.onInterceptTouchEvent(e)
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(e: MotionEvent?): Boolean {
e?.let {
when (e.action) {
MotionEvent.ACTION_MOVE -> {
moveItem(e)
mLastX = e.x
}
MotionEvent.ACTION_UP -> {
stopMove()
}
}
}
return super.onTouchEvent(e)
}
//活动结束
//判断一下结束的位置,补充或恢复位置
private fun stopMove() {
mItem?.let {
//如果移动过半,判定左划成功
val deleteWidth = it.getChildAt(it.childCount - 1).width
//如果整个移动过程速度大于600也判定滑动成功
//注意如果没有拦截ACTION_MOVE,mVelocityTracker是没有初始化的
var velocity = 0f
mVelocityTracker?.let { tracker ->
tracker.computeCurrentVelocity(1000)
velocity = tracker.xVelocity
}
//判断结束情况,移动过半或者向左速度很快都展开
if ((abs(it.scrollX) >= deleteWidth / 2f) || (velocity < -mMinVelocity)) {
//触发移动至完全展开
mScroller.startScroll(it.scrollX, 0, deleteWidth - it.scrollX, 0)
invalidate()
} else {
//如果移动未过半应恢复状态
mScroller.startScroll(it.scrollX, 0, -it.scrollX, 0)
invalidate()
}
}
//清除状态
mLastX = 0f
//mVeloctityTracker由native实现需要及时释放
mVelocityTracker?.apply {
clear()
recycle()
}
mVelocityTracker = null
}
//移动Item
//绝对值小于删除按钮长度随便移动,大于则不移动
@SuppressLint("Recycle")
private fun moveItem(e: MotionEvent): Boolean {
mItem?.let {
val dx = mLastX - e.x
//最小的移动距离应该舍弃onInterceptTouchEvent不拦截onTouchEvent内才更新mLastX
// if (abs(dx) > mTouchSlop) {
//检查mItem移动后应该在【-deleteLength0】内
val deleteWith = it.getChildAt(it.childCount - 1).width
if ((it.scrollX + dx) <= deleteWith && (it.scrollX + dx) >= 0) {
//触发移动
it.scrollBy(dx.toInt(), 0)
//触发速度计算
//这里Rectycle不存在问题一旦返回true就会拦截事件就会到达ACTION_UP去回收
mVelocityTracker = mVelocityTracker ?: VelocityTracker.obtain()
mVelocityTracker!!.addMovement(e)
return true
// }
}
}
return false
}
//获取点击位置
//通过点击的y坐标除以Item高度得出
private fun getSelectItem(e: MotionEvent) {
val frame = Rect()
mItem = null
forEach {
if (it.visibility != GONE) {
it.getHitRect(frame)
if (frame.contains(e.x.toInt(), e.y.toInt())) {
mItem = it as ViewGroup
}
}
}
}
//流畅地滑动
override fun computeScroll() {
if (mScroller.computeScrollOffset()) {
mItem?.scrollBy(mScroller.currX, mScroller.currY)
postInvalidate()
}
}
}

View File

@ -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
* 描述多媒体选择工具类
* @authorVincent
*/
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<Intent>()
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<Intent>()
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<Intent>()
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
}
}
}
}

View File

@ -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
}
}

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#000000" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M18.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM5,9v6h4l5,5V4L9,9H5z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#000000" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M7,9v6h4l5,5V4l-5,5H7z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#000000" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"/>
</vector>

View File

@ -12,6 +12,7 @@
android:layout_marginStart="0dp" android:layout_marginStart="0dp"
android:layout_marginEnd="0dp" android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground" android:background="?android:attr/windowBackground"
app:labelVisibilityMode="labeled"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
@ -29,4 +30,26 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" /> app:navGraph="@navigation/mobile_navigation" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_new_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:src="@drawable/ic_add_24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:elevation="8dp"
/>
<FrameLayout
android:id="@+id/layer_main_child_fragment"
android:layout_width="0dp"
android:layout_height="0dp"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/nav_graph"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"></FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.message.MessageActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/Theme.NavinfoVolvo.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:navigationIcon="@drawable/ic_back_file_picker"
app:popupTheme="@style/Theme.NavinfoVolvo.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<include layout="@layout/content_message" />
<!-- <com.google.android.material.floatingactionbutton.FloatingActionButton-->
<!-- android:id="@+id/fab"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_gravity="bottom|end"-->
<!-- android:layout_marginEnd="@dimen/fab_margin"-->
<!-- android:layout_marginBottom="16dp"-->
<!-- app:srcCompat="@android:drawable/ic_dialog_email" />-->
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -9,7 +9,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="8dp" android:layout_marginLeft="8dp"
android:src="@mipmap/ic_launcher" android:src="@mipmap/volvo_logo_small"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<fragment
android:id="@+id/nav_host_fragment_message"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -6,8 +6,8 @@
<com.otaliastudios.cameraview.CameraView <com.otaliastudios.cameraview.CameraView
android:id="@+id/camera" android:id="@+id/camera"
android:keepScreenOn="true" android:keepScreenOn="true"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="match_parent" />
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/img_start_camera" android:id="@+id/img_start_camera"

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FirstFragment">
<TextView
android:id="@+id/textview_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_first_fragment"
app:layout_constraintBottom_toTopOf="@id/button_first"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/next"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textview_first" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -27,7 +27,7 @@
app:counterEnabled="true" app:counterEnabled="true"
app:counterMaxLength="10" app:counterMaxLength="10"
app:errorEnabled="true" app:errorEnabled="true"
android:hint="问候信息" android:hint="请输入问候信息"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
@ -40,13 +40,19 @@
tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck" /> tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.nhaarman.supertooltips.ToolTipRelativeLayout
android:id="@+id/tt_title"
app:layout_constraintTop_toBottomOf="@id/ti_layout_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/label_message_subtitle" android:id="@+id/label_message_subtitle"
style="@style/TextAppearance.AppCompat.Subhead" style="@style/TextAppearance.AppCompat.Subhead"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="问候信息" android:text="问候附件"
app:layout_constraintTop_toBottomOf="@id/ti_layout_title"></com.google.android.material.textview.MaterialTextView> app:layout_constraintTop_toBottomOf="@id/tt_title"></com.google.android.material.textview.MaterialTextView>
<com.google.android.material.divider.MaterialDivider <com.google.android.material.divider.MaterialDivider
android:id="@+id/div_message" android:id="@+id/div_message"
@ -77,10 +83,15 @@
android:textColor="@color/red"></TextView> android:textColor="@color/red"></TextView>
<TextView <TextView
android:id="@+id/tv_upload_pic"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="上传图片:"></TextView> android:text="上传图片:"></TextView>
<LinearLayout
android:id="@+id/layer_get_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn_start_camera" android:id="@+id/btn_start_camera"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -101,6 +112,44 @@
android:text="相册选择" android:text="相册选择"
android:padding="@dimen/default_widget_padding"></com.google.android.material.button.MaterialButton> android:padding="@dimen/default_widget_padding"></com.google.android.material.button.MaterialButton>
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/layer_photo_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:visibility="gone"
android:orientation="horizontal">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tv_photo_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""></com.google.android.material.textview.MaterialTextView>
<Space
android:layout_width="@dimen/default_widget_padding"
android:layout_height="wrap_content"></Space>
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/img_photo_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:src="@drawable/ic_baseline_delete_24"></com.google.android.material.imageview.ShapeableImageView>
</LinearLayout>
</LinearLayout>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/img_message_attachment"
android:layout_width="160dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:scaleType="fitCenter">
</androidx.appcompat.widget.AppCompatImageView>
<com.nhaarman.supertooltips.ToolTipRelativeLayout
android:id="@+id/tt_pic"
app:layout_constraintTop_toBottomOf="@id/ti_layout_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout <LinearLayout
style="@style/default_line" style="@style/default_line"
@ -119,18 +168,25 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="上传音频:"></TextView> android:text="上传音频:"></TextView>
<LinearLayout
android:id="@+id/layer_get_audio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn_start_record"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/Widget.Material3.Button.ElevatedButton" style="@style/Widget.Material3.Button.ElevatedButton"
app:icon="@drawable/ic_baseline_fiber_manual_record_24" app:icon="@drawable/ic_baseline_fiber_manual_record_24"
android:text="长按录音" android:text="录"
android:padding="@dimen/default_widget_padding"></com.google.android.material.button.MaterialButton> android:padding="@dimen/default_widget_padding"></com.google.android.material.button.MaterialButton>
<Space <Space
android:layout_width="@dimen/default_widget_padding" android:layout_width="@dimen/default_widget_padding"
android:layout_height="wrap_content"></Space> android:layout_height="wrap_content"></Space>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn_select_sound"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/Widget.Material3.Button.ElevatedButton" style="@style/Widget.Material3.Button.ElevatedButton"
@ -138,6 +194,43 @@
android:text="音频选择" android:text="音频选择"
android:padding="@dimen/default_widget_padding"></com.google.android.material.button.MaterialButton> android:padding="@dimen/default_widget_padding"></com.google.android.material.button.MaterialButton>
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/layer_audio_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:visibility="gone"
android:orientation="horizontal">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tv_audio_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""></com.google.android.material.textview.MaterialTextView>
<Space
android:layout_width="@dimen/default_widget_padding"
android:layout_height="wrap_content"></Space>
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/img_audio_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:src="@drawable/ic_baseline_delete_24"></com.google.android.material.imageview.ShapeableImageView>
</LinearLayout>
</LinearLayout>
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/img_sound_play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_baseline_volume_up_24"
android:visibility="gone"
android:layout_gravity="center"></com.google.android.material.imageview.ShapeableImageView>
<com.nhaarman.supertooltips.ToolTipRelativeLayout
android:id="@+id/tt_audio"
app:layout_constraintTop_toBottomOf="@id/ti_layout_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
@ -188,6 +281,12 @@
android:background="@drawable/selector_bg_4_round_corner"></androidx.appcompat.widget.AppCompatEditText> android:background="@drawable/selector_bg_4_round_corner"></androidx.appcompat.widget.AppCompatEditText>
</LinearLayout> </LinearLayout>
<com.nhaarman.supertooltips.ToolTipRelativeLayout
android:id="@+id/tt_send_from"
app:layout_constraintTop_toBottomOf="@id/ti_layout_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout <LinearLayout
style="@style/default_line" style="@style/default_line"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -206,13 +305,18 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="发给谁:"></TextView> android:text="发给谁:"></TextView>
<androidx.appcompat.widget.AppCompatEditText <androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/edt_send_to" android:id="@+id/edt_send_to"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"></androidx.appcompat.widget.AppCompatSpinner>
android:background="@drawable/selector_bg_4_round_corner"></androidx.appcompat.widget.AppCompatEditText>
</LinearLayout> </LinearLayout>
<com.nhaarman.supertooltips.ToolTipRelativeLayout
android:id="@+id/tt_send_to"
app:layout_constraintTop_toBottomOf="@id/ti_layout_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout <LinearLayout
style="@style/default_line" style="@style/default_line"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -240,7 +344,11 @@
android:text="现在" android:text="现在"
android:padding="@dimen/default_widget_padding"></com.google.android.material.button.MaterialButton> android:padding="@dimen/default_widget_padding"></com.google.android.material.button.MaterialButton>
</LinearLayout> </LinearLayout>
<com.nhaarman.supertooltips.ToolTipRelativeLayout
android:id="@+id/tt_send_time"
app:layout_constraintTop_toBottomOf="@id/ti_layout_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
@ -252,6 +360,7 @@
app:layout_constraintBottom_toBottomOf="parent"> app:layout_constraintBottom_toBottomOf="parent">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn_obtain_message_back"
style="@style/Widget.Material3.Button.ElevatedButton" style="@style/Widget.Material3.Button.ElevatedButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -263,6 +372,7 @@
android:layout_height="wrap_content"></Space> android:layout_height="wrap_content"></Space>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn_obtain_message_confirm"
style="@style/Widget.Material3.Button" style="@style/Widget.Material3.Button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SecondFragment">
<TextView
android:id="@+id/textview_second"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/button_second"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button_second"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/previous"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textview_second" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -5,16 +5,22 @@
android:id="@+id/navigation_home" android:id="@+id/navigation_home"
android:icon="@drawable/ic_home_black_24dp" android:icon="@drawable/ic_home_black_24dp"
android:title="@string/title_home" /> android:title="@string/title_home" />
<item <item
android:id="@+id/navigation_dashboard" android:id="@+id/navigation_dashboard"
android:icon="@drawable/ic_dashboard_black_24dp" android:icon="@drawable/ic_dashboard_black_24dp"
android:title="@string/title_dashboard" /> android:title="@string/title_dashboard" />
<item
android:id="@+id/navigation_none"
android:icon="@color/black"
android:title=" " />
<item <item
android:id="@+id/navigation_notifications" android:id="@+id/navigation_notifications"
android:icon="@drawable/ic_notifications_black_24dp" android:icon="@drawable/ic_notifications_black_24dp"
android:title="@string/title_notifications" /> android:title="@string/title_notifications" />
<!-- <item-->
<!-- android:id="@+id/navigation_my"-->
<!-- android:icon="@drawable/ic_baseline_person_24"-->
<!-- android:title="@string/my" />-->
<item <item
android:id="@+id/navigation_obtain_message" android:id="@+id/navigation_obtain_message"

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@ -22,24 +22,16 @@
android:label="@string/title_dashboard" android:label="@string/title_dashboard"
tools:layout="@layout/fragment_dashboard" /> tools:layout="@layout/fragment_dashboard" />
<fragment
android:id="@+id/fab_new_message"
android:name="com.navinfo.volvo.ui.dashboard.DashboardFragment"
android:label="@string/title_dashboard"
tools:layout="@layout/fragment_dashboard" />
<fragment <fragment
android:id="@+id/navigation_notifications" android:id="@+id/navigation_notifications"
android:name="com.navinfo.volvo.ui.notifications.NotificationsFragment" android:name="com.navinfo.volvo.ui.notifications.NotificationsFragment"
android:label="@string/title_notifications" android:label="@string/title_notifications"
tools:layout="@layout/fragment_notifications" /> tools:layout="@layout/fragment_notifications" />
<fragment
android:id="@+id/navigation_obtain_message"
android:name="com.navinfo.volvo.ui.message.ObtainMessageFragment"
android:label="问候编辑"
tools:layout="@layout/fragment_obtain_message" />
<fragment
android:id="@+id/navigation_camera"
android:name="com.navinfo.volvo.ui.camera.CameraFragment"
android:label="@string/title_dashboard"
tools:layout="@layout/fragment_camera" />
<action android:id="@+id/nav_2_camera"
app:destination="@id/nav_2_camera"></action>
</navigation> </navigation>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/navigation_obtain_message">
<fragment
android:id="@+id/navigation_obtain_message"
android:name="com.navinfo.volvo.ui.message.ObtainMessageFragment"
android:label="问候编辑"
tools:layout="@layout/fragment_obtain_message" >
<action
android:id="@+id/nav_2_camera"
app:destination="@id/navigation_camera"></action>
</fragment>
<fragment
android:id="@+id/navigation_camera"
android:name="com.navinfo.volvo.ui.camera.CameraFragment"
android:label="拍照"
tools:layout="@layout/fragment_camera" />
</navigation>

View File

@ -0,0 +1,3 @@
<resources>
<dimen name="fab_margin">48dp</dimen>
</resources>

View File

@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="Theme.NavinfoVolvo" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <style name="Theme.NavinfoVolvo" parent="Theme.Material3.Dark">
<!-- Primary brand color. --> <!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item> <item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item> <item name="colorPrimaryVariant">@color/purple_700</item>

View File

@ -0,0 +1,3 @@
<resources>
<dimen name="fab_margin">200dp</dimen>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<dimen name="fab_margin">48dp</dimen>
</resources>

View File

@ -5,4 +5,5 @@
<string name="title_notifications">Notifications</string> <string name="title_notifications">Notifications</string>
<string name="delete">删除</string> <string name="delete">删除</string>
<string name="share">分享</string> <string name="share">分享</string>
<string name="my">我的</string>
</resources> </resources>

View File

@ -5,4 +5,5 @@
<dimen name="activity_default_padding">12dp</dimen> <dimen name="activity_default_padding">12dp</dimen>
<dimen name="default_font_size">18sp</dimen> <dimen name="default_font_size">18sp</dimen>
<dimen name="default_widget_padding">6dp</dimen> <dimen name="default_widget_padding">6dp</dimen>
<dimen name="fab_margin">16dp</dimen>
</resources> </resources>

View File

@ -5,4 +5,14 @@
<string name="title_notifications">Notifications</string> <string name="title_notifications">Notifications</string>
<string name="delete">Del</string> <string name="delete">Del</string>
<string name="share">Share</string> <string name="share">Share</string>
<string name="my">My</string>
<string name="title_activity_second">SecondActivity</string>
<!-- Strings used for fragments for navigation -->
<string name="first_fragment_label">First Fragment</string>
<string name="second_fragment_label">Second Fragment</string>
<string name="next">Next</string>
<string name="previous">Previous</string>
<string name="hello_first_fragment">Hello first fragment</string>
<string name="hello_second_fragment">Hello second fragment. Arg: %1$s</string>
</resources> </resources>

View File

@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="Theme.NavinfoVolvo" parent="Theme.Material3.DayNight.NoActionBar"> <style name="Theme.NavinfoVolvo" parent="Theme.Material3.Light">
<!-- Primary brand color. --> <!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item> <item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item> <item name="colorPrimaryVariant">@color/purple_700</item>
@ -13,4 +13,13 @@
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item> <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
</style> </style>
<style name="Theme.NavinfoVolvo.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="Theme.NavinfoVolvo.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="Theme.NavinfoVolvo.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources> </resources>