feat: 初始化项目结构和基本功能
- 创建项目根目录和主要子模块 - 添加基本的 Activity 和布局文件 - 实现简单的导航和电话拨打功能 - 添加相机和图像处理相关代码 - 创建网络请求和数据加密工具类 - 设置 AndroidManifest 文件和权限
This commit is contained in:
113
servicing/src/main/java/com/za/common/GlobalData.kt
Normal file
113
servicing/src/main/java/com/za/common/GlobalData.kt
Normal file
@ -0,0 +1,113 @@
|
||||
package com.za.common
|
||||
|
||||
import android.app.Application
|
||||
import com.amap.api.location.AMapLocation
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.za.bean.DriverInfo
|
||||
import com.za.bean.VehicleInfo
|
||||
import com.za.bean.db.order.OrderInfo
|
||||
import com.za.room.RoomHelper
|
||||
|
||||
object GlobalData {
|
||||
lateinit var application : Application
|
||||
var activityCount : Int = 0
|
||||
|
||||
var token : String? = null
|
||||
get() {
|
||||
return MMKV.defaultMMKV().decodeString("TOKEN", null)
|
||||
}
|
||||
set(value) {
|
||||
MMKV.defaultMMKV().encode("TOKEN", value)
|
||||
field = value
|
||||
}
|
||||
|
||||
//记录上次登录的手机号
|
||||
var lastLoginPhone : String? = null
|
||||
get() {
|
||||
return MMKV.defaultMMKV().decodeString("lastLoginPhone", null)
|
||||
}
|
||||
set(value) {
|
||||
MMKV.defaultMMKV().encode("lastLoginPhone", value)
|
||||
field = value
|
||||
}
|
||||
|
||||
var aesKey : String? = null
|
||||
get() {
|
||||
return MMKV.defaultMMKV().decodeString("AES_KEY", null)
|
||||
}
|
||||
set(value) {
|
||||
MMKV.defaultMMKV().encode("AES_KEY", value)
|
||||
field = value
|
||||
}
|
||||
|
||||
var driverInfo : DriverInfo? = null
|
||||
get() {
|
||||
return MMKV.defaultMMKV().decodeParcelable("driverInfo", DriverInfo::class.java)
|
||||
}
|
||||
set(value) {
|
||||
MMKV.defaultMMKV().encode("driverInfo", value)
|
||||
field = value
|
||||
}
|
||||
|
||||
var vehicleInfo : VehicleInfo? = null
|
||||
get() {
|
||||
return MMKV.defaultMMKV().decodeParcelable("vehicleInfo", VehicleInfo::class.java)
|
||||
}
|
||||
set(value) {
|
||||
MMKV.defaultMMKV().encode("vehicleInfo", value)
|
||||
field = value
|
||||
}
|
||||
|
||||
|
||||
var currentOrder : OrderInfo? = null
|
||||
get() {
|
||||
return MMKV.defaultMMKV().decodeParcelable("currentOrder", OrderInfo::class.java)
|
||||
}
|
||||
set(value) {
|
||||
MMKV.defaultMMKV().encode("currentOrder", value)
|
||||
if (RoomHelper.db?.orderDao()?.getCurrentOrder() == null && value != null) {
|
||||
RoomHelper.db?.orderDao()?.insertOrder(value)
|
||||
} else if (value != null) {
|
||||
RoomHelper.db?.orderDao()?.update(value)
|
||||
}
|
||||
field = value
|
||||
}
|
||||
|
||||
var currentLocation : AMapLocation? = null
|
||||
get() {
|
||||
return MMKV.defaultMMKV().decodeParcelable("currentLocation", AMapLocation::class.java)
|
||||
}
|
||||
set(value) {
|
||||
value?.time = System.currentTimeMillis()
|
||||
MMKV.defaultMMKV().encode("currentLocation", value)
|
||||
field = value
|
||||
}
|
||||
|
||||
var loginTime : Long? = null
|
||||
get() {
|
||||
return MMKV.defaultMMKV().decodeLong("loginTime", System.currentTimeMillis())
|
||||
}
|
||||
set(value) {
|
||||
MMKV.defaultMMKV().encode("loginTime", value ?: System.currentTimeMillis())
|
||||
field = value
|
||||
}
|
||||
|
||||
fun clearUserCache() {
|
||||
token = null
|
||||
aesKey = null
|
||||
driverInfo = null
|
||||
vehicleInfo = null
|
||||
currentLocation = null
|
||||
loginTime = null
|
||||
}
|
||||
|
||||
fun clearAllOrderCache() {
|
||||
currentOrder = null
|
||||
RoomHelper.clearAll()
|
||||
}
|
||||
|
||||
fun clearOrderCache(taskId : Int) {
|
||||
RoomHelper.clearOrderFromTaskCode(taskId = taskId)
|
||||
}
|
||||
|
||||
}
|
35
servicing/src/main/java/com/za/common/ZDManager.kt
Normal file
35
servicing/src/main/java/com/za/common/ZDManager.kt
Normal file
@ -0,0 +1,35 @@
|
||||
package com.za.common
|
||||
|
||||
import android.app.Application
|
||||
import com.tencent.bugly.Bugly
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.tencent.mmkv.MMKVLogLevel
|
||||
import com.za.base.AppConfig
|
||||
import com.za.common.log.LogUtil
|
||||
import com.za.room.RoomHelper
|
||||
import com.za.service.location.ZdLocationManager
|
||||
|
||||
object ZDManager {
|
||||
lateinit var application : Application
|
||||
fun init(application : Application) {
|
||||
this.application = application
|
||||
thirdSdkInit()
|
||||
}
|
||||
|
||||
private fun thirdSdkInit() {
|
||||
GlobalData.application = application
|
||||
MMKV.initialize(application, MMKVLogLevel.LevelInfo)
|
||||
Bugly.init(application, "6972a6b56d", true)
|
||||
AppConfig.crm1()
|
||||
LogUtil.init(application)
|
||||
RoomHelper.init(application)
|
||||
ZdLocationManager.init(application)
|
||||
|
||||
|
||||
// 初始化讯飞SDK
|
||||
//科大讯飞初始化
|
||||
// SpeechUtility.createUtility(application,
|
||||
// SpeechConstant.APPID + "=6fd4aabe," + SpeechConstant.FORCE_LOGIN + "=true")
|
||||
// SpeechManager.init(application)
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package com.za.common.log
|
||||
|
||||
import android.util.Log
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.net.URLDecoder
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
object LogRetrofitHelper {
|
||||
private var retrofit: Retrofit? = null
|
||||
private var apiService: LogService? = null
|
||||
private val loggerInterceptor = HttpLoggingInterceptor {
|
||||
try {
|
||||
if (it.contains("image/*")) {
|
||||
return@HttpLoggingInterceptor
|
||||
}
|
||||
if (it.contains("name=\"file\"; filename")) {
|
||||
return@HttpLoggingInterceptor
|
||||
}
|
||||
Log.e(
|
||||
"--network--",
|
||||
URLDecoder.decode(it.replace(Regex("%(?![0-9a-fA-F]{2})"), ""), "utf-8")
|
||||
)
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}.setLevel(HttpLoggingInterceptor.Level.BODY)
|
||||
|
||||
|
||||
fun getDefaultService(): LogService {
|
||||
return if (apiService == null) {
|
||||
apiService = getDefaultRetrofit().create(LogService::class.java)
|
||||
apiService!!
|
||||
} else {
|
||||
apiService!!
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDefaultRetrofit(): Retrofit {
|
||||
return if (retrofit == null) {
|
||||
retrofit = Retrofit.Builder().baseUrl("https://api2.sino-assist.com")
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
|
||||
.client(getOkHttpClient())
|
||||
.build()
|
||||
retrofit!!
|
||||
} else {
|
||||
retrofit!!
|
||||
}
|
||||
}
|
||||
|
||||
private fun getOkHttpClient(): OkHttpClient {
|
||||
return OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.addInterceptor(loggerInterceptor)
|
||||
.build()
|
||||
}
|
||||
}
|
27
servicing/src/main/java/com/za/common/log/LogService.kt
Normal file
27
servicing/src/main/java/com/za/common/log/LogService.kt
Normal file
@ -0,0 +1,27 @@
|
||||
package com.za.common.log
|
||||
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import okhttp3.MultipartBody
|
||||
import retrofit2.http.Multipart
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Part
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface LogService {
|
||||
//日志上传
|
||||
@Multipart
|
||||
@POST("/oss/minio/upload")
|
||||
fun uploadLog(
|
||||
@Part file: MultipartBody.Part?,
|
||||
@Query("fileName") fileName: String?,
|
||||
@Query("bucketName") bucketName: String?
|
||||
): Observable<LogBaseResponse<String>>
|
||||
}
|
||||
|
||||
data class LogBaseResponse<T : Any>(
|
||||
val data: T?,
|
||||
val msg: String?,
|
||||
val success: Boolean?,
|
||||
val code: Int?,
|
||||
val total: Int?,
|
||||
)
|
317
servicing/src/main/java/com/za/common/log/LogUtil.kt
Normal file
317
servicing/src/main/java/com/za/common/log/LogUtil.kt
Normal file
@ -0,0 +1,317 @@
|
||||
package com.za.common.log
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.work.Configuration
|
||||
import androidx.work.PeriodicWorkRequest
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.blankj.utilcode.constant.MemoryConstants
|
||||
import com.blankj.utilcode.util.AppUtils
|
||||
import com.blankj.utilcode.util.DeviceUtils
|
||||
import com.blankj.utilcode.util.FileUtils
|
||||
import com.blankj.utilcode.util.TimeUtils
|
||||
import com.za.common.GlobalData
|
||||
import com.za.common.util.AppFileManager
|
||||
import com.za.servicing.BuildConfig
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.asRequestBody
|
||||
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream
|
||||
import java.io.BufferedWriter
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.FileWriter
|
||||
import java.io.IOException
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
object LogUtil {
|
||||
private var context: Application? = null
|
||||
private var logDestinationPath: String? = null
|
||||
private var orderLogDirPath: String? = null
|
||||
private var normalLogDirPath: String? = null
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
private val logBuffer = StringBuilder()
|
||||
private val isWriting = AtomicBoolean(false)
|
||||
|
||||
fun init(context: Application) {
|
||||
this.context = context
|
||||
|
||||
logDestinationPath = AppFileManager.getLogPath(context).also { path ->
|
||||
createDirectoryIfNotExists(path)
|
||||
orderLogDirPath = "$path${File.separator}order_log".also { createDirectoryIfNotExists(it) }
|
||||
normalLogDirPath = "$path${File.separator}normal_log".also { createDirectoryIfNotExists(it) }
|
||||
}
|
||||
|
||||
initializeWorkManager(context)
|
||||
}
|
||||
|
||||
private fun createDirectoryIfNotExists(path: String) {
|
||||
File(path).apply { if (!exists()) mkdir() }
|
||||
}
|
||||
|
||||
private fun initializeWorkManager(context: Application) {
|
||||
if (!WorkManager.isInitialized()) {
|
||||
WorkManager.initialize(context, Configuration.Builder()
|
||||
.setMinimumLoggingLevel(Log.INFO)
|
||||
.build())
|
||||
}
|
||||
|
||||
WorkManager.getInstance(context).apply {
|
||||
cancelAllWorkByTag("logWorkRequest")
|
||||
enqueue(PeriodicWorkRequest.Builder(LogTask::class.java, 20, TimeUnit.MINUTES)
|
||||
.addTag("logWorkRequest")
|
||||
.build())
|
||||
}
|
||||
}
|
||||
|
||||
fun print(tag: String, content: String) {
|
||||
val time = getCurrentTime()
|
||||
val logEntry = "$time---$tag---$content\n"
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.e("normal", "$tag---$content")
|
||||
}
|
||||
|
||||
synchronized(logBuffer) {
|
||||
logBuffer.append(logEntry)
|
||||
}
|
||||
|
||||
if (logBuffer.length > 4096) {
|
||||
coroutineScope.launch {
|
||||
flushBuffer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun print(tag: String, throwable: Throwable) {
|
||||
val content = StringWriter()
|
||||
val printWriter = PrintWriter(content)
|
||||
throwable.printStackTrace(printWriter)
|
||||
print(tag, content.toString())
|
||||
}
|
||||
|
||||
private suspend fun flushBuffer() = withContext(Dispatchers.IO) {
|
||||
if (!isWriting.compareAndSet(false, true)) return@withContext
|
||||
|
||||
val logContent: String
|
||||
synchronized(logBuffer) {
|
||||
if (logBuffer.isEmpty()) {
|
||||
isWriting.set(false)
|
||||
return@withContext
|
||||
}
|
||||
logContent = logBuffer.toString()
|
||||
logBuffer.clear()
|
||||
}
|
||||
|
||||
try {
|
||||
val fileName = "normal_log.txt"
|
||||
val logFile = File("$normalLogDirPath${File.separator}$fileName")
|
||||
|
||||
logFile.parentFile?.mkdirs()
|
||||
|
||||
if (!logFile.exists()) {
|
||||
logFile.createNewFile()
|
||||
addLogHead(logFile, getCurrentTime())
|
||||
}
|
||||
|
||||
BufferedWriter(FileWriter(logFile, true)).use { writer ->
|
||||
writer.write(logContent)
|
||||
writer.flush()
|
||||
}
|
||||
|
||||
if (logFile.length() >= 8 * MemoryConstants.MB) {
|
||||
rotateLogFile(logFile)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e("LogUtil", "Error in flushBuffer: ${e.message}")
|
||||
} catch (e: Exception) {
|
||||
Log.e("LogUtil", "Error in flushBuffer: ${e.message}")
|
||||
} finally {
|
||||
isWriting.set(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun rotateLogFile(file: File) {
|
||||
if (!file.exists()) return
|
||||
|
||||
val newFileName = buildString {
|
||||
append(AppUtils.getAppVersionCode())
|
||||
append("_")
|
||||
append(GlobalData.vehicleInfo?.vehicleName ?: "unknown")
|
||||
append("_")
|
||||
append(GlobalData.driverInfo?.userName ?: "unknown")
|
||||
append("_")
|
||||
append(TimeUtils.getNowString())
|
||||
append(".txt")
|
||||
}
|
||||
|
||||
val newFile = File("$normalLogDirPath${File.separator}$newFileName")
|
||||
|
||||
try {
|
||||
if (file.renameTo(newFile)) {
|
||||
compressAndUploadLog(newFile)
|
||||
// 创建新的日志文件
|
||||
file.createNewFile()
|
||||
addLogHead(file, getCurrentTime())
|
||||
} else {
|
||||
print("LogUtil", "Failed to rename log file")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
print("LogUtil", "Error during log rotation: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun compressAndUploadLog(logFile: File) = coroutineScope.launch {
|
||||
try {
|
||||
val compressedFile = File("${logFile.absolutePath}.7z")
|
||||
compress(logFile, compressedFile.absolutePath)
|
||||
upload(logFile, compressedFile)
|
||||
} catch (e: Exception) {
|
||||
print("LogUtil", e.toString())
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteLog(file: File) {
|
||||
try {
|
||||
FileUtils.delete(file.absolutePath)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateNormalLog() {
|
||||
thread {
|
||||
if (GlobalData.token.isNullOrBlank()) {
|
||||
return@thread
|
||||
}
|
||||
val fileName = "normal_log.txt"
|
||||
val file = File("$normalLogDirPath${File.separator}$fileName")
|
||||
val reName = "${AppUtils.getAppVersionCode()}_${GlobalData.vehicleInfo?.vehicleName}_${GlobalData.driverInfo?.userName}_${TimeUtils.getNowString()}.txt"
|
||||
val reNamePath = "$normalLogDirPath${File.separator}$reName"
|
||||
file.renameTo(File(reNamePath))
|
||||
normalLogDirPath?.let { it ->
|
||||
File(it).listFiles()?.forEach {
|
||||
if (it.length() / MemoryConstants.MB >= 10) {
|
||||
deleteLog(it)
|
||||
return@thread
|
||||
}
|
||||
if (it.exists() && !it.name.contains("normal_log")) {
|
||||
if (it.name.contains("7z")) {
|
||||
upload(null, desFile = it)
|
||||
} else {
|
||||
val zipNamePath = it.absolutePath + ".7z"
|
||||
val zipFile = File(zipNamePath)
|
||||
if (!zipFile.exists()) {
|
||||
try {
|
||||
zipFile.createNewFile()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
compress(it, zipNamePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun compress(srcFile: File, desFilePath: String) {
|
||||
try {
|
||||
val out = XZCompressorOutputStream(FileOutputStream(desFilePath))
|
||||
addToArchiveCompression(out, srcFile, File(desFilePath))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun addToArchiveCompression(
|
||||
sevenZOutputFile: XZCompressorOutputStream,
|
||||
srcFile: File, desFile: File
|
||||
) {
|
||||
if (srcFile.isFile) {
|
||||
var inputStream: FileInputStream? = null
|
||||
try {
|
||||
inputStream = FileInputStream(srcFile)
|
||||
val b = ByteArray(2048)
|
||||
var count: Int
|
||||
while (inputStream.read(b).also { count = it } != -1) {
|
||||
sevenZOutputFile.write(b, 0, count)
|
||||
}
|
||||
sevenZOutputFile.close()
|
||||
inputStream.close()
|
||||
upload(srcFile, desFile)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
try {
|
||||
sevenZOutputFile.close()
|
||||
inputStream!!.close()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun upload(srcFile: File?, desFile: File) {
|
||||
val requestBody: RequestBody = desFile.asRequestBody("multipart/form-data".toMediaType())
|
||||
val part = MultipartBody.Part.createFormData("file", desFile.name, requestBody)
|
||||
|
||||
val disposable = LogRetrofitHelper.getDefaultService()
|
||||
.uploadLog(part, desFile.name, "rescue-app")
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe({ it ->
|
||||
if (it.code == 200) {
|
||||
deleteLog(desFile)
|
||||
}
|
||||
srcFile?.let {
|
||||
deleteLog(it)
|
||||
}
|
||||
}, {
|
||||
}, {})
|
||||
}
|
||||
|
||||
private fun addLogHead(file: File, time: String) {
|
||||
file.appendBytes("${time}---应用版本---${AppUtils.getAppVersionName()}".toByteArray())
|
||||
file.appendBytes("\n".toByteArray())
|
||||
file.appendBytes("$time---系统版本---Android${DeviceUtils.getSDKVersionName()} ${DeviceUtils.getSDKVersionCode()}".toByteArray())
|
||||
file.appendBytes("\n".toByteArray())
|
||||
file.appendBytes("$time---ROM---${DeviceUtils.getManufacturer()} ${DeviceUtils.getModel()}".toByteArray())
|
||||
file.appendBytes("\n".toByteArray())
|
||||
file.appendBytes("$time---build---${AppUtils.getAppVersionCode()}".toByteArray())
|
||||
file.appendBytes("\n".toByteArray())
|
||||
file.appendBytes("$time---APP名称---中道救援-司机端".toByteArray())
|
||||
file.appendBytes("\n".toByteArray())
|
||||
file.appendBytes("$time---车辆名称---${GlobalData.vehicleInfo?.vehicleName}".toByteArray())
|
||||
file.appendBytes("\n".toByteArray())
|
||||
file.appendBytes("$time---司机名称---${GlobalData.driverInfo?.userName ?: GlobalData.vehicleInfo?.userName}".toByteArray())
|
||||
file.appendBytes("\n".toByteArray())
|
||||
}
|
||||
|
||||
private fun getCurrentTime(): String {
|
||||
return TimeUtils.millis2String(System.currentTimeMillis(), "yyyy/MM/dd HH:mm:ss.SSS")
|
||||
}
|
||||
|
||||
class LogTask(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {
|
||||
override fun doWork(): Result {
|
||||
updateNormalLog()
|
||||
return Result.success()
|
||||
}
|
||||
}
|
||||
}
|
82
servicing/src/main/java/com/za/common/util/AppFileManager.kt
Normal file
82
servicing/src/main/java/com/za/common/util/AppFileManager.kt
Normal file
@ -0,0 +1,82 @@
|
||||
package com.za.common.util
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import com.za.base.Const
|
||||
import java.io.File
|
||||
|
||||
object AppFileManager {
|
||||
|
||||
//获取人脸大头照存储地址
|
||||
fun getFaceDir(context: Context): File {
|
||||
val path = context.cacheDir?.path + File.separator + "faceVerify"
|
||||
val file = File(path)
|
||||
if (!file.exists()) {
|
||||
file.mkdir()
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
/**
|
||||
* 师傅签名位置保存
|
||||
*/
|
||||
fun getDriverSignDirPath(context : Context) : String {
|
||||
val tempPath = context.cacheDir.toString() + File.separator + "driver_sign"
|
||||
val tempFile = File(tempPath)
|
||||
if (! tempFile.exists()) {
|
||||
tempFile.mkdir()
|
||||
}
|
||||
return tempFile.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* 师傅签名位置保存
|
||||
*/
|
||||
fun getDriverSignPath(context : Context) : String {
|
||||
return getDriverSignDirPath(context) + File.separator + Const.driverSighName
|
||||
}
|
||||
|
||||
//人脸大头照存储地址
|
||||
fun getFaceFilePath(context: Context): String {
|
||||
return getFaceDir(context).absolutePath + File.separator + "avatar.jpg"
|
||||
}
|
||||
|
||||
/**
|
||||
* app日志路径
|
||||
*/
|
||||
fun getLogPath(context: Context?): String {
|
||||
val path = "${context?.filesDir?.absolutePath}${File.separator}Log"
|
||||
if (!File(path).exists()) {
|
||||
File(path).mkdir()
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
|
||||
fun orderWaterMarkerPath(context: Context): String {
|
||||
val path = context.cacheDir?.path + File.separator + "water_marker"
|
||||
if (!File(path).exists()) {
|
||||
File(path).mkdir()
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
/**
|
||||
* 拍摄的照片的保存位置
|
||||
*/
|
||||
fun getTakePictureParentPath(): String {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
return Environment.DIRECTORY_PICTURES
|
||||
} else {
|
||||
val dstPath =
|
||||
Environment.getExternalStorageDirectory().absolutePath + File.separator + Environment.DIRECTORY_PICTURES + File.separator + "中道救援"
|
||||
val file = File(dstPath)
|
||||
if (!file.exists()) {
|
||||
file.mkdir()
|
||||
}
|
||||
return dstPath
|
||||
}
|
||||
}
|
||||
|
||||
}
|
20
servicing/src/main/java/com/za/common/util/ClickProxy.java
Normal file
20
servicing/src/main/java/com/za/common/util/ClickProxy.java
Normal file
@ -0,0 +1,20 @@
|
||||
package com.za.common.util;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
public class ClickProxy implements View.OnClickListener {
|
||||
private long lastClick = 0;
|
||||
private final View.OnClickListener onClickListener;
|
||||
|
||||
public ClickProxy(View.OnClickListener onClickListener) {
|
||||
this.onClickListener = onClickListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (System.currentTimeMillis() - lastClick >= 1000) {
|
||||
onClickListener.onClick(v);
|
||||
lastClick = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
}
|
76
servicing/src/main/java/com/za/common/util/DeviceUtil.java
Normal file
76
servicing/src/main/java/com/za/common/util/DeviceUtil.java
Normal file
@ -0,0 +1,76 @@
|
||||
package com.za.common.util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.media.MediaDrm;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
|
||||
import com.blankj.utilcode.util.SPUtils;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 设备唯一标识
|
||||
* 顺序为:Android_ID->DRM_ID->UUID
|
||||
*/
|
||||
public class DeviceUtil {
|
||||
public static String getAndroidId(Context context) {
|
||||
String deviceId = SPUtils.getInstance().getString("deviceId");
|
||||
if (deviceId == null || deviceId.isEmpty()) {
|
||||
@SuppressLint("HardwareIds") String temp = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
|
||||
if (temp == null || temp.isEmpty() || temp.replace("0", "").isEmpty()) {
|
||||
temp = getDrmId();
|
||||
}
|
||||
SPUtils.getInstance().put("deviceId", temp);
|
||||
return temp;
|
||||
}
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
private static String getDrmId() {
|
||||
String sRet = "";
|
||||
UUID WIDEVINE_UUID = new UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L);
|
||||
MediaDrm mediaDrm = null;
|
||||
try {
|
||||
mediaDrm = new MediaDrm(WIDEVINE_UUID);
|
||||
byte[] widevineId = mediaDrm.getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID);
|
||||
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
md.update(widevineId);
|
||||
|
||||
sRet = bytesToHex(md.digest()); //we convert byte[] to hex for our purposes
|
||||
} catch (Exception e) {
|
||||
//WIDEVINE is not available
|
||||
} finally {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
if (null != mediaDrm) {
|
||||
mediaDrm.close();
|
||||
}
|
||||
} else {
|
||||
if (null != mediaDrm) {
|
||||
mediaDrm.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sRet.isEmpty()) {
|
||||
sRet = WIDEVINE_UUID.toString().replace("-", "");
|
||||
}
|
||||
return sRet;
|
||||
}
|
||||
|
||||
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
|
||||
|
||||
private static String bytesToHex(byte[] bytes) {
|
||||
char[] hexChars = new char[bytes.length * 2];
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
|
||||
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars);
|
||||
}
|
||||
}
|
||||
|
489
servicing/src/main/java/com/za/common/util/ImageUtil.kt
Normal file
489
servicing/src/main/java/com/za/common/util/ImageUtil.kt
Normal file
@ -0,0 +1,489 @@
|
||||
package com.za.common.util
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.VectorDrawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.text.format.DateUtils
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.createBitmap
|
||||
import com.amap.api.maps.model.BitmapDescriptor
|
||||
import com.amap.api.maps.model.BitmapDescriptorFactory
|
||||
import com.za.common.log.LogUtil
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* 图片工具类
|
||||
*
|
||||
* @author
|
||||
*/
|
||||
object ImageUtil {
|
||||
/**
|
||||
* 设置水印图片在左上角
|
||||
*
|
||||
* @param context
|
||||
* @param src
|
||||
* @param watermark
|
||||
* @param paddingLeft
|
||||
* @param paddingTop
|
||||
* @return
|
||||
*/
|
||||
fun createWaterMaskLeftTop(context : Context,
|
||||
src : Bitmap?,
|
||||
watermark : Bitmap,
|
||||
paddingLeft : Int,
|
||||
paddingTop : Int) : Bitmap? {
|
||||
return createWaterMaskBitmap(src,
|
||||
watermark,
|
||||
paddingLeft,
|
||||
dp2px(context, paddingTop.toFloat()))
|
||||
}
|
||||
|
||||
private fun createWaterMaskBitmap(src : Bitmap?,
|
||||
watermark : Bitmap,
|
||||
paddingLeft : Int,
|
||||
paddingTop : Int) : Bitmap? {
|
||||
if (src == null) {
|
||||
return null
|
||||
}
|
||||
val width = src.width
|
||||
val height = src.height //创建一个bitmap
|
||||
val newb = Bitmap.createBitmap(width,
|
||||
height,
|
||||
Bitmap.Config.ARGB_8888) // 创建一个新的和SRC长度宽度一样的位图 //将该图片作为画布
|
||||
val canvas = Canvas(newb) //在画布 0,0坐标上开始绘制原始图片
|
||||
canvas.drawBitmap(src, 0f, 0f, null) //在画布上绘制水印图片
|
||||
canvas.drawBitmap(watermark, paddingLeft.toFloat(), paddingTop.toFloat(), null) // 保存
|
||||
canvas.save() // 存储
|
||||
canvas.restore()
|
||||
return newb
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置水印图片在右下角
|
||||
*
|
||||
* @param context
|
||||
* @param src
|
||||
* @param watermark
|
||||
* @param paddingRight
|
||||
* @param paddingBottom
|
||||
* @return
|
||||
*/
|
||||
fun createWaterMaskRightBottom(context : Context?,
|
||||
src : Bitmap,
|
||||
watermark : Bitmap,
|
||||
paddingRight : Int,
|
||||
paddingBottom : Int) : Bitmap? {
|
||||
return createWaterMaskBitmap(src,
|
||||
watermark,
|
||||
src.width - watermark.width - paddingRight,
|
||||
src.height - watermark.height - paddingBottom)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置水印图片到右上角
|
||||
*
|
||||
* @param context
|
||||
* @param src
|
||||
* @param watermark
|
||||
* @param paddingRight
|
||||
* @param paddingTop
|
||||
* @return
|
||||
*/
|
||||
fun createWaterMaskRightTop(context : Context?,
|
||||
src : Bitmap,
|
||||
watermark : Bitmap,
|
||||
paddingRight : Int,
|
||||
paddingTop : Int) : Bitmap? {
|
||||
return createWaterMaskBitmap(src,
|
||||
watermark,
|
||||
src.width - watermark.width - paddingRight,
|
||||
paddingTop)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置水印图片到左下角
|
||||
*
|
||||
* @param context
|
||||
* @param src
|
||||
* @param watermark
|
||||
* @param paddingLeft
|
||||
* @param paddingBottom
|
||||
* @return
|
||||
*/
|
||||
fun createWaterMaskLeftBottom(context : Context?,
|
||||
src : Bitmap,
|
||||
watermark : Bitmap,
|
||||
paddingLeft : Int,
|
||||
paddingBottom : Int) : Bitmap? {
|
||||
return createWaterMaskBitmap(src,
|
||||
watermark,
|
||||
paddingLeft,
|
||||
src.height - watermark.height - paddingBottom)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置水印图片到中间
|
||||
*
|
||||
* @param src
|
||||
* @param watermark
|
||||
* @return
|
||||
*/
|
||||
fun createWaterMaskCenter(src : Bitmap, watermark : Bitmap) : Bitmap? {
|
||||
return createWaterMaskBitmap(src,
|
||||
watermark,
|
||||
(src.width - watermark.width) / 2,
|
||||
(src.height - watermark.height) / 2)
|
||||
}
|
||||
|
||||
/**
|
||||
* 给图片添加文字到左上角
|
||||
*
|
||||
* @param context
|
||||
* @param bitmap
|
||||
* @param text
|
||||
* @return
|
||||
*/
|
||||
fun drawTextToLeftTop(context : Context,
|
||||
bitmap : Bitmap,
|
||||
text : String,
|
||||
size : Int,
|
||||
color : Int,
|
||||
paddingLeft : Int,
|
||||
paddingTop : Int) : Bitmap {
|
||||
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
paint.color = color
|
||||
paint.textSize = size.toFloat()
|
||||
paint.setTypeface(Typeface.createFromAsset(context.assets, "fonts/song.ttf"))
|
||||
paint.isFakeBoldText = true
|
||||
val bounds = Rect()
|
||||
paint.getTextBounds(text, 0, text.length, bounds)
|
||||
return drawTextToBitmap(context,
|
||||
bitmap,
|
||||
text,
|
||||
paint,
|
||||
bounds,
|
||||
paddingLeft,
|
||||
paddingTop + bounds.height())
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制文字到右下角
|
||||
*
|
||||
* @param context
|
||||
* @param bitmap
|
||||
* @param text
|
||||
* @param size
|
||||
* @param color
|
||||
* @return
|
||||
*/
|
||||
fun drawTextToRightBottom(context : Context,
|
||||
bitmap : Bitmap,
|
||||
text : String,
|
||||
size : Int,
|
||||
color : Int,
|
||||
paddingRight : Int,
|
||||
paddingBottom : Int) : Bitmap {
|
||||
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
paint.color = color
|
||||
paint.textSize = size.toFloat()
|
||||
paint.setTypeface(Typeface.createFromAsset(context.assets, "fonts/song.ttf"))
|
||||
paint.isFakeBoldText = true
|
||||
val bounds = Rect()
|
||||
paint.getTextBounds(text, 0, text.length, bounds)
|
||||
return drawTextToBitmap(context,
|
||||
bitmap,
|
||||
text,
|
||||
paint,
|
||||
bounds,
|
||||
bitmap.width - bounds.width() - paddingRight,
|
||||
bitmap.height - paddingBottom)
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制文字到右上方
|
||||
*
|
||||
* @param context
|
||||
* @param bitmap
|
||||
* @param text
|
||||
* @param size
|
||||
* @param color
|
||||
* @param paddingRight
|
||||
* @param paddingTop
|
||||
* @return
|
||||
*/
|
||||
fun drawTextToRightTop(context : Context,
|
||||
bitmap : Bitmap,
|
||||
text : String,
|
||||
size : Int,
|
||||
color : Int,
|
||||
paddingRight : Int,
|
||||
paddingTop : Int) : Bitmap {
|
||||
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
paint.color = color
|
||||
paint.textSize = size.toFloat()
|
||||
paint.setTypeface(Typeface.createFromAsset(context.assets, "fonts/song.ttf"))
|
||||
paint.isFakeBoldText = true
|
||||
val bounds = Rect()
|
||||
paint.getTextBounds(text, 0, text.length, bounds)
|
||||
return drawTextToBitmap(context,
|
||||
bitmap,
|
||||
text,
|
||||
paint,
|
||||
bounds,
|
||||
bitmap.width - bounds.width() - paddingRight,
|
||||
paddingTop + bounds.height())
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制文字到左下方
|
||||
*
|
||||
* @param context
|
||||
* @param bitmap
|
||||
* @param text
|
||||
* @param size
|
||||
* @param color
|
||||
* @param paddingLeft
|
||||
* @param paddingBottom
|
||||
* @return
|
||||
*/
|
||||
fun drawTextToLeftBottom(context : Context,
|
||||
bitmap : Bitmap,
|
||||
text : String,
|
||||
size : Int,
|
||||
color : Int,
|
||||
paddingLeft : Int,
|
||||
paddingBottom : Int) : Bitmap {
|
||||
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
paint.color = color
|
||||
paint.textSize = size.toFloat()
|
||||
paint.setTypeface(Typeface.createFromAsset(context.assets, "fonts/song.ttf"))
|
||||
paint.isFakeBoldText = true
|
||||
val bounds = Rect()
|
||||
paint.getTextBounds(text, 0, text.length, bounds)
|
||||
return drawTextToBitmap(context,
|
||||
bitmap,
|
||||
text,
|
||||
paint,
|
||||
bounds,
|
||||
paddingLeft,
|
||||
bitmap.height - paddingBottom)
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制文字到中间
|
||||
*
|
||||
* @param context
|
||||
* @param bitmap
|
||||
* @param text
|
||||
* @param size
|
||||
* @param color
|
||||
* @return
|
||||
*/
|
||||
fun drawTextToCenter(context : Context,
|
||||
bitmap : Bitmap,
|
||||
text : String,
|
||||
size : Int,
|
||||
color : Int) : Bitmap {
|
||||
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
paint.color = color
|
||||
paint.textSize = size.toFloat()
|
||||
paint.setTypeface(Typeface.createFromAsset(context.assets, "fonts/song.ttf"))
|
||||
paint.isFakeBoldText = true
|
||||
val bounds = Rect()
|
||||
paint.getTextBounds(text, 0, text.length, bounds)
|
||||
return drawTextToBitmap(context,
|
||||
bitmap,
|
||||
text,
|
||||
paint,
|
||||
bounds,
|
||||
(bitmap.width - bounds.width()) / 2,
|
||||
(bitmap.height + bounds.height()) / 2)
|
||||
}
|
||||
|
||||
//图片上绘制文字
|
||||
private fun drawTextToBitmap(context : Context,
|
||||
myBitmap : Bitmap,
|
||||
text : String,
|
||||
paint : Paint,
|
||||
bounds : Rect,
|
||||
paddingLeft : Int,
|
||||
paddingTop : Int) : Bitmap {
|
||||
|
||||
val bitmapConfig = myBitmap.config
|
||||
paint.isDither = true // 获取跟清晰的图像采样
|
||||
paint.isFilterBitmap = true // 过滤一些
|
||||
val bitmap = myBitmap.copy(bitmapConfig !!, true)
|
||||
val canvas = Canvas(bitmap)
|
||||
canvas.drawText(text, paddingLeft.toFloat(), paddingTop.toFloat(), paint)
|
||||
return bitmap
|
||||
}
|
||||
|
||||
/**
|
||||
* 缩放图片
|
||||
*
|
||||
* @param src
|
||||
* @param w
|
||||
* @param h
|
||||
* @return
|
||||
*/
|
||||
fun scaleWithWH(src : Bitmap?, w : Double, h : Double) : Bitmap? {
|
||||
if (w == 0.0 || h == 0.0 || src == null) {
|
||||
return src
|
||||
} else { // 记录src的宽高
|
||||
val width = src.width
|
||||
val height = src.height // 创建一个matrix容器
|
||||
val matrix = Matrix() // 计算缩放比例
|
||||
val scaleWidth = (w / width).toFloat()
|
||||
val scaleHeight = (h / height).toFloat() // 开始缩放
|
||||
matrix.postScale(scaleWidth, scaleHeight) // 创建缩放后的图片
|
||||
return Bitmap.createBitmap(src, 0, 0, width, height, matrix, true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* dip转pix
|
||||
*
|
||||
* @param context
|
||||
* @param dp
|
||||
* @return
|
||||
*/
|
||||
private fun dp2px(context : Context, dp : Float) : Int {
|
||||
val scale = context.resources.displayMetrics.density
|
||||
return (dp * scale + 0.5f).toInt()
|
||||
}
|
||||
|
||||
|
||||
// view转bitmap
|
||||
fun createBitmapFromView(view : View) : Bitmap? { //是ImageView直接获取
|
||||
if (view is ImageView) {
|
||||
val drawable = view.drawable
|
||||
if (drawable is BitmapDrawable) {
|
||||
return drawable.bitmap
|
||||
}
|
||||
}
|
||||
view.clearFocus()
|
||||
val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bitmap)
|
||||
view.draw(canvas)
|
||||
canvas.setBitmap(null)
|
||||
return bitmap
|
||||
}
|
||||
|
||||
// 保存图片到相册
|
||||
fun saveImage(context : Context, bitmap : Bitmap?) {
|
||||
if (bitmap == null) {
|
||||
return
|
||||
}
|
||||
val isSaveSuccess = if (Build.VERSION.SDK_INT < 29) {
|
||||
saveImageToGallery(context, bitmap)
|
||||
} else {
|
||||
saveImageToGallery1(context, bitmap)
|
||||
}
|
||||
if (isSaveSuccess) {
|
||||
LogUtil.print("照片保存到相册成功", "success")
|
||||
} else {
|
||||
LogUtil.print("照片保存到相册失败", "failed")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* android 10 以下版本
|
||||
*/
|
||||
private fun saveImageToGallery(context : Context, image : Bitmap) : Boolean { // 首先保存图片
|
||||
val storePath =
|
||||
Environment.getExternalStorageDirectory().absolutePath + File.separator + "中道救援"
|
||||
|
||||
val appDir = File(storePath)
|
||||
if (! appDir.exists()) {
|
||||
appDir.mkdir()
|
||||
}
|
||||
val fileName = System.currentTimeMillis().toString() + ".jpg"
|
||||
val file = File(appDir, fileName)
|
||||
try {
|
||||
val fos = FileOutputStream(file) // 通过io流的方式来压缩保存图片
|
||||
val isSuccess = image.compress(Bitmap.CompressFormat.JPEG, 60, fos)
|
||||
fos.flush()
|
||||
fos.close()
|
||||
|
||||
// 保存图片后发送广播通知更新数据库
|
||||
val uri = Uri.fromFile(file)
|
||||
context.sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri))
|
||||
return isSuccess
|
||||
} catch (e : IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* android 10 以上版本
|
||||
*/
|
||||
private fun saveImageToGallery1(context : Context, image : Bitmap) : Boolean {
|
||||
val mImageTime = System.currentTimeMillis()
|
||||
val imageDate =
|
||||
SimpleDateFormat("yyyyMMdd-HHmmss", Locale.getDefault()).format(Date(mImageTime))
|
||||
val SCREENSHOT_FILE_NAME_TEMPLATE = "zd_%s.png" //图片名称,以"zd"+时间戳命名
|
||||
val mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate)
|
||||
|
||||
val values = ContentValues()
|
||||
values.put(MediaStore.MediaColumns.RELATIVE_PATH,
|
||||
Environment.DIRECTORY_PICTURES + File.separator + "中道救援") //Environment.DIRECTORY_SCREENSHOTS:截图,图库中显示的文件夹名
|
||||
values.put(MediaStore.MediaColumns.DISPLAY_NAME, mImageFileName)
|
||||
values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
|
||||
values.put(MediaStore.MediaColumns.DATE_ADDED, mImageTime / 1000)
|
||||
values.put(MediaStore.MediaColumns.DATE_MODIFIED, mImageTime / 1000)
|
||||
values.put(MediaStore.MediaColumns.DATE_EXPIRES,
|
||||
(mImageTime + DateUtils.DAY_IN_MILLIS) / 1000)
|
||||
values.put(MediaStore.MediaColumns.IS_PENDING, 1)
|
||||
|
||||
val resolver = context.contentResolver
|
||||
val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
|
||||
try { // First, write the actual data for our screenshot
|
||||
resolver.openOutputStream(uri !!).use { out ->
|
||||
if (! image.compress(Bitmap.CompressFormat.PNG, 100, out !!)) {
|
||||
return false
|
||||
}
|
||||
} // Everything went well above, publish it!
|
||||
values.clear()
|
||||
values.put(MediaStore.MediaColumns.IS_PENDING, 0)
|
||||
values.putNull(MediaStore.MediaColumns.DATE_EXPIRES)
|
||||
resolver.update(uri, values, null, null)
|
||||
} catch (e : IOException) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
resolver.delete(uri !!, null)
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
fun vectorToBitmap(context : Context, vectorResId : Int) : BitmapDescriptor {
|
||||
val vectorDrawable = ContextCompat.getDrawable(context, vectorResId) as VectorDrawable
|
||||
val bitmap = createBitmap(vectorDrawable.intrinsicWidth, vectorDrawable.intrinsicHeight)
|
||||
val canvas = Canvas(bitmap)
|
||||
vectorDrawable.setBounds(0, 0, canvas.width, canvas.height)
|
||||
vectorDrawable.draw(canvas)
|
||||
return BitmapDescriptorFactory.fromBitmap(bitmap)
|
||||
}
|
||||
}
|
||||
|
101
servicing/src/main/java/com/za/common/util/MapUtil.kt
Normal file
101
servicing/src/main/java/com/za/common/util/MapUtil.kt
Normal file
@ -0,0 +1,101 @@
|
||||
package com.za.common.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import com.amap.api.maps.model.LatLng
|
||||
import kotlin.math.atan2
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.ln
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.sqrt
|
||||
|
||||
object MapUtil {
|
||||
|
||||
const val PN_GAODE_MAP: String = "com.autonavi.minimap" // 高德地图包名
|
||||
const val PN_BAIDU_MAP: String = "com.baidu.BaiduMap" // 百度地图包名
|
||||
const val PN_TENCENT_MAP: String = "com.tencent.map" // 百度地图包名
|
||||
|
||||
/**
|
||||
* 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换
|
||||
* 即谷歌、高德 转 百度
|
||||
*
|
||||
* @param latLng
|
||||
* @returns
|
||||
*/
|
||||
fun gcj02ToBD09(latLng: LatLng): LatLng {
|
||||
val xPI = 3.141592653589793 * 3000.0 / 180.0
|
||||
val z = sqrt(latLng.longitude * latLng.longitude + latLng.latitude * latLng.latitude) + 0.00002 * sin(latLng.latitude * xPI)
|
||||
val theta = atan2(latLng.latitude, latLng.longitude) + 0.000003 * cos(latLng.longitude * xPI)
|
||||
val bdLat = z * sin(theta) + 0.006
|
||||
val bdLng = z * cos(theta) + 0.0065
|
||||
return LatLng(bdLat, bdLng)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查应用是否安装
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
fun isGdMapInstalled(context: Context): Boolean {
|
||||
return isInstallPackage(context, PN_GAODE_MAP)
|
||||
}
|
||||
|
||||
fun isBaiduMapInstalled(context: Context): Boolean {
|
||||
return isInstallPackage(context, PN_BAIDU_MAP)
|
||||
}
|
||||
|
||||
fun isTencentInstalled(context: Context): Boolean {
|
||||
return isInstallPackage(context, PN_TENCENT_MAP)
|
||||
}
|
||||
|
||||
private fun isInstallPackage(context: Context, packageName: String): Boolean {
|
||||
try {
|
||||
val info = context.packageManager
|
||||
.getApplicationInfo(packageName,
|
||||
PackageManager.GET_UNINSTALLED_PACKAGES)
|
||||
return true
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//开启高德导航
|
||||
fun startNavigationGd(context: Context, lat: Double?, lng: Double?, address: String?) {
|
||||
val stringBuffer = "androidamap://route?sourceApplication=" + "amap" +
|
||||
"&dlat=" + lat +
|
||||
"&dlon=" + lng +
|
||||
"&dname=" + address +
|
||||
"&dev=" + 0 +
|
||||
"&t=" + 0
|
||||
val intent = Intent("android.intent.action.VIEW", Uri.parse(stringBuffer))
|
||||
intent.setPackage("com.autonavi.minimap")
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
//开启高德导航
|
||||
fun startNavigationBd(context: Context, lat: Double?, lng: Double?, address: String?) {
|
||||
val i1 = Intent("android.intent.action.VIEW")
|
||||
i1.setData(Uri.parse("baidumap://map/navi?query=${address}&mode=driving&location=${lat},${lng}&coord_type=gcj02&src=com.za.servicing"))
|
||||
context.startActivity(i1)
|
||||
}
|
||||
|
||||
//开启高德导航
|
||||
fun startNavigationTencent(context: Context, lat: Double?, lng: Double?, address: String?) {
|
||||
val stringBuffer = "androidamap://route?sourceApplication=" + "amap" +
|
||||
"&dlat=" + lat +
|
||||
"&dlon=" + lng +
|
||||
"&dname=" + address +
|
||||
"&dev=" + 0 +
|
||||
"&t=" + 0
|
||||
val intent = Intent("android.intent.action.VIEW", Uri.parse(stringBuffer))
|
||||
intent.setPackage("com.autonavi.minimap")
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.za.common.util
|
||||
|
||||
object NotificationUtil {
|
||||
const val CHANNEL_ID = "1001"
|
||||
}
|
92
servicing/src/main/java/com/za/common/util/QRCodeUtil.kt
Normal file
92
servicing/src/main/java/com/za/common/util/QRCodeUtil.kt
Normal file
@ -0,0 +1,92 @@
|
||||
package com.za.common.util
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.text.TextUtils
|
||||
import androidx.annotation.ColorInt
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.EncodeHintType
|
||||
import com.google.zxing.WriterException
|
||||
import com.google.zxing.qrcode.QRCodeWriter
|
||||
import java.util.Hashtable
|
||||
|
||||
/**
|
||||
* @ClassName: QRCodeUtil
|
||||
* @Description: 二维码工具类
|
||||
*/
|
||||
object QRCodeUtil {
|
||||
/**
|
||||
* 创建二维码位图 (支持自定义配置和自定义样式)
|
||||
*
|
||||
* @param content 字符串内容
|
||||
* @param width 位图宽度,要求>=0(单位:px)
|
||||
* @param height 位图高度,要求>=0(单位:px)
|
||||
* @param character_set 字符集/字符转码格式 (支持格式:[CharacterSetECI])。传null时,zxing源码默认使用 "ISO-8859-1"
|
||||
* @param error_correction 容错级别 (支持级别:[ErrorCorrectionLevel])。传null时,zxing源码默认使用 "L"
|
||||
* @param margin 空白边距 (可修改,要求:整型且>=0), 传null时,zxing源码默认使用"4"。
|
||||
* @param color_black 黑色色块的自定义颜色值
|
||||
* @param color_white 白色色块的自定义颜色值
|
||||
* @return
|
||||
*/
|
||||
/**
|
||||
* 创建二维码位图
|
||||
*
|
||||
* @param content 字符串内容(支持中文)
|
||||
* @param width 位图宽度(单位:px)
|
||||
* @param height 位图高度(单位:px)
|
||||
* @return
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun createQRCodeBitmap(content: String?, width: Int, height: Int,
|
||||
characterSet: String? = "UTF-8", errorCorrection: String? = "H", margin: String? = "2",
|
||||
@ColorInt colorBlack: Int = Color.BLACK, @ColorInt colorWhite: Int = Color.WHITE): Bitmap? {
|
||||
/** 1.参数合法性判断 */
|
||||
|
||||
if (TextUtils.isEmpty(content)) { // 字符串内容判空
|
||||
return null
|
||||
}
|
||||
|
||||
if (width < 0 || height < 0) { // 宽和高都需要>=0
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
/** 2.设置二维码相关配置,生成BitMatrix(位矩阵)对象 */
|
||||
val hints = Hashtable<EncodeHintType, String?>()
|
||||
|
||||
if (!TextUtils.isEmpty(characterSet)) {
|
||||
hints[EncodeHintType.CHARACTER_SET] = characterSet // 字符转码格式设置
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(errorCorrection)) {
|
||||
hints[EncodeHintType.ERROR_CORRECTION] = errorCorrection // 容错级别设置
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(margin)) {
|
||||
hints[EncodeHintType.MARGIN] = margin // 空白边距设置
|
||||
}
|
||||
val bitMatrix = QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints)
|
||||
|
||||
/** 3.创建像素数组,并根据BitMatrix(位矩阵)对象为数组元素赋颜色值 */
|
||||
val pixels = IntArray(width * height)
|
||||
for (y in 0 until height) {
|
||||
for (x in 0 until width) {
|
||||
if (bitMatrix[x, y]) {
|
||||
pixels[y * width + x] = colorBlack // 黑色色块像素设置
|
||||
} else {
|
||||
pixels[y * width + x] = colorWhite // 白色色块像素设置
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 4.创建Bitmap对象,根据像素数组设置Bitmap每个像素点的颜色值,之后返回Bitmap对象 */
|
||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
bitmap.setPixels(pixels, 0, width, 0, 0, width, height)
|
||||
return bitmap
|
||||
} catch (e: WriterException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package com.za.common.util
|
||||
|
||||
import android.content.Context
|
||||
import android.media.MediaPlayer
|
||||
import com.za.servicing.R
|
||||
|
||||
object ServicingSpeechManager {
|
||||
private var mediaPlayer: MediaPlayer? = null
|
||||
|
||||
// 提醒客户签字
|
||||
fun playOrderCustomSign(mContext: Context) {
|
||||
mediaPlayer = MediaPlayer.create(mContext, R.raw.custom_sign)
|
||||
mediaPlayer?.start()
|
||||
}
|
||||
|
||||
|
||||
// 提醒接车人签字
|
||||
fun playOrderAcceptSign(mContext: Context) {
|
||||
mediaPlayer = MediaPlayer.create(mContext, R.raw.accept_sign)
|
||||
mediaPlayer?.start()
|
||||
}
|
||||
|
||||
// 车主签字
|
||||
fun playCarOwnerSign(mContext: Context) {
|
||||
mediaPlayer = MediaPlayer.create(mContext, R.raw.car_onwer_sign)
|
||||
mediaPlayer?.start()
|
||||
}
|
||||
|
||||
// 提醒五星好评
|
||||
fun playOrderGoodService(mContext: Context) {
|
||||
mediaPlayer = MediaPlayer.create(mContext, R.raw.good_star)
|
||||
mediaPlayer?.start()
|
||||
}
|
||||
|
||||
//提醒客户不需要再上传纸质工单
|
||||
fun playNoUploadEleOrderWork(mContext: Context) {
|
||||
mediaPlayer = MediaPlayer.create(mContext, R.raw.no_upload_ele_order_work)
|
||||
mediaPlayer?.start()
|
||||
}
|
||||
|
||||
|
||||
// 发车前提示
|
||||
fun playStartTip(mContext: Context) {
|
||||
mediaPlayer = MediaPlayer.create(mContext, R.raw.start_tip)
|
||||
mediaPlayer?.start()
|
||||
}
|
||||
|
||||
// 验车前提示
|
||||
fun playCheckTip(mContext: Context) {
|
||||
mediaPlayer = MediaPlayer.create(mContext, R.raw.check_tip)
|
||||
mediaPlayer?.start()
|
||||
}
|
||||
}
|
33
servicing/src/main/java/com/za/common/util/Tools.kt
Normal file
33
servicing/src/main/java/com/za/common/util/Tools.kt
Normal file
@ -0,0 +1,33 @@
|
||||
package com.za.common.util
|
||||
|
||||
import android.util.Base64
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
/**
|
||||
* @author DoggieX
|
||||
* @create 2021/3/22 16:17
|
||||
* @mail coldpuppy@163.com
|
||||
*/
|
||||
object Tools {
|
||||
/**
|
||||
* @param text 要签名的文本
|
||||
* @param secretKey 阿里云MQ secretKey
|
||||
* @return 加密后的字符串
|
||||
* @throws InvalidKeyException
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
@Throws(InvalidKeyException::class, NoSuchAlgorithmException::class)
|
||||
fun macSignature(text: String, secretKey: String): String {
|
||||
val charset = StandardCharsets.UTF_8
|
||||
val algorithm = "HmacSHA1"
|
||||
val mac = Mac.getInstance(algorithm)
|
||||
mac.init(SecretKeySpec(secretKey.toByteArray(charset), algorithm))
|
||||
val bytes = mac.doFinal(text.toByteArray(charset))
|
||||
// android的base64编码注意换行符情况, 使用NO_WRAP
|
||||
return String(Base64.encode(bytes, Base64.NO_WRAP), charset)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user